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