ip_fw_pfil.c revision 173399
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/ip_fw_pfil.c 173399 2007-11-06 23:01:42Z oleg $");
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/socket.h>
47#include <sys/socketvar.h>
48#include <sys/sysctl.h>
49#include <sys/ucred.h>
50
51#include <net/if.h>
52#include <net/route.h>
53#include <net/pfil.h>
54
55#include <netinet/in.h>
56#include <netinet/in_systm.h>
57#include <netinet/in_var.h>
58#include <netinet/ip.h>
59#include <netinet/ip_var.h>
60#include <netinet/ip_fw.h>
61#include <netinet/ip_divert.h>
62#include <netinet/ip_dummynet.h>
63
64#include <netgraph/ng_ipfw.h>
65
66#include <machine/in_cksum.h>
67
68int fw_enable = 1;
69#ifdef INET6
70int fw6_enable = 1;
71#endif
72
73int ipfw_chg_hook(SYSCTL_HANDLER_ARGS);
74
75/* Dummynet hooks. */
76ip_dn_ruledel_t	*ip_dn_ruledel_ptr = NULL;
77
78/* Divert hooks. */
79ip_divert_packet_t *ip_divert_ptr = NULL;
80
81/* ng_ipfw hooks. */
82ng_ipfw_input_t *ng_ipfw_input_p = NULL;
83
84/* Forward declarations. */
85static int	ipfw_divert(struct mbuf **, int, int);
86#define	DIV_DIR_IN	1
87#define	DIV_DIR_OUT	0
88
89int
90ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
91    struct inpcb *inp)
92{
93	struct ip_fw_args args;
94	struct ng_ipfw_tag *ng_tag;
95	struct m_tag *dn_tag;
96	int ipfw = 0;
97	int divert;
98	int tee;
99#ifdef IPFIREWALL_FORWARD
100	struct m_tag *fwd_tag;
101#endif
102
103	KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!"));
104
105	bzero(&args, sizeof(args));
106
107	ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
108	    NULL);
109	if (ng_tag != NULL) {
110		KASSERT(ng_tag->dir == NG_IPFW_IN,
111		    ("ng_ipfw tag with wrong direction"));
112		args.rule = ng_tag->rule;
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
124		m_tag_delete(*m0, dn_tag);
125	}
126
127	args.m = *m0;
128	args.inp = inp;
129	ipfw = ipfw_chk(&args);
130	*m0 = args.m;
131	tee = 0;
132
133	KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
134	    __func__));
135
136	switch (ipfw) {
137	case IP_FW_PASS:
138		if (args.next_hop == NULL)
139			goto pass;
140
141#ifdef IPFIREWALL_FORWARD
142		fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
143				sizeof(struct sockaddr_in), M_NOWAIT);
144		if (fwd_tag == NULL)
145			goto drop;
146		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
147		m_tag_prepend(*m0, fwd_tag);
148
149		if (in_localip(args.next_hop->sin_addr))
150			(*m0)->m_flags |= M_FASTFWD_OURS;
151		goto pass;
152#endif
153		break;			/* not reached */
154
155	case IP_FW_DENY:
156		goto drop;
157		break;			/* not reached */
158
159	case IP_FW_DUMMYNET:
160		if (!DUMMYNET_LOADED)
161			goto drop;
162		if (mtod(*m0, struct ip *)->ip_v == 4)
163			ip_dn_io_ptr(m0, DN_TO_IP_IN, &args);
164		else if (mtod(*m0, struct ip *)->ip_v == 6)
165			ip_dn_io_ptr(m0, DN_TO_IP6_IN, &args);
166		if (*m0 != NULL)
167			goto again;
168		return 0;		/* packet consumed */
169
170	case IP_FW_TEE:
171		tee = 1;
172		/* fall through */
173
174	case IP_FW_DIVERT:
175		divert = ipfw_divert(m0, DIV_DIR_IN, tee);
176		if (divert) {
177			*m0 = NULL;
178			return 0;	/* packet consumed */
179		} else {
180			args.rule = NULL;
181			goto again;	/* continue with packet */
182		}
183
184	case IP_FW_NGTEE:
185		if (!NG_IPFW_LOADED)
186			goto drop;
187		(void)ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 1);
188		goto again;		/* continue with packet */
189
190	case IP_FW_NETGRAPH:
191		if (!NG_IPFW_LOADED)
192			goto drop;
193		return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0);
194
195	case IP_FW_NAT:
196		goto again;		/* continue with packet */
197
198	default:
199		KASSERT(0, ("%s: unknown retval", __func__));
200	}
201
202drop:
203	if (*m0)
204		m_freem(*m0);
205	*m0 = NULL;
206	return (EACCES);
207pass:
208	return 0;	/* not filtered */
209}
210
211int
212ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
213    struct inpcb *inp)
214{
215	struct ip_fw_args args;
216	struct ng_ipfw_tag *ng_tag;
217	struct m_tag *dn_tag;
218	int ipfw = 0;
219	int divert;
220	int tee;
221#ifdef IPFIREWALL_FORWARD
222	struct m_tag *fwd_tag;
223#endif
224
225	KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!"));
226
227	bzero(&args, sizeof(args));
228
229	ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
230	    NULL);
231	if (ng_tag != NULL) {
232		KASSERT(ng_tag->dir == NG_IPFW_OUT,
233		    ("ng_ipfw tag with wrong direction"));
234		args.rule = ng_tag->rule;
235		m_tag_delete(*m0, (struct m_tag *)ng_tag);
236	}
237
238again:
239	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
240	if (dn_tag != NULL) {
241		struct dn_pkt_tag *dt;
242
243		dt = (struct dn_pkt_tag *)(dn_tag+1);
244		args.rule = dt->rule;
245
246		m_tag_delete(*m0, dn_tag);
247	}
248
249	args.m = *m0;
250	args.oif = ifp;
251	args.inp = inp;
252	ipfw = ipfw_chk(&args);
253	*m0 = args.m;
254	tee = 0;
255
256	KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
257	    __func__));
258
259	switch (ipfw) {
260	case IP_FW_PASS:
261                if (args.next_hop == NULL)
262                        goto pass;
263#ifdef IPFIREWALL_FORWARD
264		/* Overwrite existing tag. */
265		fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
266		if (fwd_tag == NULL) {
267			fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
268				sizeof(struct sockaddr_in), M_NOWAIT);
269			if (fwd_tag == NULL)
270				goto drop;
271		} else
272			m_tag_unlink(*m0, fwd_tag);
273		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
274		m_tag_prepend(*m0, fwd_tag);
275
276		if (in_localip(args.next_hop->sin_addr))
277			(*m0)->m_flags |= M_FASTFWD_OURS;
278		goto pass;
279#endif
280		break;			/* not reached */
281
282	case IP_FW_DENY:
283		goto drop;
284		break;  		/* not reached */
285
286	case IP_FW_DUMMYNET:
287		if (!DUMMYNET_LOADED)
288			break;
289		if (mtod(*m0, struct ip *)->ip_v == 4)
290			ip_dn_io_ptr(m0, DN_TO_IP_OUT, &args);
291		else if (mtod(*m0, struct ip *)->ip_v == 6)
292			ip_dn_io_ptr(m0, DN_TO_IP6_OUT, &args);
293		if (*m0 != NULL)
294			goto again;
295		return 0;		/* packet consumed */
296
297		break;
298
299	case IP_FW_TEE:
300		tee = 1;
301		/* fall through */
302
303	case IP_FW_DIVERT:
304		divert = ipfw_divert(m0, DIV_DIR_OUT, tee);
305		if (divert) {
306			*m0 = NULL;
307			return 0;	/* packet consumed */
308		} else {
309			args.rule = NULL;
310			goto again;	/* continue with packet */
311		}
312
313	case IP_FW_NGTEE:
314		if (!NG_IPFW_LOADED)
315			goto drop;
316		(void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1);
317		goto again;		/* continue with packet */
318
319	case IP_FW_NETGRAPH:
320		if (!NG_IPFW_LOADED)
321			goto drop;
322		return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0);
323
324	case IP_FW_NAT:
325		goto again;		/* continue with packet */
326
327	default:
328		KASSERT(0, ("%s: unknown retval", __func__));
329	}
330
331drop:
332	if (*m0)
333		m_freem(*m0);
334	*m0 = NULL;
335	return (EACCES);
336pass:
337	return 0;	/* not filtered */
338}
339
340static int
341ipfw_divert(struct mbuf **m, int incoming, int tee)
342{
343	/*
344	 * ipfw_chk() has already tagged the packet with the divert tag.
345	 * If tee is set, copy packet and return original.
346	 * If not tee, consume packet and send it to divert socket.
347	 */
348	struct mbuf *clone, *reass;
349	struct ip *ip;
350	int hlen;
351
352	reass = NULL;
353
354	/* Is divert module loaded? */
355	if (ip_divert_ptr == NULL)
356		goto nodivert;
357
358	/* Cloning needed for tee? */
359	if (tee)
360		clone = m_dup(*m, M_DONTWAIT);
361	else
362		clone = *m;
363
364	/* In case m_dup was unable to allocate mbufs. */
365	if (clone == NULL)
366		goto teeout;
367
368	/*
369	 * Divert listeners can only handle non-fragmented packets.
370	 * However when tee is set we will *not* de-fragment the packets;
371	 * Doing do would put the reassembly into double-jeopardy.  On top
372	 * of that someone doing a tee will probably want to get the packet
373	 * in its original form.
374	 */
375	ip = mtod(clone, struct ip *);
376	if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
377
378		/* Reassemble packet. */
379		reass = ip_reass(clone);
380
381		/*
382		 * IP header checksum fixup after reassembly and leave header
383		 * in network byte order.
384		 */
385		if (reass != NULL) {
386			ip = mtod(reass, struct ip *);
387			hlen = ip->ip_hl << 2;
388			ip->ip_len = htons(ip->ip_len);
389			ip->ip_off = htons(ip->ip_off);
390			ip->ip_sum = 0;
391			if (hlen == sizeof(struct ip))
392				ip->ip_sum = in_cksum_hdr(ip);
393			else
394				ip->ip_sum = in_cksum(reass, hlen);
395			clone = reass;
396		} else
397			clone = NULL;
398	} else {
399		/* Convert header to network byte order. */
400		ip->ip_len = htons(ip->ip_len);
401		ip->ip_off = htons(ip->ip_off);
402	}
403
404	/* Do the dirty job... */
405	if (clone && ip_divert_ptr != NULL)
406		ip_divert_ptr(clone, incoming);
407
408teeout:
409	/*
410	 * For tee we leave the divert tag attached to original packet.
411	 * It will then continue rule evaluation after the tee rule.
412	 */
413	if (tee)
414		return 0;
415
416	/* Packet diverted and consumed */
417	return 1;
418
419nodivert:
420	m_freem(*m);
421	return 1;
422}
423
424static int
425ipfw_hook(void)
426{
427	struct pfil_head *pfh_inet;
428
429	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
430	if (pfh_inet == NULL)
431		return ENOENT;
432
433	pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
434	pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
435
436	return 0;
437}
438
439static int
440ipfw_unhook(void)
441{
442	struct pfil_head *pfh_inet;
443
444	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
445	if (pfh_inet == NULL)
446		return ENOENT;
447
448	pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
449	pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
450
451	return 0;
452}
453
454#ifdef INET6
455static int
456ipfw6_hook(void)
457{
458	struct pfil_head *pfh_inet6;
459
460	pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
461	if (pfh_inet6 == NULL)
462		return ENOENT;
463
464	pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
465	pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
466
467	return 0;
468}
469
470static int
471ipfw6_unhook(void)
472{
473	struct pfil_head *pfh_inet6;
474
475	pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
476	if (pfh_inet6 == NULL)
477		return ENOENT;
478
479	pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
480	pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
481
482	return 0;
483}
484#endif /* INET6 */
485
486int
487ipfw_chg_hook(SYSCTL_HANDLER_ARGS)
488{
489	int enable = *(int *)arg1;
490	int error;
491
492	error = sysctl_handle_int(oidp, &enable, 0, req);
493	if (error)
494		return (error);
495
496	enable = (enable) ? 1 : 0;
497
498	if (enable == *(int *)arg1)
499		return (0);
500
501	if (arg1 == &fw_enable) {
502		if (enable)
503			error = ipfw_hook();
504		else
505			error = ipfw_unhook();
506	}
507#ifdef INET6
508	if (arg1 == &fw6_enable) {
509		if (enable)
510			error = ipfw6_hook();
511		else
512			error = ipfw6_unhook();
513	}
514#endif
515
516	if (error)
517		return (error);
518
519	*(int *)arg1 = enable;
520
521	return (0);
522}
523
524static int
525ipfw_modevent(module_t mod, int type, void *unused)
526{
527	int err = 0;
528
529	switch (type) {
530	case MOD_LOAD:
531		if ((err = ipfw_init()) != 0) {
532			printf("ipfw_init() error\n");
533			break;
534		}
535		if ((err = ipfw_hook()) != 0) {
536			printf("ipfw_hook() error\n");
537			break;
538		}
539#ifdef INET6
540		if ((err = ipfw6_hook()) != 0) {
541			printf("ipfw_hook() error\n");
542			break;
543		}
544#endif
545		break;
546
547	case MOD_UNLOAD:
548		if ((err = ipfw_unhook()) > 0)
549			break;
550#ifdef INET6
551		if ((err = ipfw6_unhook()) > 0)
552			break;
553#endif
554		ipfw_destroy();
555		break;
556
557	default:
558		return EOPNOTSUPP;
559		break;
560	}
561	return err;
562}
563
564static moduledata_t ipfwmod = {
565	"ipfw",
566	ipfw_modevent,
567	0
568};
569DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
570MODULE_VERSION(ipfw, 2);
571