ip_fw_pfil.c revision 185895
1164190Sjkoshy/*-
2164190Sjkoshy * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG
3164190Sjkoshy * All rights reserved.
4164190Sjkoshy *
5164190Sjkoshy * Redistribution and use in source and binary forms, with or without
6164190Sjkoshy * modification, are permitted provided that the following conditions
7164190Sjkoshy * are met:
8164190Sjkoshy * 1. Redistributions of source code must retain the above copyright
9164190Sjkoshy *    notice, this list of conditions and the following disclaimer.
10164190Sjkoshy * 2. Redistributions in binary form must reproduce the above copyright
11164190Sjkoshy *    notice, this list of conditions and the following disclaimer in the
12164190Sjkoshy *    documentation and/or other materials provided with the distribution.
13164190Sjkoshy *
14164190Sjkoshy * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15164190Sjkoshy * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16164190Sjkoshy * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17164190Sjkoshy * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18164190Sjkoshy * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19164190Sjkoshy * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20164190Sjkoshy * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21164190Sjkoshy * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22164190Sjkoshy * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23164190Sjkoshy * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24164190Sjkoshy * SUCH DAMAGE.
25164190Sjkoshy */
26164190Sjkoshy
27164190Sjkoshy#include <sys/cdefs.h>
28164190Sjkoshy__FBSDID("$FreeBSD: head/sys/netinet/ip_fw_pfil.c 185895 2008-12-10 23:12:39Z zec $");
29164190Sjkoshy
30164190Sjkoshy#if !defined(KLD_MODULE)
31164190Sjkoshy#include "opt_ipfw.h"
32164190Sjkoshy#include "opt_ipdn.h"
33164190Sjkoshy#include "opt_inet.h"
34164190Sjkoshy#ifndef INET
35164190Sjkoshy#error IPFIREWALL requires INET.
36164190Sjkoshy#endif /* INET */
37164190Sjkoshy#endif /* KLD_MODULE */
38164190Sjkoshy#include "opt_inet6.h"
39164190Sjkoshy
40164190Sjkoshy#include <sys/param.h>
41164190Sjkoshy#include <sys/systm.h>
42164190Sjkoshy#include <sys/malloc.h>
43164190Sjkoshy#include <sys/mbuf.h>
44164190Sjkoshy#include <sys/module.h>
45164190Sjkoshy#include <sys/kernel.h>
46164190Sjkoshy#include <sys/lock.h>
47164190Sjkoshy#include <sys/rwlock.h>
48164190Sjkoshy#include <sys/socket.h>
49164190Sjkoshy#include <sys/socketvar.h>
50164190Sjkoshy#include <sys/sysctl.h>
51164190Sjkoshy#include <sys/ucred.h>
52164190Sjkoshy#include <sys/vimage.h>
53164190Sjkoshy
54164190Sjkoshy#include <net/if.h>
55164190Sjkoshy#include <net/route.h>
56164190Sjkoshy#include <net/pfil.h>
57164190Sjkoshy
58164190Sjkoshy#include <netinet/in.h>
59164190Sjkoshy#include <netinet/in_systm.h>
60164190Sjkoshy#include <netinet/in_var.h>
61164190Sjkoshy#include <netinet/ip.h>
62164190Sjkoshy#include <netinet/ip_var.h>
63164190Sjkoshy#include <netinet/ip_fw.h>
64164190Sjkoshy#include <netinet/ip_divert.h>
65164190Sjkoshy#include <netinet/ip_dummynet.h>
66164190Sjkoshy
67164190Sjkoshy#include <netgraph/ng_ipfw.h>
68164190Sjkoshy
69164190Sjkoshy#include <machine/in_cksum.h>
70164190Sjkoshy
71164190Sjkoshyint fw_enable = 1;
72164190Sjkoshy#ifdef INET6
73164190Sjkoshyint fw6_enable = 1;
74164190Sjkoshy#endif
75164190Sjkoshy
76164190Sjkoshyint ipfw_chg_hook(SYSCTL_HANDLER_ARGS);
77164190Sjkoshy
78165032Sjkoshy/* Dummynet hooks. */
79164190Sjkoshyip_dn_ruledel_t	*ip_dn_ruledel_ptr = NULL;
80164190Sjkoshy
81164190Sjkoshy/* Divert hooks. */
82164190Sjkoshyip_divert_packet_t *ip_divert_ptr = NULL;
83164190Sjkoshy
84164190Sjkoshy/* ng_ipfw hooks. */
85164190Sjkoshyng_ipfw_input_t *ng_ipfw_input_p = NULL;
86164190Sjkoshy
87164190Sjkoshy/* Forward declarations. */
88164190Sjkoshystatic int	ipfw_divert(struct mbuf **, int, int);
89164190Sjkoshy#define	DIV_DIR_IN	1
90164190Sjkoshy#define	DIV_DIR_OUT	0
91164190Sjkoshy
92164190Sjkoshyint
93164190Sjkoshyipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
94164190Sjkoshy    struct inpcb *inp)
95164190Sjkoshy{
96164190Sjkoshy	struct ip_fw_args args;
97164190Sjkoshy	struct ng_ipfw_tag *ng_tag;
98164190Sjkoshy	struct m_tag *dn_tag;
99164190Sjkoshy	int ipfw = 0;
100164190Sjkoshy	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	ipfw = ipfw_chk(&args);
133	*m0 = args.m;
134	tee = 0;
135
136	KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
137	    __func__));
138
139	switch (ipfw) {
140	case IP_FW_PASS:
141		if (args.next_hop == NULL)
142			goto pass;
143
144#ifdef IPFIREWALL_FORWARD
145		fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
146				sizeof(struct sockaddr_in), M_NOWAIT);
147		if (fwd_tag == NULL)
148			goto drop;
149		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
150		m_tag_prepend(*m0, fwd_tag);
151
152		if (in_localip(args.next_hop->sin_addr))
153			(*m0)->m_flags |= M_FASTFWD_OURS;
154		goto pass;
155#endif
156		break;			/* not reached */
157
158	case IP_FW_DENY:
159		goto drop;
160		break;			/* not reached */
161
162	case IP_FW_DUMMYNET:
163		if (!DUMMYNET_LOADED)
164			goto drop;
165		if (mtod(*m0, struct ip *)->ip_v == 4)
166			ip_dn_io_ptr(m0, DN_TO_IP_IN, &args);
167		else if (mtod(*m0, struct ip *)->ip_v == 6)
168			ip_dn_io_ptr(m0, DN_TO_IP6_IN, &args);
169		if (*m0 != NULL)
170			goto again;
171		return 0;		/* packet consumed */
172
173	case IP_FW_TEE:
174		tee = 1;
175		/* fall through */
176
177	case IP_FW_DIVERT:
178		divert = ipfw_divert(m0, DIV_DIR_IN, tee);
179		if (divert) {
180			*m0 = NULL;
181			return 0;	/* packet consumed */
182		} else {
183			args.rule = NULL;
184			goto again;	/* continue with packet */
185		}
186
187	case IP_FW_NGTEE:
188		if (!NG_IPFW_LOADED)
189			goto drop;
190		(void)ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 1);
191		goto again;		/* continue with packet */
192
193	case IP_FW_NETGRAPH:
194		if (!NG_IPFW_LOADED)
195			goto drop;
196		return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0);
197
198	case IP_FW_NAT:
199		goto again;		/* continue with packet */
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	ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
233	    NULL);
234	if (ng_tag != NULL) {
235		KASSERT(ng_tag->dir == NG_IPFW_OUT,
236		    ("ng_ipfw tag with wrong direction"));
237		args.rule = ng_tag->rule;
238		m_tag_delete(*m0, (struct m_tag *)ng_tag);
239	}
240
241again:
242	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
243	if (dn_tag != NULL) {
244		struct dn_pkt_tag *dt;
245
246		dt = (struct dn_pkt_tag *)(dn_tag+1);
247		args.rule = dt->rule;
248
249		m_tag_delete(*m0, dn_tag);
250	}
251
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		if (*m0 != NULL)
297			goto again;
298		return 0;		/* packet consumed */
299
300		break;
301
302	case IP_FW_TEE:
303		tee = 1;
304		/* fall through */
305
306	case IP_FW_DIVERT:
307		divert = ipfw_divert(m0, DIV_DIR_OUT, tee);
308		if (divert) {
309			*m0 = NULL;
310			return 0;	/* packet consumed */
311		} else {
312			args.rule = NULL;
313			goto again;	/* continue with packet */
314		}
315
316	case IP_FW_NGTEE:
317		if (!NG_IPFW_LOADED)
318			goto drop;
319		(void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1);
320		goto again;		/* continue with packet */
321
322	case IP_FW_NETGRAPH:
323		if (!NG_IPFW_LOADED)
324			goto drop;
325		return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0);
326
327	case IP_FW_NAT:
328		goto again;		/* continue with packet */
329
330	default:
331		KASSERT(0, ("%s: unknown retval", __func__));
332	}
333
334drop:
335	if (*m0)
336		m_freem(*m0);
337	*m0 = NULL;
338	return (EACCES);
339pass:
340	return 0;	/* not filtered */
341}
342
343static int
344ipfw_divert(struct mbuf **m, int incoming, int tee)
345{
346	/*
347	 * ipfw_chk() has already tagged the packet with the divert tag.
348	 * If tee is set, copy packet and return original.
349	 * If not tee, consume packet and send it to divert socket.
350	 */
351	struct mbuf *clone, *reass;
352	struct ip *ip;
353	int hlen;
354
355	reass = NULL;
356
357	/* Is divert module loaded? */
358	if (ip_divert_ptr == NULL)
359		goto nodivert;
360
361	/* Cloning needed for tee? */
362	if (tee)
363		clone = m_dup(*m, M_DONTWAIT);
364	else
365		clone = *m;
366
367	/* In case m_dup was unable to allocate mbufs. */
368	if (clone == NULL)
369		goto teeout;
370
371	/*
372	 * Divert listeners can only handle non-fragmented packets.
373	 * However when tee is set we will *not* de-fragment the packets;
374	 * Doing do would put the reassembly into double-jeopardy.  On top
375	 * of that someone doing a tee will probably want to get the packet
376	 * in its original form.
377	 */
378	ip = mtod(clone, struct ip *);
379	if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
380
381		/* Reassemble packet. */
382		reass = ip_reass(clone);
383
384		/*
385		 * IP header checksum fixup after reassembly and leave header
386		 * in network byte order.
387		 */
388		if (reass != NULL) {
389			ip = mtod(reass, struct ip *);
390			hlen = ip->ip_hl << 2;
391			ip->ip_len = htons(ip->ip_len);
392			ip->ip_off = htons(ip->ip_off);
393			ip->ip_sum = 0;
394			if (hlen == sizeof(struct ip))
395				ip->ip_sum = in_cksum_hdr(ip);
396			else
397				ip->ip_sum = in_cksum(reass, hlen);
398			clone = reass;
399		} else
400			clone = NULL;
401	} else {
402		/* Convert header to network byte order. */
403		ip->ip_len = htons(ip->ip_len);
404		ip->ip_off = htons(ip->ip_off);
405	}
406
407	/* Do the dirty job... */
408	if (clone && ip_divert_ptr != NULL)
409		ip_divert_ptr(clone, incoming);
410
411teeout:
412	/*
413	 * For tee we leave the divert tag attached to original packet.
414	 * It will then continue rule evaluation after the tee rule.
415	 */
416	if (tee)
417		return 0;
418
419	/* Packet diverted and consumed */
420	return 1;
421
422nodivert:
423	m_freem(*m);
424	return 1;
425}
426
427static int
428ipfw_hook(void)
429{
430	struct pfil_head *pfh_inet;
431
432	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
433	if (pfh_inet == NULL)
434		return ENOENT;
435
436	pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
437	pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
438
439	return 0;
440}
441
442static int
443ipfw_unhook(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	pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
452	pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
453
454	return 0;
455}
456
457#ifdef INET6
458static int
459ipfw6_hook(void)
460{
461	struct pfil_head *pfh_inet6;
462
463	pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
464	if (pfh_inet6 == NULL)
465		return ENOENT;
466
467	pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
468	pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
469
470	return 0;
471}
472
473static int
474ipfw6_unhook(void)
475{
476	struct pfil_head *pfh_inet6;
477
478	pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
479	if (pfh_inet6 == NULL)
480		return ENOENT;
481
482	pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
483	pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
484
485	return 0;
486}
487#endif /* INET6 */
488
489int
490ipfw_chg_hook(SYSCTL_HANDLER_ARGS)
491{
492	INIT_VNET_IPFW(curvnet);
493	int enable = *(int *)arg1;
494	int error;
495
496	error = sysctl_handle_int(oidp, &enable, 0, req);
497	if (error)
498		return (error);
499
500	enable = (enable) ? 1 : 0;
501
502	if (enable == *(int *)arg1)
503		return (0);
504
505	if (arg1 == &V_fw_enable) {
506		if (enable)
507			error = ipfw_hook();
508		else
509			error = ipfw_unhook();
510	}
511#ifdef INET6
512	if (arg1 == &V_fw6_enable) {
513		if (enable)
514			error = ipfw6_hook();
515		else
516			error = ipfw6_unhook();
517	}
518#endif
519
520	if (error)
521		return (error);
522
523	*(int *)arg1 = enable;
524
525	return (0);
526}
527
528static int
529ipfw_modevent(module_t mod, int type, void *unused)
530{
531	int err = 0;
532
533	switch (type) {
534	case MOD_LOAD:
535		if ((err = ipfw_init()) != 0) {
536			printf("ipfw_init() error\n");
537			break;
538		}
539		if ((err = ipfw_hook()) != 0) {
540			printf("ipfw_hook() error\n");
541			break;
542		}
543#ifdef INET6
544		if ((err = ipfw6_hook()) != 0) {
545			printf("ipfw_hook() error\n");
546			break;
547		}
548#endif
549		break;
550
551	case MOD_UNLOAD:
552		if ((err = ipfw_unhook()) > 0)
553			break;
554#ifdef INET6
555		if ((err = ipfw6_unhook()) > 0)
556			break;
557#endif
558		ipfw_destroy();
559		break;
560
561	default:
562		return EOPNOTSUPP;
563		break;
564	}
565	return err;
566}
567
568static moduledata_t ipfwmod = {
569	"ipfw",
570	ipfw_modevent,
571	0
572};
573DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY - 256);
574MODULE_VERSION(ipfw, 2);
575