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