ng_ipfw.c revision 141699
1/*-
2 * Copyright 2005, Gleb Smirnoff <glebius@FreeBSD.org>
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/netgraph/ng_ipfw.c 141699 2005-02-11 20:53:41Z glebius $
27 */
28
29#include <sys/param.h>
30#include <sys/systm.h>
31#include <sys/kernel.h>
32#include <sys/mbuf.h>
33#include <sys/malloc.h>
34#include <sys/ctype.h>
35#include <sys/errno.h>
36#include <sys/socket.h>
37#include <sys/syslog.h>
38
39#include <net/if.h>
40
41#include <netinet/in.h>
42#include <netinet/in_systm.h>
43#include <netinet/in_var.h>
44#include <netinet/ip_fw.h>
45#include <netinet/ip.h>
46#include <netinet/ip_var.h>
47
48#include <netgraph/ng_message.h>
49#include <netgraph/ng_parse.h>
50#include <netgraph/ng_ipfw.h>
51#include <netgraph/netgraph.h>
52
53static int		ng_ipfw_mod_event(module_t mod, int event, void *data);
54static ng_constructor_t	ng_ipfw_constructor;
55static ng_shutdown_t	ng_ipfw_shutdown;
56static ng_newhook_t	ng_ipfw_newhook;
57static ng_connect_t	ng_ipfw_connect;
58static ng_findhook_t	ng_ipfw_findhook;
59static ng_rcvdata_t	ng_ipfw_rcvdata;
60static ng_disconnect_t	ng_ipfw_disconnect;
61
62static hook_p		ng_ipfw_findhook1(node_p, u_int16_t );
63static int		ng_ipfw_input(struct mbuf **, int, struct ip_fw_args *,
64			    int);
65
66/* We have only one node */
67static node_p	fw_node;
68
69/* Netgraph node type descriptor */
70static struct ng_type ng_ipfw_typestruct = {
71	.version =	NG_ABI_VERSION,
72	.name =		NG_IPFW_NODE_TYPE,
73	.mod_event =	ng_ipfw_mod_event,
74	.constructor =	ng_ipfw_constructor,
75	.shutdown =	ng_ipfw_shutdown,
76	.newhook =	ng_ipfw_newhook,
77	.connect =	ng_ipfw_connect,
78	.findhook =	ng_ipfw_findhook,
79	.rcvdata =	ng_ipfw_rcvdata,
80	.disconnect =	ng_ipfw_disconnect,
81};
82NETGRAPH_INIT(ipfw, &ng_ipfw_typestruct);
83MODULE_DEPEND(ng_ipfw, ipfw, 2, 2, 2);
84
85/* Information we store for each hook */
86struct ng_ipfw_hook_priv {
87        hook_p		hook;
88	u_int16_t	rulenum;
89};
90typedef struct ng_ipfw_hook_priv *hpriv_p;
91
92static int
93ng_ipfw_mod_event(module_t mod, int event, void *data)
94{
95	int error = 0;
96
97	switch (event) {
98	case MOD_LOAD:
99
100		if (ng_ipfw_input_p != NULL) {
101			error = EEXIST;
102			break;
103		}
104
105		/* Setup node without any private data */
106		if ((error = ng_make_node_common(&ng_ipfw_typestruct, &fw_node))
107		    != 0) {
108			log(LOG_ERR, "%s: can't create ng_ipfw node", __func__);
109                	break;
110		};
111
112		/* Try to name node */
113		if (ng_name_node(fw_node, "ipfw") != 0)
114			log(LOG_WARNING, "%s: failed to name node \"ipfw\"",
115			    __func__);
116
117		/* Register hook */
118		ng_ipfw_input_p = ng_ipfw_input;
119		break;
120
121	case MOD_UNLOAD:
122		 /*
123		  * This won't happen if a node exists.
124		  * ng_ipfw_input_p is already cleared.
125		  */
126		break;
127
128	default:
129		error = EOPNOTSUPP;
130		break;
131	}
132
133	return (error);
134}
135
136static int
137ng_ipfw_constructor(node_p node)
138{
139	return (EINVAL);	/* Only one node */
140}
141
142static int
143ng_ipfw_newhook(node_p node, hook_p hook, const char *name)
144{
145	hpriv_p	hpriv;
146	u_int16_t rulenum;
147	const char *cp;
148	char *endptr;
149
150	/* Check that name contains only digits */
151	for (cp = name; *cp != '\0'; cp++)
152		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
153			return (EINVAL);
154
155	/* Convert it to integer */
156	rulenum = (u_int16_t)strtol(name, &endptr, 10);
157	if (*endptr != '\0')
158		return (EINVAL);
159
160	/* Allocate memory for this hook's private data */
161	MALLOC(hpriv, hpriv_p, sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO);
162	if (hpriv== NULL)
163		return (ENOMEM);
164
165	hpriv->hook = hook;
166	hpriv->rulenum = rulenum;
167
168	NG_HOOK_SET_PRIVATE(hook, hpriv);
169
170	return(0);
171}
172
173/*
174 * Set hooks into queueing mode, to avoid recursion between
175 * netgraph layer and ip_{input,output}.
176 */
177static int
178ng_ipfw_connect(hook_p hook)
179{
180	NG_HOOK_FORCE_QUEUE(hook);
181	return (0);
182}
183
184/* Look up hook by name */
185hook_p
186ng_ipfw_findhook(node_p node, const char *name)
187{
188	u_int16_t n;	/* numeric representation of hook */
189	char *endptr;
190
191	n = (u_int16_t)strtol(name, &endptr, 10);
192	if (*endptr != '\0')
193		return NULL;
194	return ng_ipfw_findhook1(node, n);
195}
196
197/* Look up hook by rule number */
198static hook_p
199ng_ipfw_findhook1(node_p node, u_int16_t rulenum)
200{
201	hook_p	hook;
202	hpriv_p	hpriv;
203
204	LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
205		hpriv = NG_HOOK_PRIVATE(hook);
206		if (NG_HOOK_IS_VALID(hook) && (hpriv->rulenum == rulenum))
207                        return (hook);
208	}
209
210	return (NULL);
211}
212
213
214static int
215ng_ipfw_rcvdata(hook_p hook, item_p item)
216{
217	struct ng_ipfw_tag	*ngit;
218	struct mbuf *m;
219
220	NGI_GET_M(item, m);
221	NG_FREE_ITEM(item);
222
223	if ((ngit = (struct ng_ipfw_tag *)m_tag_locate(m, NGM_IPFW_COOKIE, 0,
224	    NULL)) == NULL) {
225		NG_FREE_M(m);
226		return (EINVAL);	/* XXX: find smth better */
227	};
228
229	switch (ngit->dir) {
230	case NG_IPFW_OUT:
231	    {
232		struct ip *ip = mtod(m, struct ip *);
233
234		ip->ip_len = ntohs(ip->ip_len);
235		ip->ip_off = ntohs(ip->ip_off);
236
237		return ip_output(m, NULL, NULL, ngit->flags, NULL, NULL);
238	    }
239	case NG_IPFW_IN:
240		ip_input(m);
241		return (0);
242	default:
243		panic("ng_ipfw_rcvdata: bad dir %u", ngit->dir);
244	}
245
246	/* not reached */
247	return (0);
248}
249
250static int
251ng_ipfw_input(struct mbuf **m0, int dir, struct ip_fw_args *fwa, int tee)
252{
253	struct mbuf *m;
254	struct ng_ipfw_tag *ngit;
255	struct ip *ip;
256	hook_p	hook;
257	int error = 0;
258
259	/*
260	 * Node must be loaded and corresponding hook must be present.
261	 */
262	if (fw_node == NULL ||
263	   (hook = ng_ipfw_findhook1(fw_node, fwa->cookie)) == NULL) {
264		if (tee == 0)
265			m_freem(*m0);
266		return (ESRCH);		/* no hook associated with this rule */
267	}
268
269	/*
270	 * We have two modes: in normal mode we add a tag to packet, which is
271	 * important to return packet back to IP stack. In tee mode we make
272	 * a copy of a packet and forward it into netgraph without a tag.
273	 */
274	if (tee == 0) {
275		m = *m0;
276		*m0 = NULL;	/* it belongs now to netgraph */
277
278		if ((ngit = (struct ng_ipfw_tag *)m_tag_alloc(NGM_IPFW_COOKIE,
279		    0, TAGSIZ, M_NOWAIT|M_ZERO)) == NULL) {
280			m_freem(m);
281			return (ENOMEM);
282		}
283		ngit->rule = fwa->rule;
284		ngit->dir = dir;
285		ngit->ifp = fwa->oif;
286		if (dir == NG_IPFW_OUT)
287			ngit->flags = fwa->flags;
288		m_tag_prepend(m, &ngit->mt);
289
290	} else
291		if ((m = m_dup(*m0, M_DONTWAIT)) == NULL)
292			return (ENOMEM);	/* which is ignored */
293
294	ip = mtod(m, struct ip *);
295	ip->ip_len = htons(ip->ip_len);
296	ip->ip_off = htons(ip->ip_off);
297
298	NG_SEND_DATA_ONLY(error, hook, m);
299
300	return (error);
301}
302
303static int
304ng_ipfw_shutdown(node_p node)
305{
306
307	/*
308	 * After our single node has been removed,
309	 * the only thing that can be done is
310	 * 'kldunload ng_ipfw.ko'
311	 */
312	ng_ipfw_input_p = NULL;
313	NG_NODE_UNREF(node);
314	return (0);
315}
316
317static int
318ng_ipfw_disconnect(hook_p hook)
319{
320	const hpriv_p hpriv = NG_HOOK_PRIVATE(hook);
321
322	FREE(hpriv, M_NETGRAPH);
323	NG_HOOK_SET_PRIVATE(hook, NULL);
324
325	return (0);
326}
327