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