1141351Sglebius/*-
2141351Sglebius * Copyright 2005, Gleb Smirnoff <glebius@FreeBSD.org>
3141351Sglebius * All rights reserved.
4141351Sglebius *
5141351Sglebius * Redistribution and use in source and binary forms, with or without
6141351Sglebius * modification, are permitted provided that the following conditions
7141351Sglebius * are met:
8141351Sglebius * 1. Redistributions of source code must retain the above copyright
9141351Sglebius *    notice, this list of conditions and the following disclaimer.
10141351Sglebius * 2. Redistributions in binary form must reproduce the above copyright
11141351Sglebius *    notice, this list of conditions and the following disclaimer in the
12141351Sglebius *    documentation and/or other materials provided with the distribution.
13141351Sglebius *
14141351Sglebius * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15141351Sglebius * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16141351Sglebius * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17141351Sglebius * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18141351Sglebius * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19141351Sglebius * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20141351Sglebius * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21141351Sglebius * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22141351Sglebius * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23141351Sglebius * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24141351Sglebius * SUCH DAMAGE.
25141351Sglebius *
26141351Sglebius * $FreeBSD$
27141351Sglebius */
28141351Sglebius
29225586Sae#include "opt_inet.h"
30225586Sae#include "opt_inet6.h"
31225586Sae
32141351Sglebius#include <sys/param.h>
33141351Sglebius#include <sys/systm.h>
34141351Sglebius#include <sys/kernel.h>
35185895Szec#include <sys/lock.h>
36141351Sglebius#include <sys/mbuf.h>
37141351Sglebius#include <sys/malloc.h>
38141351Sglebius#include <sys/ctype.h>
39141351Sglebius#include <sys/errno.h>
40185895Szec#include <sys/rwlock.h>
41141351Sglebius#include <sys/socket.h>
42141351Sglebius#include <sys/syslog.h>
43141351Sglebius
44141351Sglebius#include <net/if.h>
45257176Sglebius#include <net/if_var.h>
46141351Sglebius
47141351Sglebius#include <netinet/in.h>
48141351Sglebius#include <netinet/in_systm.h>
49141351Sglebius#include <netinet/in_var.h>
50201748Sluigi#include <netinet/ip_var.h>
51141351Sglebius#include <netinet/ip_fw.h>
52141351Sglebius#include <netinet/ip.h>
53225586Sae#include <netinet/ip6.h>
54225586Sae#include <netinet6/ip6_var.h>
55141351Sglebius
56240494Sglebius#include <netpfil/ipfw/ip_fw_private.h>
57240494Sglebius
58141351Sglebius#include <netgraph/ng_message.h>
59141351Sglebius#include <netgraph/ng_parse.h>
60141351Sglebius#include <netgraph/ng_ipfw.h>
61141351Sglebius#include <netgraph/netgraph.h>
62141351Sglebius
63141351Sglebiusstatic int		ng_ipfw_mod_event(module_t mod, int event, void *data);
64141351Sglebiusstatic ng_constructor_t	ng_ipfw_constructor;
65141351Sglebiusstatic ng_shutdown_t	ng_ipfw_shutdown;
66141351Sglebiusstatic ng_newhook_t	ng_ipfw_newhook;
67141351Sglebiusstatic ng_connect_t	ng_ipfw_connect;
68141351Sglebiusstatic ng_findhook_t	ng_ipfw_findhook;
69141351Sglebiusstatic ng_rcvdata_t	ng_ipfw_rcvdata;
70141351Sglebiusstatic ng_disconnect_t	ng_ipfw_disconnect;
71141351Sglebius
72141351Sglebiusstatic hook_p		ng_ipfw_findhook1(node_p, u_int16_t );
73141351Sglebiusstatic int		ng_ipfw_input(struct mbuf **, int, struct ip_fw_args *,
74141351Sglebius			    int);
75141351Sglebius
76141351Sglebius/* We have only one node */
77141351Sglebiusstatic node_p	fw_node;
78141351Sglebius
79141351Sglebius/* Netgraph node type descriptor */
80141351Sglebiusstatic struct ng_type ng_ipfw_typestruct = {
81141351Sglebius	.version =	NG_ABI_VERSION,
82141351Sglebius	.name =		NG_IPFW_NODE_TYPE,
83141351Sglebius	.mod_event =	ng_ipfw_mod_event,
84141351Sglebius	.constructor =	ng_ipfw_constructor,
85141351Sglebius	.shutdown =	ng_ipfw_shutdown,
86141351Sglebius	.newhook =	ng_ipfw_newhook,
87141351Sglebius	.connect =	ng_ipfw_connect,
88141351Sglebius	.findhook =	ng_ipfw_findhook,
89141351Sglebius	.rcvdata =	ng_ipfw_rcvdata,
90141351Sglebius	.disconnect =	ng_ipfw_disconnect,
91141351Sglebius};
92141351SglebiusNETGRAPH_INIT(ipfw, &ng_ipfw_typestruct);
93272840SmelifaroMODULE_DEPEND(ng_ipfw, ipfw, 3, 3, 3);
94141351Sglebius
95141351Sglebius/* Information we store for each hook */
96141351Sglebiusstruct ng_ipfw_hook_priv {
97141351Sglebius        hook_p		hook;
98141351Sglebius	u_int16_t	rulenum;
99141351Sglebius};
100141351Sglebiustypedef struct ng_ipfw_hook_priv *hpriv_p;
101141351Sglebius
102141351Sglebiusstatic int
103141351Sglebiusng_ipfw_mod_event(module_t mod, int event, void *data)
104141351Sglebius{
105141351Sglebius	int error = 0;
106141351Sglebius
107141351Sglebius	switch (event) {
108141351Sglebius	case MOD_LOAD:
109141351Sglebius
110141351Sglebius		if (ng_ipfw_input_p != NULL) {
111141351Sglebius			error = EEXIST;
112141351Sglebius			break;
113141351Sglebius		}
114141351Sglebius
115141351Sglebius		/* Setup node without any private data */
116141351Sglebius		if ((error = ng_make_node_common(&ng_ipfw_typestruct, &fw_node))
117141351Sglebius		    != 0) {
118141351Sglebius			log(LOG_ERR, "%s: can't create ng_ipfw node", __func__);
119141351Sglebius                	break;
120297793Spfg		}
121141351Sglebius
122141351Sglebius		/* Try to name node */
123141351Sglebius		if (ng_name_node(fw_node, "ipfw") != 0)
124141351Sglebius			log(LOG_WARNING, "%s: failed to name node \"ipfw\"",
125141351Sglebius			    __func__);
126141351Sglebius
127141351Sglebius		/* Register hook */
128141351Sglebius		ng_ipfw_input_p = ng_ipfw_input;
129141351Sglebius		break;
130141351Sglebius
131141351Sglebius	case MOD_UNLOAD:
132141351Sglebius		 /*
133141351Sglebius		  * This won't happen if a node exists.
134141351Sglebius		  * ng_ipfw_input_p is already cleared.
135141351Sglebius		  */
136141351Sglebius		break;
137141351Sglebius
138141351Sglebius	default:
139141351Sglebius		error = EOPNOTSUPP;
140141351Sglebius		break;
141141351Sglebius	}
142141351Sglebius
143141351Sglebius	return (error);
144141351Sglebius}
145141351Sglebius
146141351Sglebiusstatic int
147141351Sglebiusng_ipfw_constructor(node_p node)
148141351Sglebius{
149141351Sglebius	return (EINVAL);	/* Only one node */
150141351Sglebius}
151141351Sglebius
152141351Sglebiusstatic int
153141351Sglebiusng_ipfw_newhook(node_p node, hook_p hook, const char *name)
154141351Sglebius{
155141351Sglebius	hpriv_p	hpriv;
156141351Sglebius	u_int16_t rulenum;
157141351Sglebius	const char *cp;
158141451Sglebius	char *endptr;
159141351Sglebius
160146745Sglebius	/* Protect from leading zero */
161146745Sglebius	if (name[0] == '0' && name[1] != '\0')
162146745Sglebius		return (EINVAL);
163146745Sglebius
164141351Sglebius	/* Check that name contains only digits */
165141451Sglebius	for (cp = name; *cp != '\0'; cp++)
166146745Sglebius		if (!isdigit(*cp))
167141351Sglebius			return (EINVAL);
168141351Sglebius
169141351Sglebius	/* Convert it to integer */
170141451Sglebius	rulenum = (u_int16_t)strtol(name, &endptr, 10);
171141451Sglebius	if (*endptr != '\0')
172141351Sglebius		return (EINVAL);
173141351Sglebius
174141351Sglebius	/* Allocate memory for this hook's private data */
175184205Sdes	hpriv = malloc(sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO);
176141351Sglebius	if (hpriv== NULL)
177141351Sglebius		return (ENOMEM);
178141351Sglebius
179141351Sglebius	hpriv->hook = hook;
180141351Sglebius	hpriv->rulenum = rulenum;
181141351Sglebius
182141351Sglebius	NG_HOOK_SET_PRIVATE(hook, hpriv);
183141351Sglebius
184141351Sglebius	return(0);
185141351Sglebius}
186141351Sglebius
187141351Sglebius/*
188141351Sglebius * Set hooks into queueing mode, to avoid recursion between
189141351Sglebius * netgraph layer and ip_{input,output}.
190141351Sglebius */
191141351Sglebiusstatic int
192141351Sglebiusng_ipfw_connect(hook_p hook)
193141351Sglebius{
194141351Sglebius	NG_HOOK_FORCE_QUEUE(hook);
195141351Sglebius	return (0);
196141351Sglebius}
197141351Sglebius
198141351Sglebius/* Look up hook by name */
199230214Sglebiusstatic hook_p
200141351Sglebiusng_ipfw_findhook(node_p node, const char *name)
201141351Sglebius{
202141351Sglebius	u_int16_t n;	/* numeric representation of hook */
203141451Sglebius	char *endptr;
204141351Sglebius
205141451Sglebius	n = (u_int16_t)strtol(name, &endptr, 10);
206141451Sglebius	if (*endptr != '\0')
207141351Sglebius		return NULL;
208141351Sglebius	return ng_ipfw_findhook1(node, n);
209141351Sglebius}
210141351Sglebius
211141351Sglebius/* Look up hook by rule number */
212141351Sglebiusstatic hook_p
213141351Sglebiusng_ipfw_findhook1(node_p node, u_int16_t rulenum)
214141351Sglebius{
215141351Sglebius	hook_p	hook;
216141351Sglebius	hpriv_p	hpriv;
217141351Sglebius
218141351Sglebius	LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
219141351Sglebius		hpriv = NG_HOOK_PRIVATE(hook);
220141351Sglebius		if (NG_HOOK_IS_VALID(hook) && (hpriv->rulenum == rulenum))
221141351Sglebius                        return (hook);
222141351Sglebius	}
223141351Sglebius
224141351Sglebius	return (NULL);
225141351Sglebius}
226141351Sglebius
227141351Sglebius
228141351Sglebiusstatic int
229141351Sglebiusng_ipfw_rcvdata(hook_p hook, item_p item)
230141351Sglebius{
231209633Sglebius	struct m_tag *tag;
232209633Sglebius	struct ipfw_rule_ref *r;
233141351Sglebius	struct mbuf *m;
234225586Sae	struct ip *ip;
235141351Sglebius
236141351Sglebius	NGI_GET_M(item, m);
237141351Sglebius	NG_FREE_ITEM(item);
238141351Sglebius
239209633Sglebius	tag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL);
240201527Sluigi	if (tag == NULL) {
241141351Sglebius		NG_FREE_M(m);
242141351Sglebius		return (EINVAL);	/* XXX: find smth better */
243297793Spfg	}
244141351Sglebius
245225586Sae	if (m->m_len < sizeof(struct ip) &&
246225586Sae	    (m = m_pullup(m, sizeof(struct ip))) == NULL)
247226186Smelifaro		return (ENOBUFS);
248225586Sae
249225586Sae	ip = mtod(m, struct ip *);
250225586Sae
251209633Sglebius	r = (struct ipfw_rule_ref *)(tag + 1);
252209633Sglebius	if (r->info & IPFW_INFO_IN) {
253225586Sae		switch (ip->ip_v) {
254225586Sae#ifdef INET
255225586Sae		case IPVERSION:
256225586Sae			ip_input(m);
257226186Smelifaro			return (0);
258225586Sae#endif
259225586Sae#ifdef INET6
260225586Sae		case IPV6_VERSION >> 4:
261225586Sae			ip6_input(m);
262226186Smelifaro			return (0);
263225586Sae#endif
264225586Sae		}
265201527Sluigi	} else {
266225586Sae		switch (ip->ip_v) {
267225586Sae#ifdef INET
268225586Sae		case IPVERSION:
269225586Sae			return (ip_output(m, NULL, NULL, IP_FORWARDING,
270225586Sae			    NULL, NULL));
271225586Sae#endif
272225586Sae#ifdef INET6
273225586Sae		case IPV6_VERSION >> 4:
274225586Sae			return (ip6_output(m, NULL, NULL, 0, NULL,
275225586Sae			    NULL, NULL));
276225586Sae#endif
277225586Sae		}
278225586Sae	}
279226186Smelifaro
280226186Smelifaro	/* unknown IP protocol version */
281226186Smelifaro	NG_FREE_M(m);
282226186Smelifaro	return (EPROTONOSUPPORT);
283141351Sglebius}
284141351Sglebius
285141351Sglebiusstatic int
286141351Sglebiusng_ipfw_input(struct mbuf **m0, int dir, struct ip_fw_args *fwa, int tee)
287141351Sglebius{
288141351Sglebius	struct mbuf *m;
289141699Sglebius	struct ip *ip;
290141351Sglebius	hook_p	hook;
291141351Sglebius	int error = 0;
292141351Sglebius
293141351Sglebius	/*
294141351Sglebius	 * Node must be loaded and corresponding hook must be present.
295141351Sglebius	 */
296141351Sglebius	if (fw_node == NULL ||
297209722Sglebius	   (hook = ng_ipfw_findhook1(fw_node, fwa->rule.info)) == NULL)
298141351Sglebius		return (ESRCH);		/* no hook associated with this rule */
299141351Sglebius
300141351Sglebius	/*
301141351Sglebius	 * We have two modes: in normal mode we add a tag to packet, which is
302141351Sglebius	 * important to return packet back to IP stack. In tee mode we make
303141351Sglebius	 * a copy of a packet and forward it into netgraph without a tag.
304141351Sglebius	 */
305141351Sglebius	if (tee == 0) {
306201527Sluigi		struct m_tag *tag;
307201527Sluigi		struct ipfw_rule_ref *r;
308141351Sglebius		m = *m0;
309141351Sglebius		*m0 = NULL;	/* it belongs now to netgraph */
310141351Sglebius
311201527Sluigi		tag = m_tag_alloc(MTAG_IPFW_RULE, 0, sizeof(*r),
312201527Sluigi			M_NOWAIT|M_ZERO);
313201527Sluigi		if (tag == NULL) {
314141351Sglebius			m_freem(m);
315141351Sglebius			return (ENOMEM);
316141351Sglebius		}
317201527Sluigi		r = (struct ipfw_rule_ref *)(tag + 1);
318201527Sluigi		*r = fwa->rule;
319210537Sglebius		r->info &= IPFW_ONEPASS;  /* keep this info */
320210537Sglebius		r->info |= dir ? IPFW_INFO_IN : IPFW_INFO_OUT;
321201527Sluigi		m_tag_prepend(m, tag);
322141351Sglebius
323141351Sglebius	} else
324243882Sglebius		if ((m = m_dup(*m0, M_NOWAIT)) == NULL)
325141351Sglebius			return (ENOMEM);	/* which is ignored */
326141351Sglebius
327141705Sglebius	if (m->m_len < sizeof(struct ip) &&
328141706Sglebius	    (m = m_pullup(m, sizeof(struct ip))) == NULL)
329141706Sglebius		return (EINVAL);
330141705Sglebius
331141699Sglebius	ip = mtod(m, struct ip *);
332141699Sglebius
333141351Sglebius	NG_SEND_DATA_ONLY(error, hook, m);
334141351Sglebius
335141351Sglebius	return (error);
336141351Sglebius}
337141351Sglebius
338141351Sglebiusstatic int
339141351Sglebiusng_ipfw_shutdown(node_p node)
340141351Sglebius{
341141351Sglebius
342141351Sglebius	/*
343141351Sglebius	 * After our single node has been removed,
344141351Sglebius	 * the only thing that can be done is
345141351Sglebius	 * 'kldunload ng_ipfw.ko'
346141351Sglebius	 */
347141351Sglebius	ng_ipfw_input_p = NULL;
348141351Sglebius	NG_NODE_UNREF(node);
349141351Sglebius	return (0);
350141351Sglebius}
351141351Sglebius
352141351Sglebiusstatic int
353141351Sglebiusng_ipfw_disconnect(hook_p hook)
354141351Sglebius{
355141351Sglebius	const hpriv_p hpriv = NG_HOOK_PRIVATE(hook);
356141351Sglebius
357184205Sdes	free(hpriv, M_NETGRAPH);
358141351Sglebius	NG_HOOK_SET_PRIVATE(hook, NULL);
359141351Sglebius
360141351Sglebius	return (0);
361141351Sglebius}
362