ip_fw_pfil.c revision 201122
1255219Spjd/*-
2255219Spjd * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG
3255219Spjd * All rights reserved.
4255219Spjd *
5255219Spjd * Redistribution and use in source and binary forms, with or without
6255219Spjd * modification, are permitted provided that the following conditions
7255219Spjd * are met:
8255219Spjd * 1. Redistributions of source code must retain the above copyright
9255219Spjd *    notice, this list of conditions and the following disclaimer.
10255219Spjd * 2. Redistributions in binary form must reproduce the above copyright
11255219Spjd *    notice, this list of conditions and the following disclaimer in the
12255219Spjd *    documentation and/or other materials provided with the distribution.
13255219Spjd *
14255219Spjd * 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 201122 2009-12-28 10:47:04Z 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/sysctl.h>
50
51#include <net/if.h>
52#include <net/route.h>
53#include <net/pfil.h>
54#include <net/vnet.h>
55
56#include <netinet/in.h>
57#include <netinet/in_systm.h>
58#include <netinet/ip.h>
59#include <netinet/ip_var.h>
60#include <netinet/ip_fw.h>
61#include <netinet/ipfw/ip_fw_private.h>
62#include <netinet/ip_divert.h>
63#include <netinet/ip_dummynet.h>
64
65#include <machine/in_cksum.h>
66
67static VNET_DEFINE(int, fw_enable) = 1;
68#define V_fw_enable	VNET(fw_enable)
69
70#ifdef INET6
71static VNET_DEFINE(int, fw6_enable) = 1;
72#define V_fw6_enable	VNET(fw6_enable)
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 void	ipfw_divert(struct mbuf **, int, int);
85
86#ifdef SYSCTL_NODE
87SYSCTL_DECL(_net_inet_ip_fw);
88SYSCTL_VNET_PROC(_net_inet_ip_fw, OID_AUTO, enable,
89    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw_enable), 0,
90    ipfw_chg_hook, "I", "Enable ipfw");
91#ifdef INET6
92SYSCTL_DECL(_net_inet6_ip6_fw);
93SYSCTL_VNET_PROC(_net_inet6_ip6_fw, OID_AUTO, enable,
94    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, &VNET_NAME(fw6_enable), 0,
95    ipfw_chg_hook, "I", "Enable ipfw+6");
96#endif /* INET6 */
97#endif /* SYSCTL_NODE */
98
99/*
100 * The pfilter hook to pass packets to ipfw_chk and then to
101 * dummynet, divert, netgraph or other modules.
102 * The packet may be consumed.
103 */
104static int
105ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
106    struct inpcb *inp)
107{
108	struct ip_fw_args args;
109	struct ng_ipfw_tag *ng_tag;
110	struct m_tag *dn_tag;
111	int ipfw;
112	int ret;
113#ifdef IPFIREWALL_FORWARD
114	struct m_tag *fwd_tag;
115#endif
116
117	/* convert dir to IPFW values */
118	dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT;
119	bzero(&args, sizeof(args));
120
121	ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
122	    NULL);
123	if (ng_tag != NULL) {
124		KASSERT(ng_tag->dir == dir,
125		    ("ng_ipfw tag with wrong direction"));
126		args.slot = ng_tag->slot;
127		args.rulenum = ng_tag->rulenum;
128		args.rule_id = ng_tag->rule_id;
129		args.chain_id = ng_tag->chain_id;
130		m_tag_delete(*m0, (struct m_tag *)ng_tag);
131	}
132
133again:
134	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
135	if (dn_tag != NULL) {
136		struct dn_pkt_tag *dt;
137
138		dt = (struct dn_pkt_tag *)(dn_tag+1);
139		args.slot = dt->slot;
140		args.rulenum = dt->rulenum;
141		args.rule_id = dt->rule_id;
142		args.chain_id = dt->chain_id;
143		m_tag_delete(*m0, dn_tag);
144	}
145
146	args.m = *m0;
147	args.oif = dir == DIR_OUT ? ifp : NULL;
148	args.inp = inp;
149
150	if (V_fw_one_pass == 0 || args.slot == 0) {
151		ipfw = ipfw_chk(&args);
152		*m0 = args.m;
153	} else
154		ipfw = IP_FW_PASS;
155
156	KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
157	    __func__));
158
159	/* breaking out of the switch means drop */
160	ret = 0;	/* default return value for pass */
161	switch (ipfw) {
162	case IP_FW_PASS:
163		/* next_hop may be set by ipfw_chk */
164                if (args.next_hop == NULL)
165                        break; /* pass */
166#ifndef IPFIREWALL_FORWARD
167		ret = EACCES;
168#else
169		/* Incoming packets should not be tagged so we do not
170		 * m_tag_find. Outgoing packets may be tagged, so we
171		 * reuse the tag if present.
172		 */
173		fwd_tag = (dir == DIR_IN) ? NULL :
174			m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
175		if (fwd_tag != NULL) {
176			m_tag_unlink(*m0, fwd_tag);
177		} else {
178			fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
179				sizeof(struct sockaddr_in), M_NOWAIT);
180			if (fwd_tag == NULL) {
181				ret = EACCES;
182				break; /* i.e. drop */
183			}
184		}
185		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
186		m_tag_prepend(*m0, fwd_tag);
187
188		if (in_localip(args.next_hop->sin_addr))
189			(*m0)->m_flags |= M_FASTFWD_OURS;
190#endif
191		break;
192
193	case IP_FW_DENY:
194		ret = EACCES;
195		break; /* i.e. drop */
196
197	case IP_FW_DUMMYNET:
198		ret = EACCES;
199		if (ip_dn_io_ptr == NULL)
200			break; /* i.e. drop */
201		if (mtod(*m0, struct ip *)->ip_v == 4)
202			ret = ip_dn_io_ptr(m0, dir, &args);
203		else if (mtod(*m0, struct ip *)->ip_v == 6)
204			ret = ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args);
205		else
206			break; /* drop it */
207		/*
208		 * XXX should read the return value.
209		 * dummynet normally eats the packet and sets *m0=NULL
210		 * unless the packet can be sent immediately. In this
211		 * case args is updated and we should re-run the
212		 * check without clearing args.
213		 */
214		if (*m0 != NULL)
215			goto again;
216		break;
217
218	case IP_FW_TEE:
219	case IP_FW_DIVERT:
220		if (ip_divert_ptr == NULL) {
221			ret = EACCES;
222			break; /* i.e. drop */
223		}
224		ipfw_divert(m0, dir, (ipfw == IP_FW_TEE) ? 1 : 0);
225		if (*m0) {
226			/* continue processing for this one. We set
227			 * args.slot=0, but the divert tag is processed
228			 * in ipfw_chk to jump to the right place.
229			 */
230			args.slot = 0;
231			goto again;	/* continue with packet */
232		}
233		break;
234
235	case IP_FW_NGTEE:
236	case IP_FW_NETGRAPH:
237		if (!NG_IPFW_LOADED) {
238			ret = EACCES;
239			break; /* i.e. drop */
240		}
241		ret = ng_ipfw_input_p(m0, dir, &args,
242			(ipfw == IP_FW_NGTEE) ? 1 : 0);
243		if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */
244			goto again;	/* continue with packet */
245		break;
246
247	case IP_FW_NAT:
248	case IP_FW_REASS:
249		goto again;		/* continue with packet */
250
251	default:
252		KASSERT(0, ("%s: unknown retval", __func__));
253	}
254
255	if (ret != 0) {
256		if (*m0)
257			m_freem(*m0);
258		*m0 = NULL;
259	}
260	return ret;
261}
262
263static void
264ipfw_divert(struct mbuf **m0, int incoming, int tee)
265{
266	/*
267	 * ipfw_chk() has already tagged the packet with the divert tag.
268	 * If tee is set, copy packet and return original.
269	 * If not tee, consume packet and send it to divert socket.
270	 */
271	struct mbuf *clone;
272	struct ip *ip;
273
274	/* Cloning needed for tee? */
275	if (tee == 0) {
276		clone = *m0;	/* use the original mbuf */
277		*m0 = NULL;
278	} else {
279		clone = m_dup(*m0, M_DONTWAIT);
280		/* If we cannot duplicate the mbuf, we sacrifice the divert
281		 * chain and continue with the tee-ed packet.
282		 */
283		if (clone == NULL)
284			return;
285	}
286
287	/*
288	 * Divert listeners can normally handle non-fragmented packets,
289	 * but we can only reass in the non-tee case.
290	 * This means that listeners on a tee rule may get fragments,
291	 * and have to live with that.
292	 * Note that we now have the 'reass' ipfw option so if we care
293	 * we can do it before a 'tee'.
294	 */
295	ip = mtod(clone, struct ip *);
296	if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
297		int hlen;
298		struct mbuf *reass;
299
300		reass = ip_reass(clone); /* Reassemble packet. */
301		if (reass == NULL)
302			return;
303		/* if reass = NULL then it was consumed by ip_reass */
304		/*
305		 * IP header checksum fixup after reassembly and leave header
306		 * in network byte order.
307		 */
308		ip = mtod(reass, struct ip *);
309		hlen = ip->ip_hl << 2;
310		SET_NET_IPLEN(ip);
311		ip->ip_sum = 0;
312		if (hlen == sizeof(struct ip))
313			ip->ip_sum = in_cksum_hdr(ip);
314		else
315			ip->ip_sum = in_cksum(reass, hlen);
316		clone = reass;
317	} else {
318		/* Convert header to network byte order. */
319		SET_NET_IPLEN(ip);
320	}
321
322	/* Do the dirty job... */
323	ip_divert_ptr(clone, incoming);
324}
325
326/*
327 * attach or detach hooks for a given protocol family
328 */
329static int
330ipfw_hook(int onoff, int pf)
331{
332	const int arg = PFIL_IN | PFIL_OUT | PFIL_WAITOK;
333	struct pfil_head *pfh;
334
335	pfh = pfil_head_get(PFIL_TYPE_AF, pf);
336	if (pfh == NULL)
337		return ENOENT;
338
339	if (onoff)
340		(void)pfil_add_hook(ipfw_check_hook, NULL, arg, pfh);
341	else
342		(void)pfil_remove_hook(ipfw_check_hook, NULL, arg, pfh);
343
344	return 0;
345}
346
347int
348ipfw_attach_hooks(int arg)
349{
350	int error = 0;
351
352	if (arg == 0) /* detach */
353		ipfw_hook(0, AF_INET);
354        else if (V_fw_enable && ipfw_hook(1, AF_INET) != 0) {
355                error = ENOENT; /* see ip_fw_pfil.c::ipfw_hook() */
356                printf("ipfw_hook() error\n");
357        }
358#ifdef INET6
359	if (arg == 0) /* detach */
360		ipfw_hook(0, AF_INET6);
361        else if (V_fw6_enable && ipfw_hook(1, AF_INET6) != 0) {
362                error = ENOENT;
363                printf("ipfw6_hook() error\n");
364        }
365#endif
366	return error;
367}
368
369int
370ipfw_chg_hook(SYSCTL_HANDLER_ARGS)
371{
372	int enable;
373	int oldenable;
374	int error;
375	int af;
376
377	if (arg1 == &VNET_NAME(fw_enable)) {
378		enable = V_fw_enable;
379		af = AF_INET;
380	}
381#ifdef INET6
382	else if (arg1 == &VNET_NAME(fw6_enable)) {
383		enable = V_fw6_enable;
384		af = AF_INET6;
385	}
386#endif
387	else
388		return (EINVAL);
389
390	oldenable = enable;
391
392	error = sysctl_handle_int(oidp, &enable, 0, req);
393
394	if (error)
395		return (error);
396
397	enable = (enable) ? 1 : 0;
398
399	if (enable == oldenable)
400		return (0);
401
402	error = ipfw_hook(enable, af);
403	if (error)
404		return (error);
405	if (af == AF_INET)
406		V_fw_enable = enable;
407#ifdef INET6
408	else if (af == AF_INET6)
409		V_fw6_enable = enable;
410#endif
411
412	return (0);
413}
414/* end of file */
415