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