ng_nat.c revision 169867
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_nat.c 169867 2007-05-22 12:23:39Z mav $
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/syslog.h>
37
38#include <netinet/in_systm.h>
39#include <netinet/in.h>
40#include <netinet/ip.h>
41#include <netinet/ip_var.h>
42#include <netinet/tcp.h>
43#include <machine/in_cksum.h>
44
45#include <netinet/libalias/alias.h>
46
47#include <netgraph/ng_message.h>
48#include <netgraph/ng_parse.h>
49#include <netgraph/ng_nat.h>
50#include <netgraph/netgraph.h>
51
52static ng_constructor_t	ng_nat_constructor;
53static ng_rcvmsg_t	ng_nat_rcvmsg;
54static ng_shutdown_t	ng_nat_shutdown;
55static ng_newhook_t	ng_nat_newhook;
56static ng_rcvdata_t	ng_nat_rcvdata;
57static ng_disconnect_t	ng_nat_disconnect;
58
59static unsigned int	ng_nat_translate_flags(unsigned int x);
60
61/* Parse type for struct ng_nat_mode. */
62static const struct ng_parse_struct_field ng_nat_mode_fields[]
63	= NG_NAT_MODE_INFO;
64static const struct ng_parse_type ng_nat_mode_type = {
65	&ng_parse_struct_type,
66	ng_nat_mode_fields
67};
68
69/* List of commands and how to convert arguments to/from ASCII. */
70static const struct ng_cmdlist ng_nat_cmdlist[] = {
71	{
72	  NGM_NAT_COOKIE,
73	  NGM_NAT_SET_IPADDR,
74	  "setaliasaddr",
75	  &ng_parse_ipaddr_type,
76	  NULL
77	},
78	{
79	  NGM_NAT_COOKIE,
80	  NGM_NAT_SET_MODE,
81	  "setmode",
82	  &ng_nat_mode_type,
83	  NULL
84	},
85	{
86	  NGM_NAT_COOKIE,
87	  NGM_NAT_SET_TARGET,
88	  "settarget",
89	  &ng_parse_ipaddr_type,
90	  NULL
91	},
92	{ 0 }
93};
94
95/* Netgraph node type descriptor. */
96static struct ng_type typestruct = {
97	.version =	NG_ABI_VERSION,
98	.name =		NG_NAT_NODE_TYPE,
99	.constructor =	ng_nat_constructor,
100	.rcvmsg =	ng_nat_rcvmsg,
101	.shutdown =	ng_nat_shutdown,
102	.newhook =	ng_nat_newhook,
103	.rcvdata =	ng_nat_rcvdata,
104	.disconnect =	ng_nat_disconnect,
105	.cmdlist =	ng_nat_cmdlist,
106};
107NETGRAPH_INIT(nat, &typestruct);
108MODULE_DEPEND(ng_nat, libalias, 1, 1, 1);
109
110/* Information we store for each node. */
111struct ng_nat_priv {
112	node_p		node;		/* back pointer to node */
113	hook_p		in;		/* hook for demasquerading */
114	hook_p		out;		/* hook for masquerading */
115	struct libalias	*lib;		/* libalias handler */
116	uint32_t	flags;		/* status flags */
117};
118typedef struct ng_nat_priv *priv_p;
119
120/* Values of flags */
121#define	NGNAT_CONNECTED		0x1	/* We have both hooks connected */
122#define	NGNAT_ADDR_DEFINED	0x2	/* NGM_NAT_SET_IPADDR happened */
123
124static int
125ng_nat_constructor(node_p node)
126{
127	priv_p priv;
128
129	/* Initialize private descriptor. */
130	MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH,
131		M_NOWAIT | M_ZERO);
132	if (priv == NULL)
133		return (ENOMEM);
134
135	/* Init aliasing engine. */
136	priv->lib = LibAliasInit(NULL);
137	if (priv->lib == NULL) {
138		FREE(priv, M_NETGRAPH);
139		return (ENOMEM);
140	}
141
142	/* Set same ports on. */
143	(void )LibAliasSetMode(priv->lib, PKT_ALIAS_SAME_PORTS,
144	    PKT_ALIAS_SAME_PORTS);
145
146	/* Link structs together. */
147	NG_NODE_SET_PRIVATE(node, priv);
148	priv->node = node;
149
150	/*
151	 * libalias is not thread safe, so our node
152	 * must be single threaded.
153	 */
154	NG_NODE_FORCE_WRITER(node);
155
156	return (0);
157}
158
159static int
160ng_nat_newhook(node_p node, hook_p hook, const char *name)
161{
162	const priv_p priv = NG_NODE_PRIVATE(node);
163
164	if (strcmp(name, NG_NAT_HOOK_IN) == 0) {
165		priv->in = hook;
166	} else if (strcmp(name, NG_NAT_HOOK_OUT) == 0) {
167		priv->out = hook;
168	} else
169		return (EINVAL);
170
171	if (priv->out != NULL &&
172	    priv->in != NULL)
173		priv->flags |= NGNAT_CONNECTED;
174
175	return(0);
176}
177
178static int
179ng_nat_rcvmsg(node_p node, item_p item, hook_p lasthook)
180{
181	const priv_p priv = NG_NODE_PRIVATE(node);
182	struct ng_mesg *resp = NULL;
183	struct ng_mesg *msg;
184	int error = 0;
185
186	NGI_GET_MSG(item, msg);
187
188	switch (msg->header.typecookie) {
189	case NGM_NAT_COOKIE:
190		switch (msg->header.cmd) {
191		case NGM_NAT_SET_IPADDR:
192		    {
193			struct in_addr *const ia = (struct in_addr *)msg->data;
194
195			if (msg->header.arglen < sizeof(*ia)) {
196				error = EINVAL;
197				break;
198			}
199
200			LibAliasSetAddress(priv->lib, *ia);
201
202			priv->flags |= NGNAT_ADDR_DEFINED;
203		    }
204			break;
205		case NGM_NAT_SET_MODE:
206		    {
207			struct ng_nat_mode *const mode =
208			    (struct ng_nat_mode *)msg->data;
209
210			if (msg->header.arglen < sizeof(*mode)) {
211				error = EINVAL;
212				break;
213			}
214
215			if (LibAliasSetMode(priv->lib,
216			    ng_nat_translate_flags(mode->flags),
217			    ng_nat_translate_flags(mode->mask)) < 0) {
218				error = ENOMEM;
219				break;
220			}
221		    }
222			break;
223		case NGM_NAT_SET_TARGET:
224		    {
225			struct in_addr *const ia = (struct in_addr *)msg->data;
226
227			if (msg->header.arglen < sizeof(*ia)) {
228				error = EINVAL;
229				break;
230			}
231
232			LibAliasSetTarget(priv->lib, *ia);
233		    }
234			break;
235		default:
236			error = EINVAL;		/* unknown command */
237			break;
238		}
239		break;
240	default:
241		error = EINVAL;			/* unknown cookie type */
242		break;
243	}
244
245	NG_RESPOND_MSG(error, node, item, resp);
246	NG_FREE_MSG(msg);
247	return (error);
248}
249
250static int
251ng_nat_rcvdata(hook_p hook, item_p item )
252{
253	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
254	struct mbuf	*m;
255	struct ip	*ip;
256	int rval, error = 0;
257	char *c;
258
259	/* We have no required hooks. */
260	if (!(priv->flags & NGNAT_CONNECTED)) {
261		NG_FREE_ITEM(item);
262		return (ENXIO);
263	}
264
265	/* We have no alias address yet to do anything. */
266	if (!(priv->flags & NGNAT_ADDR_DEFINED))
267		goto send;
268
269	m = NGI_M(item);
270
271	if ((m = m_megapullup(m, m->m_pkthdr.len)) == NULL) {
272		NGI_M(item) = NULL;	/* avoid double free */
273		NG_FREE_ITEM(item);
274		return (ENOBUFS);
275	}
276
277	NGI_M(item) = m;
278
279	c = mtod(m, char *);
280	ip = mtod(m, struct ip *);
281
282	KASSERT(m->m_pkthdr.len == ntohs(ip->ip_len),
283	    ("ng_nat: ip_len != m_pkthdr.len"));
284
285	if (hook == priv->in) {
286		rval = LibAliasIn(priv->lib, c, MCLBYTES);
287		if (rval != PKT_ALIAS_OK &&
288		    rval != PKT_ALIAS_FOUND_HEADER_FRAGMENT) {
289			NG_FREE_ITEM(item);
290			return (EINVAL);
291		}
292	} else if (hook == priv->out) {
293		rval = LibAliasOut(priv->lib, c, MCLBYTES);
294		if (rval != PKT_ALIAS_OK) {
295			NG_FREE_ITEM(item);
296			return (EINVAL);
297		}
298	} else
299		panic("ng_nat: unknown hook!\n");
300
301	m->m_pkthdr.len = m->m_len = ntohs(ip->ip_len);
302
303	if ((ip->ip_off & htons(IP_OFFMASK)) == 0 &&
304	    ip->ip_p == IPPROTO_TCP) {
305		struct tcphdr *th = (struct tcphdr *)((caddr_t)ip +
306		    (ip->ip_hl << 2));
307
308		/*
309		 * Here is our terrible HACK.
310		 *
311		 * Sometimes LibAlias edits contents of TCP packet.
312		 * In this case it needs to recompute full TCP
313		 * checksum. However, the problem is that LibAlias
314		 * doesn't have any idea about checksum offloading
315		 * in kernel. To workaround this, we do not do
316		 * checksumming in LibAlias, but only mark the
317		 * packets in th_x2 field. If we receive a marked
318		 * packet, we calculate correct checksum for it
319		 * aware of offloading.
320		 *
321		 * Why do I do such a terrible hack instead of
322		 * recalculating checksum for each packet?
323		 * Because the previous checksum was not checked!
324		 * Recalculating checksums for EVERY packet will
325		 * hide ALL transmission errors. Yes, marked packets
326		 * still suffer from this problem. But, sigh, natd(8)
327		 * has this problem, too.
328		 */
329
330		if (th->th_x2) {
331			th->th_x2 = 0;
332			ip->ip_len = ntohs(ip->ip_len);
333			th->th_sum = in_pseudo(ip->ip_src.s_addr,
334			    ip->ip_dst.s_addr, htons(IPPROTO_TCP +
335			    ip->ip_len - (ip->ip_hl << 2)));
336
337			if ((m->m_pkthdr.csum_flags & CSUM_TCP) == 0) {
338				m->m_pkthdr.csum_data = offsetof(struct tcphdr,
339				    th_sum);
340				in_delayed_cksum(m);
341			}
342			ip->ip_len = htons(ip->ip_len);
343		}
344	}
345
346send:
347	if (hook == priv->in)
348		NG_FWD_ITEM_HOOK(error, item, priv->out);
349	else
350		NG_FWD_ITEM_HOOK(error, item, priv->in);
351
352	return (error);
353}
354
355static int
356ng_nat_shutdown(node_p node)
357{
358	const priv_p priv = NG_NODE_PRIVATE(node);
359
360	NG_NODE_SET_PRIVATE(node, NULL);
361	NG_NODE_UNREF(node);
362	LibAliasUninit(priv->lib);
363	FREE(priv, M_NETGRAPH);
364
365	return (0);
366}
367
368static int
369ng_nat_disconnect(hook_p hook)
370{
371	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
372
373	priv->flags &= ~NGNAT_CONNECTED;
374
375	if (hook == priv->out)
376		priv->out = NULL;
377	if (hook == priv->in)
378		priv->in = NULL;
379
380	if (priv->out == NULL && priv->in == NULL)
381		ng_rmnode_self(NG_HOOK_NODE(hook));
382
383	return (0);
384}
385
386static unsigned int
387ng_nat_translate_flags(unsigned int x)
388{
389	unsigned int	res = 0;
390
391	if (x & NG_NAT_LOG)
392		res |= PKT_ALIAS_LOG;
393	if (x & NG_NAT_DENY_INCOMING)
394		res |= PKT_ALIAS_DENY_INCOMING;
395	if (x & NG_NAT_SAME_PORTS)
396		res |= PKT_ALIAS_SAME_PORTS;
397	if (x & NG_NAT_UNREGISTERED_ONLY)
398		res |= PKT_ALIAS_UNREGISTERED_ONLY;
399	if (x & NG_NAT_RESET_ON_ADDR_CHANGE)
400		res |= PKT_ALIAS_RESET_ON_ADDR_CHANGE;
401	if (x & NG_NAT_PROXY_ONLY)
402		res |= PKT_ALIAS_PROXY_ONLY;
403	if (x & NG_NAT_REVERSE)
404		res |= PKT_ALIAS_REVERSE;
405
406	return (res);
407}
408