ip_fw_pfil.c revision 135167
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 135167 2004-09-13 19:20:14Z andre $
27 */
28
29#if !defined(KLD_MODULE)
30#include "opt_ipfw.h"
31#include "opt_ipdn.h"
32#include "opt_ipdivert.h"
33#include "opt_inet.h"
34#ifndef INET
35#error IPFIREWALL requires INET.
36#endif /* INET */
37#endif /* KLD_MODULE */
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 <machine/in_cksum.h>
64
65static	int ipfw_pfil_hooked = 0;
66
67/* Dummynet hooks. */
68ip_dn_ruledel_t	*ip_dn_ruledel_ptr = NULL;
69
70#define	DIV_DIR_IN	1
71#define	DIV_DIR_OUT	0
72
73static int	ipfw_divert(struct mbuf **, int, int);
74
75int
76ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
77{
78	struct ip_fw_args args;
79	struct m_tag *dn_tag;
80	int ipfw = 0;
81	int divert;
82#ifdef IPFIREWALL_FORWARD
83	struct m_tag *fwd_tag;
84#endif
85
86	KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!"));
87
88	if (!fw_enable)
89		goto pass;
90
91	bzero(&args, sizeof(args));
92
93	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
94	if (dn_tag != NULL){
95		struct dn_pkt_tag *dt;
96
97		dt = (struct dn_pkt_tag *)(dn_tag+1);
98		args.rule = dt->rule;
99
100		m_tag_delete(*m0, dn_tag);
101	}
102
103again:
104	args.m = *m0;
105	ipfw = ipfw_chk(&args);
106	*m0 = args.m;
107
108	if ((ipfw & IP_FW_PORT_DENY_FLAG) || *m0 == NULL)
109		goto drop;
110
111	if (ipfw == 0 && args.next_hop == NULL)
112		goto pass;
113
114	if (DUMMYNET_LOADED && (ipfw & IP_FW_PORT_DYNT_FLAG) != 0) {
115		ip_dn_io_ptr(*m0, ipfw & 0xffff, DN_TO_IP_IN, &args);
116		*m0 = NULL;
117		return 0;		/* packet consumed */
118	}
119
120	if (ipfw != 0 && (ipfw & IP_FW_PORT_DYNT_FLAG) == 0) {
121		if ((ipfw & IP_FW_PORT_TEE_FLAG) != 0)
122			divert = ipfw_divert(m0, DIV_DIR_IN, 1);
123		else
124			divert = ipfw_divert(m0, DIV_DIR_IN, 0);
125
126		/* tee should continue again with the firewall. */
127		if (divert) {
128			*m0 = NULL;
129			return 0;	/* packet consumed */
130		} else
131			goto again;	/* continue with packet */
132	}
133
134#ifdef IPFIREWALL_FORWARD
135	if (ipfw == 0 && args.next_hop != NULL) {
136		fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
137				sizeof(struct sockaddr_in), M_NOWAIT);
138		if (fwd_tag == NULL)
139			goto drop;
140		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
141		m_tag_prepend(*m0, fwd_tag);
142
143		if (in_localip(args.next_hop->sin_addr))
144			(*m0)->m_flags |= M_FASTFWD_OURS;
145		goto pass;
146	}
147#endif
148
149drop:
150	if (*m0)
151		m_freem(*m0);
152	*m0 = NULL;
153	return (EACCES);
154pass:
155	return 0;	/* not filtered */
156}
157
158int
159ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
160{
161	struct ip_fw_args args;
162	struct m_tag *dn_tag;
163	int ipfw = 0;
164	int divert;
165#ifdef IPFIREWALL_FORWARD
166	struct m_tag *fwd_tag;
167#endif
168
169	KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!"));
170
171	if (!fw_enable)
172		goto pass;
173
174	bzero(&args, sizeof(args));
175
176	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
177	if (dn_tag != NULL) {
178		struct dn_pkt_tag *dt;
179
180		dt = (struct dn_pkt_tag *)(dn_tag+1);
181		args.rule = dt->rule;
182
183		m_tag_delete(*m0, dn_tag);
184	}
185
186again:
187	args.m = *m0;
188	args.oif = ifp;
189	ipfw = ipfw_chk(&args);
190	*m0 = args.m;
191
192	if ((ipfw & IP_FW_PORT_DENY_FLAG) || *m0 == NULL)
193		goto drop;
194
195	if (ipfw == 0 && args.next_hop == NULL)
196		goto pass;
197
198	if (DUMMYNET_LOADED && (ipfw & IP_FW_PORT_DYNT_FLAG) != 0) {
199		ip_dn_io_ptr(*m0, ipfw & 0xffff, DN_TO_IP_OUT, &args);
200		*m0 = NULL;
201		return 0;		/* packet consumed */
202	}
203
204	if (ipfw != 0 && (ipfw & IP_FW_PORT_DYNT_FLAG) == 0) {
205		if ((ipfw & IP_FW_PORT_TEE_FLAG) != 0)
206			divert = ipfw_divert(m0, DIV_DIR_OUT, 1);
207		else
208			divert = ipfw_divert(m0, DIV_DIR_OUT, 0);
209
210		if (divert) {
211			*m0 = NULL;
212			return 0;	/* packet consumed */
213		} else
214			goto again;	/* continue with packet */
215        }
216
217#ifdef IPFIREWALL_FORWARD
218	if (ipfw == 0 && args.next_hop != NULL) {
219		/* Overwrite existing tag. */
220		fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
221		if (fwd_tag == NULL) {
222			fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
223				sizeof(struct sockaddr_in), M_NOWAIT);
224			if (fwd_tag == NULL)
225				goto drop;
226		} else
227			m_tag_unlink(*m0, fwd_tag);
228		bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
229		m_tag_prepend(*m0, fwd_tag);
230
231		if (in_localip(args.next_hop->sin_addr))
232			(*m0)->m_flags |= M_FASTFWD_OURS;
233		goto pass;
234	}
235#endif
236
237drop:
238	if (*m0)
239		m_freem(*m0);
240	*m0 = NULL;
241	return (EACCES);
242pass:
243	return 0;	/* not filtered */
244}
245
246static int
247ipfw_divert(struct mbuf **m, int incoming, int tee)
248{
249	/*
250	 * ipfw_chk() has already tagged the packet with the divert tag.
251	 * If tee is set, copy packet and return original.
252	 * If not tee, consume packet and send it to divert socket.
253	 */
254#ifdef IPDIVERT
255	struct mbuf *clone, *reass;
256	struct ip *ip;
257	int hlen;
258
259	reass = NULL;
260
261	/* Cloning needed for tee? */
262	if (tee)
263		clone = m_dup(*m, M_DONTWAIT);
264	else
265		clone = *m;
266
267	/* In case m_dup was unable to allocate mbufs. */
268	if (clone == NULL)
269		goto teeout;
270
271	/*
272	 * Divert listeners can only handle non-fragmented packets.
273	 * However when tee is set we will *not* de-fragment the packets;
274	 * Doing do would put the reassembly into double-jeopardy.  On top
275	 * of that someone doing a tee will probably want to get the packet
276	 * in its original form.
277	 */
278	ip = mtod(clone, struct ip *);
279	if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
280
281		/* Reassemble packet. */
282		reass = ip_reass(clone);
283
284		/*
285		 * IP header checksum fixup after reassembly and leave header
286		 * in network byte order.
287		 */
288		if (reass != NULL) {
289			ip = mtod(reass, struct ip *);
290			hlen = ip->ip_hl << 2;
291			ip->ip_len = htons(ip->ip_len);
292			ip->ip_off = htons(ip->ip_off);
293			ip->ip_sum = 0;
294			if (hlen == sizeof(struct ip))
295				ip->ip_sum = in_cksum_hdr(ip);
296			else
297				ip->ip_sum = in_cksum(reass, hlen);
298			clone = reass;
299		} else
300			clone = NULL;
301	} else {
302		/* Convert header to network byte order. */
303		ip->ip_len = htons(ip->ip_len);
304		ip->ip_off = htons(ip->ip_off);
305	}
306
307	/* Do the dirty job... */
308	if (clone)
309		divert_packet(clone, incoming);
310
311teeout:
312	/*
313	 * For tee we leave the divert tag attached to original packet.
314	 * It will then continue rule evaluation after the tee rule.
315	 */
316	if (tee)
317		return 0;
318
319	/* Packet diverted and consumed */
320	return 1;
321#else
322	m_freem(*m);
323	return 1;
324#endif	/* ipdivert */
325}
326
327static int
328ipfw_hook(void)
329{
330	struct pfil_head *pfh_inet;
331
332	if (ipfw_pfil_hooked)
333		return EEXIST;
334
335	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
336	if (pfh_inet == NULL)
337		return ENOENT;
338
339	pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
340	pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
341
342	return 0;
343}
344
345static int
346ipfw_unhook(void)
347{
348	struct pfil_head *pfh_inet;
349
350	if (!ipfw_pfil_hooked)
351		return ENOENT;
352
353	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
354	if (pfh_inet == NULL)
355		return ENOENT;
356
357	pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
358	pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
359
360	return 0;
361}
362
363static int
364ipfw_modevent(module_t mod, int type, void *unused)
365{
366	int err = 0;
367
368	switch (type) {
369	case MOD_LOAD:
370		if (ipfw_pfil_hooked) {
371			printf("IP firewall already loaded\n");
372			err = EEXIST;
373		} else {
374			if ((err = ipfw_init()) != 0) {
375				printf("ipfw_init() error\n");
376				break;
377			}
378			if ((err = ipfw_hook()) != 0) {
379				printf("ipfw_hook() error\n");
380				break;
381			}
382			ipfw_pfil_hooked = 1;
383		}
384		break;
385
386	case MOD_UNLOAD:
387		if (ipfw_pfil_hooked) {
388			if ((err = ipfw_unhook()) > 0)
389				break;
390			ipfw_destroy();
391			ipfw_pfil_hooked = 0;
392		} else {
393			printf("IP firewall already unloaded\n");
394		}
395		break;
396
397	default:
398		return EOPNOTSUPP;
399		break;
400	}
401	return err;
402}
403
404static moduledata_t ipfwmod = {
405	"ipfw",
406	ipfw_modevent,
407	0
408};
409DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
410MODULE_VERSION(ipfw, 2);
411