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