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