ip_fw_pfil.c revision 196019
1/*-
2 * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/netinet/ipfw/ip_fw_pfil.c 196019 2009-08-01 19:26:27Z rwatson $");
29
30#if !defined(KLD_MODULE)
31#include "opt_ipfw.h"
32#include "opt_ipdn.h"
33#include "opt_inet.h"
34#ifndef INET
35#error IPFIREWALL requires INET.
36#endif /* INET */
37#endif /* KLD_MODULE */
38#include "opt_inet6.h"
39
40#include <sys/param.h>
41#include <sys/systm.h>
42#include <sys/malloc.h>
43#include <sys/mbuf.h>
44#include <sys/module.h>
45#include <sys/kernel.h>
46#include <sys/lock.h>
47#include <sys/rwlock.h>
48#include <sys/socket.h>
49#include <sys/socketvar.h>
50#include <sys/sysctl.h>
51#include <sys/ucred.h>
52
53#include <net/if.h>
54#include <net/route.h>
55#include <net/pfil.h>
56
57#include <netinet/in.h>
58#include <netinet/in_systm.h>
59#include <netinet/ip.h>
60#include <netinet/ip_var.h>
61#include <netinet/ip_fw.h>
62#include <netinet/ip_divert.h>
63#include <netinet/ip_dummynet.h>
64
65#include <netgraph/ng_ipfw.h>
66
67#include <machine/in_cksum.h>
68
69VNET_DEFINE(int, fw_enable) = 1;
70#ifdef INET6
71VNET_DEFINE(int, fw6_enable) = 1;
72#endif
73
74int ipfw_chg_hook(SYSCTL_HANDLER_ARGS);
75
76/* Divert hooks. */
77ip_divert_packet_t *ip_divert_ptr = NULL;
78
79/* ng_ipfw hooks. */
80ng_ipfw_input_t *ng_ipfw_input_p = NULL;
81
82/* Forward declarations. */
83static int	ipfw_divert(struct mbuf **, int, int);
84#define	DIV_DIR_IN	1
85#define	DIV_DIR_OUT	0
86
87int
88ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
89    struct inpcb *inp)
90{
91	struct ip_fw_args args;
92	struct ng_ipfw_tag *ng_tag;
93	struct m_tag *dn_tag;
94	int ipfw = 0;
95	int divert;
96	int tee;
97#ifdef IPFIREWALL_FORWARD
98	struct m_tag *fwd_tag;
99#endif
100
101	KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!"));
102
103	bzero(&args, sizeof(args));
104
105	ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
106	    NULL);
107	if (ng_tag != NULL) {
108		KASSERT(ng_tag->dir == NG_IPFW_IN,
109		    ("ng_ipfw tag with wrong direction"));
110		args.rule = ng_tag->rule;
111		args.rule_id = ng_tag->rule_id;
112		args.chain_id = ng_tag->chain_id;
113		m_tag_delete(*m0, (struct m_tag *)ng_tag);
114	}
115
116again:
117	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
118	if (dn_tag != NULL){
119		struct dn_pkt_tag *dt;
120
121		dt = (struct dn_pkt_tag *)(dn_tag+1);
122		args.rule = dt->rule;
123		args.rule_id = dt->rule_id;
124		args.chain_id = dt->chain_id;
125
126		m_tag_delete(*m0, dn_tag);
127	}
128
129	args.m = *m0;
130	args.inp = inp;
131	tee = 0;
132
133	if (V_fw_one_pass == 0 || args.rule == NULL) {
134		ipfw = ipfw_chk(&args);
135		*m0 = args.m;
136	} else
137		ipfw = IP_FW_PASS;
138
139	KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
140	    __func__));
141
142	switch (ipfw) {
143	case IP_FW_PASS:
144		if (args.next_hop == NULL)
145			goto pass;
146
147#ifdef IPFIREWALL_FORWARD
148		fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
149				sizeof(struct sockaddr_in), M_NOWAIT);
150		if (fwd_tag == NULL)
151			goto drop;
152		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
153		m_tag_prepend(*m0, fwd_tag);
154
155		if (in_localip(args.next_hop->sin_addr))
156			(*m0)->m_flags |= M_FASTFWD_OURS;
157		goto pass;
158#endif
159		break;			/* not reached */
160
161	case IP_FW_DENY:
162		goto drop;
163		break;			/* not reached */
164
165	case IP_FW_DUMMYNET:
166		if (ip_dn_io_ptr == NULL)
167			goto drop;
168		if (mtod(*m0, struct ip *)->ip_v == 4)
169			ip_dn_io_ptr(m0, DN_TO_IP_IN, &args);
170		else if (mtod(*m0, struct ip *)->ip_v == 6)
171			ip_dn_io_ptr(m0, DN_TO_IP6_IN, &args);
172		if (*m0 != NULL)
173			goto again;
174		return 0;		/* packet consumed */
175
176	case IP_FW_TEE:
177		tee = 1;
178		/* fall through */
179
180	case IP_FW_DIVERT:
181		divert = ipfw_divert(m0, DIV_DIR_IN, tee);
182		if (divert) {
183			*m0 = NULL;
184			return 0;	/* packet consumed */
185		} else {
186			args.rule = NULL;
187			goto again;	/* continue with packet */
188		}
189
190	case IP_FW_NGTEE:
191		if (!NG_IPFW_LOADED)
192			goto drop;
193		(void)ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 1);
194		goto again;		/* continue with packet */
195
196	case IP_FW_NETGRAPH:
197		if (!NG_IPFW_LOADED)
198			goto drop;
199		return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0);
200
201	case IP_FW_NAT:
202		goto again;		/* continue with packet */
203
204	case IP_FW_REASS:
205		goto again;
206
207	default:
208		KASSERT(0, ("%s: unknown retval", __func__));
209	}
210
211drop:
212	if (*m0)
213		m_freem(*m0);
214	*m0 = NULL;
215	return (EACCES);
216pass:
217	return 0;	/* not filtered */
218}
219
220int
221ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
222    struct inpcb *inp)
223{
224	struct ip_fw_args args;
225	struct ng_ipfw_tag *ng_tag;
226	struct m_tag *dn_tag;
227	int ipfw = 0;
228	int divert;
229	int tee;
230#ifdef IPFIREWALL_FORWARD
231	struct m_tag *fwd_tag;
232#endif
233
234	KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!"));
235
236	bzero(&args, sizeof(args));
237
238	ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
239	    NULL);
240	if (ng_tag != NULL) {
241		KASSERT(ng_tag->dir == NG_IPFW_OUT,
242		    ("ng_ipfw tag with wrong direction"));
243		args.rule = ng_tag->rule;
244		args.rule_id = ng_tag->rule_id;
245		args.chain_id = ng_tag->chain_id;
246		m_tag_delete(*m0, (struct m_tag *)ng_tag);
247	}
248
249again:
250	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
251	if (dn_tag != NULL) {
252		struct dn_pkt_tag *dt;
253
254		dt = (struct dn_pkt_tag *)(dn_tag+1);
255		args.rule = dt->rule;
256		args.rule_id = dt->rule_id;
257		args.chain_id = dt->chain_id;
258
259		m_tag_delete(*m0, dn_tag);
260	}
261
262	args.m = *m0;
263	args.oif = ifp;
264	args.inp = inp;
265	tee = 0;
266
267	if (V_fw_one_pass == 0 || args.rule == NULL) {
268		ipfw = ipfw_chk(&args);
269		*m0 = args.m;
270	} else
271		ipfw = IP_FW_PASS;
272
273	KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
274	    __func__));
275
276	switch (ipfw) {
277	case IP_FW_PASS:
278                if (args.next_hop == NULL)
279                        goto pass;
280#ifdef IPFIREWALL_FORWARD
281		/* Overwrite existing tag. */
282		fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
283		if (fwd_tag == NULL) {
284			fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
285				sizeof(struct sockaddr_in), M_NOWAIT);
286			if (fwd_tag == NULL)
287				goto drop;
288		} else
289			m_tag_unlink(*m0, fwd_tag);
290		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
291		m_tag_prepend(*m0, fwd_tag);
292
293		if (in_localip(args.next_hop->sin_addr))
294			(*m0)->m_flags |= M_FASTFWD_OURS;
295		goto pass;
296#endif
297		break;			/* not reached */
298
299	case IP_FW_DENY:
300		goto drop;
301		break;  		/* not reached */
302
303	case IP_FW_DUMMYNET:
304		if (ip_dn_io_ptr == NULL)
305			break;
306		if (mtod(*m0, struct ip *)->ip_v == 4)
307			ip_dn_io_ptr(m0, DN_TO_IP_OUT, &args);
308		else if (mtod(*m0, struct ip *)->ip_v == 6)
309			ip_dn_io_ptr(m0, DN_TO_IP6_OUT, &args);
310		if (*m0 != NULL)
311			goto again;
312		return 0;		/* packet consumed */
313
314		break;
315
316	case IP_FW_TEE:
317		tee = 1;
318		/* fall through */
319
320	case IP_FW_DIVERT:
321		divert = ipfw_divert(m0, DIV_DIR_OUT, tee);
322		if (divert) {
323			*m0 = NULL;
324			return 0;	/* packet consumed */
325		} else {
326			args.rule = NULL;
327			goto again;	/* continue with packet */
328		}
329
330	case IP_FW_NGTEE:
331		if (!NG_IPFW_LOADED)
332			goto drop;
333		(void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1);
334		goto again;		/* continue with packet */
335
336	case IP_FW_NETGRAPH:
337		if (!NG_IPFW_LOADED)
338			goto drop;
339		return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0);
340
341	case IP_FW_NAT:
342		goto again;		/* continue with packet */
343
344	case IP_FW_REASS:
345		goto again;
346
347	default:
348		KASSERT(0, ("%s: unknown retval", __func__));
349	}
350
351drop:
352	if (*m0)
353		m_freem(*m0);
354	*m0 = NULL;
355	return (EACCES);
356pass:
357	return 0;	/* not filtered */
358}
359
360static int
361ipfw_divert(struct mbuf **m, int incoming, int tee)
362{
363	/*
364	 * ipfw_chk() has already tagged the packet with the divert tag.
365	 * If tee is set, copy packet and return original.
366	 * If not tee, consume packet and send it to divert socket.
367	 */
368	struct mbuf *clone, *reass;
369	struct ip *ip;
370	int hlen;
371
372	reass = NULL;
373
374	/* Is divert module loaded? */
375	if (ip_divert_ptr == NULL)
376		goto nodivert;
377
378	/* Cloning needed for tee? */
379	if (tee)
380		clone = m_dup(*m, M_DONTWAIT);
381	else
382		clone = *m;
383
384	/* In case m_dup was unable to allocate mbufs. */
385	if (clone == NULL)
386		goto teeout;
387
388	/*
389	 * Divert listeners can only handle non-fragmented packets.
390	 * However when tee is set we will *not* de-fragment the packets;
391	 * Doing do would put the reassembly into double-jeopardy.  On top
392	 * of that someone doing a tee will probably want to get the packet
393	 * in its original form.
394	 */
395	ip = mtod(clone, struct ip *);
396	if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
397
398		/* Reassemble packet. */
399		reass = ip_reass(clone);
400
401		/*
402		 * IP header checksum fixup after reassembly and leave header
403		 * in network byte order.
404		 */
405		if (reass != NULL) {
406			ip = mtod(reass, struct ip *);
407			hlen = ip->ip_hl << 2;
408			ip->ip_len = htons(ip->ip_len);
409			ip->ip_off = htons(ip->ip_off);
410			ip->ip_sum = 0;
411			if (hlen == sizeof(struct ip))
412				ip->ip_sum = in_cksum_hdr(ip);
413			else
414				ip->ip_sum = in_cksum(reass, hlen);
415			clone = reass;
416		} else
417			clone = NULL;
418	} else {
419		/* Convert header to network byte order. */
420		ip->ip_len = htons(ip->ip_len);
421		ip->ip_off = htons(ip->ip_off);
422	}
423
424	/* Do the dirty job... */
425	if (clone && ip_divert_ptr != NULL)
426		ip_divert_ptr(clone, incoming);
427
428teeout:
429	/*
430	 * For tee we leave the divert tag attached to original packet.
431	 * It will then continue rule evaluation after the tee rule.
432	 */
433	if (tee)
434		return 0;
435
436	/* Packet diverted and consumed */
437	return 1;
438
439nodivert:
440	m_freem(*m);
441	return 1;
442}
443
444static int
445ipfw_hook(void)
446{
447	struct pfil_head *pfh_inet;
448
449	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
450	if (pfh_inet == NULL)
451		return ENOENT;
452
453	(void)pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
454	    pfh_inet);
455	(void)pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
456	    pfh_inet);
457
458	return 0;
459}
460
461static int
462ipfw_unhook(void)
463{
464	struct pfil_head *pfh_inet;
465
466	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
467	if (pfh_inet == NULL)
468		return ENOENT;
469
470	(void)pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
471	    pfh_inet);
472	(void)pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
473	    pfh_inet);
474
475	return 0;
476}
477
478#ifdef INET6
479static int
480ipfw6_hook(void)
481{
482	struct pfil_head *pfh_inet6;
483
484	pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
485	if (pfh_inet6 == NULL)
486		return ENOENT;
487
488	(void)pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
489	    pfh_inet6);
490	(void)pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
491	    pfh_inet6);
492
493	return 0;
494}
495
496static int
497ipfw6_unhook(void)
498{
499	struct pfil_head *pfh_inet6;
500
501	pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
502	if (pfh_inet6 == NULL)
503		return ENOENT;
504
505	(void)pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
506	    pfh_inet6);
507	(void)pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
508	    pfh_inet6);
509
510	return 0;
511}
512#endif /* INET6 */
513
514int
515ipfw_chg_hook(SYSCTL_HANDLER_ARGS)
516{
517	int enable = *(int *)arg1;
518	int error;
519
520	error = sysctl_handle_int(oidp, &enable, 0, req);
521	if (error)
522		return (error);
523
524	enable = (enable) ? 1 : 0;
525
526	if (enable == *(int *)arg1)
527		return (0);
528
529	if (arg1 == &V_fw_enable) {
530		if (enable)
531			error = ipfw_hook();
532		else
533			error = ipfw_unhook();
534	}
535#ifdef INET6
536	if (arg1 == &V_fw6_enable) {
537		if (enable)
538			error = ipfw6_hook();
539		else
540			error = ipfw6_unhook();
541	}
542#endif
543
544	if (error)
545		return (error);
546
547	*(int *)arg1 = enable;
548
549	return (0);
550}
551
552static int
553ipfw_modevent(module_t mod, int type, void *unused)
554{
555	int err = 0;
556
557	switch (type) {
558	case MOD_LOAD:
559		if ((err = ipfw_init()) != 0) {
560			printf("ipfw_init() error\n");
561			break;
562		}
563		if ((err = ipfw_hook()) != 0) {
564			printf("ipfw_hook() error\n");
565			break;
566		}
567#ifdef INET6
568		if ((err = ipfw6_hook()) != 0) {
569			printf("ipfw_hook() error\n");
570			break;
571		}
572#endif
573		break;
574
575	case MOD_UNLOAD:
576		if ((err = ipfw_unhook()) > 0)
577			break;
578#ifdef INET6
579		if ((err = ipfw6_unhook()) > 0)
580			break;
581#endif
582		ipfw_destroy();
583		break;
584
585	default:
586		return EOPNOTSUPP;
587		break;
588	}
589	return err;
590}
591
592static moduledata_t ipfwmod = {
593	"ipfw",
594	ipfw_modevent,
595	0
596};
597DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY - 256);
598MODULE_VERSION(ipfw, 2);
599