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