ng_nat.c revision 165435
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 165435 2006-12-21 10:26:01Z 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/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
59/* List of commands and how to convert arguments to/from ASCII. */
60static const struct ng_cmdlist ng_nat_cmdlist[] = {
61	{
62	  NGM_NAT_COOKIE,
63	  NGM_NAT_SET_IPADDR,
64	  "setaliasaddr",
65	  &ng_parse_ipaddr_type,
66	  NULL
67	},
68	{ 0 }
69};
70
71/* Netgraph node type descriptor. */
72static struct ng_type typestruct = {
73	.version =	NG_ABI_VERSION,
74	.name =		NG_NAT_NODE_TYPE,
75	.constructor =	ng_nat_constructor,
76	.rcvmsg =	ng_nat_rcvmsg,
77	.shutdown =	ng_nat_shutdown,
78	.newhook =	ng_nat_newhook,
79	.rcvdata =	ng_nat_rcvdata,
80	.disconnect =	ng_nat_disconnect,
81	.cmdlist =	ng_nat_cmdlist,
82};
83NETGRAPH_INIT(nat, &typestruct);
84MODULE_DEPEND(ng_nat, libalias, 1, 1, 1);
85
86/* Information we store for each node. */
87struct ng_nat_priv {
88	node_p		node;		/* back pointer to node */
89	hook_p		in;		/* hook for demasquerading */
90	hook_p		out;		/* hook for masquerading */
91	struct libalias	*lib;		/* libalias handler */
92	uint32_t	flags;		/* status flags */
93};
94typedef struct ng_nat_priv *priv_p;
95
96/* Values of flags */
97#define	NGNAT_READY		0x1	/* We have everything to work */
98#define	NGNAT_ADDR_DEFINED	0x2	/* NGM_NAT_SET_IPADDR happened */
99
100static int
101ng_nat_constructor(node_p node)
102{
103	priv_p priv;
104
105	/* Initialize private descriptor. */
106	MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH,
107		M_NOWAIT | M_ZERO);
108	if (priv == NULL)
109		return (ENOMEM);
110
111	/* Init aliasing engine. */
112	priv->lib = LibAliasInit(NULL);
113	if (priv->lib == NULL) {
114		FREE(priv, M_NETGRAPH);
115		return (ENOMEM);
116	}
117
118	/* Set same ports on. */
119	(void )LibAliasSetMode(priv->lib, PKT_ALIAS_SAME_PORTS,
120	    PKT_ALIAS_SAME_PORTS);
121
122	/* Link structs together. */
123	NG_NODE_SET_PRIVATE(node, priv);
124	priv->node = node;
125
126	/*
127	 * libalias is not thread safe, so our node
128	 * must be single threaded.
129	 */
130	NG_NODE_FORCE_WRITER(node);
131
132	return (0);
133}
134
135static int
136ng_nat_newhook(node_p node, hook_p hook, const char *name)
137{
138	const priv_p priv = NG_NODE_PRIVATE(node);
139
140	if (strcmp(name, NG_NAT_HOOK_IN) == 0) {
141		priv->in = hook;
142	} else if (strcmp(name, NG_NAT_HOOK_OUT) == 0) {
143		priv->out = hook;
144	} else
145		return (EINVAL);
146
147	if (priv->out != NULL &&
148	    priv->in != NULL &&
149	    priv->flags & NGNAT_ADDR_DEFINED)
150		priv->flags |= NGNAT_READY;
151
152	return(0);
153}
154
155static int
156ng_nat_rcvmsg(node_p node, item_p item, hook_p lasthook)
157{
158	const priv_p priv = NG_NODE_PRIVATE(node);
159	struct ng_mesg *resp = NULL;
160	struct ng_mesg *msg;
161	int error = 0;
162
163	NGI_GET_MSG(item, msg);
164
165	switch (msg->header.typecookie) {
166	case NGM_NAT_COOKIE:
167		switch (msg->header.cmd) {
168		case NGM_NAT_SET_IPADDR:
169		    {
170			struct in_addr *const ia = (struct in_addr *)msg->data;
171
172			if (msg->header.arglen < sizeof(*ia)) {
173				error = EINVAL;
174				break;
175			}
176
177			LibAliasSetAddress(priv->lib, *ia);
178
179			priv->flags |= NGNAT_ADDR_DEFINED;
180			if (priv->out != NULL &&
181			    priv->in != NULL)
182				priv->flags |= NGNAT_READY;
183		    }
184			break;
185		default:
186			error = EINVAL;		/* unknown command */
187			break;
188		}
189		break;
190	default:
191		error = EINVAL;			/* unknown cookie type */
192		break;
193	}
194
195	NG_RESPOND_MSG(error, node, item, resp);
196	NG_FREE_MSG(msg);
197	return (error);
198}
199
200static int
201ng_nat_rcvdata(hook_p hook, item_p item )
202{
203	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
204	struct mbuf	*m;
205	struct ip	*ip;
206	int rval, error = 0;
207	char *c;
208
209	if (!(priv->flags & NGNAT_READY)) {
210		NG_FREE_ITEM(item);
211		return (ENXIO);
212	}
213
214	m = NGI_M(item);
215
216	if ((m = m_megapullup(m, m->m_pkthdr.len)) == NULL) {
217		NGI_M(item) = NULL;	/* avoid double free */
218		NG_FREE_ITEM(item);
219		return (ENOBUFS);
220	}
221
222	NGI_M(item) = m;
223
224	c = mtod(m, char *);
225	ip = mtod(m, struct ip *);
226
227	KASSERT(m->m_pkthdr.len == ntohs(ip->ip_len),
228	    ("ng_nat: ip_len != m_pkthdr.len"));
229
230	if (hook == priv->in) {
231		rval = LibAliasIn(priv->lib, c, MCLBYTES);
232		if (rval != PKT_ALIAS_OK &&
233		    rval != PKT_ALIAS_FOUND_HEADER_FRAGMENT) {
234			NG_FREE_ITEM(item);
235			return (EINVAL);
236		}
237	} else if (hook == priv->out) {
238		rval = LibAliasOut(priv->lib, c, MCLBYTES);
239		if (rval != PKT_ALIAS_OK) {
240			NG_FREE_ITEM(item);
241			return (EINVAL);
242		}
243	} else
244		panic("ng_nat: unknown hook!\n");
245
246	m->m_pkthdr.len = m->m_len = ntohs(ip->ip_len);
247
248	if ((ip->ip_off & htons(IP_OFFMASK)) == 0 &&
249	    ip->ip_p == IPPROTO_TCP) {
250		struct tcphdr *th = (struct tcphdr *)((caddr_t)ip +
251		    (ip->ip_hl << 2));
252
253		/*
254		 * Here is our terrible HACK.
255		 *
256		 * Sometimes LibAlias edits contents of TCP packet.
257		 * In this case it needs to recompute full TCP
258		 * checksum. However, the problem is that LibAlias
259		 * doesn't have any idea about checksum offloading
260		 * in kernel. To workaround this, we do not do
261		 * checksumming in LibAlias, but only mark the
262		 * packets in th_x2 field. If we receive a marked
263		 * packet, we calculate correct checksum for it
264		 * aware of offloading.
265		 *
266		 * Why do I do such a terrible hack instead of
267		 * recalculating checksum for each packet?
268		 * Because the previous checksum was not checked!
269		 * Recalculating checksums for EVERY packet will
270		 * hide ALL transmission errors. Yes, marked packets
271		 * still suffer from this problem. But, sigh, natd(8)
272		 * has this problem, too.
273		 */
274
275		if (th->th_x2) {
276			th->th_x2 = 0;
277			ip->ip_len = ntohs(ip->ip_len);
278			th->th_sum = in_pseudo(ip->ip_src.s_addr,
279			    ip->ip_dst.s_addr, htons(IPPROTO_TCP +
280			    ip->ip_len - (ip->ip_hl << 2)));
281
282			if ((m->m_pkthdr.csum_flags & CSUM_TCP) == 0) {
283				m->m_pkthdr.csum_data = offsetof(struct tcphdr,
284				    th_sum);
285				in_delayed_cksum(m);
286			}
287			ip->ip_len = htons(ip->ip_len);
288		}
289	}
290
291	if (hook == priv->in)
292		NG_FWD_ITEM_HOOK(error, item, priv->out);
293	else
294		NG_FWD_ITEM_HOOK(error, item, priv->in);
295
296	return (error);
297}
298
299static int
300ng_nat_shutdown(node_p node)
301{
302	const priv_p priv = NG_NODE_PRIVATE(node);
303
304	NG_NODE_SET_PRIVATE(node, NULL);
305	NG_NODE_UNREF(node);
306	LibAliasUninit(priv->lib);
307	FREE(priv, M_NETGRAPH);
308
309	return (0);
310}
311
312static int
313ng_nat_disconnect(hook_p hook)
314{
315	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
316
317	priv->flags &= ~NGNAT_READY;
318
319	if (hook == priv->out)
320		priv->out = NULL;
321	if (hook == priv->in)
322		priv->in = NULL;
323
324	if (priv->out == NULL && priv->in == NULL)
325		ng_rmnode_self(NG_HOOK_NODE(hook));
326
327	return (0);
328}
329
330