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