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