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