ng_nat.c revision 164797
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 164797 2006-12-01 16:27:11Z piso $
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			NG_FREE_ITEM(item);
234			return (EINVAL);
235		}
236	} else if (hook == priv->out) {
237		rval = LibAliasOut(priv->lib, c, MCLBYTES);
238		if (rval != PKT_ALIAS_OK) {
239			NG_FREE_ITEM(item);
240			return (EINVAL);
241		}
242	} else
243		panic("ng_nat: unknown hook!\n");
244
245	m->m_pkthdr.len = m->m_len = ntohs(ip->ip_len);
246
247	if ((ip->ip_off & htons(IP_OFFMASK)) == 0 &&
248	    ip->ip_p == IPPROTO_TCP) {
249		struct tcphdr 	*th = (struct tcphdr *)(ip + 1);
250
251		/*
252		 * Here is our terrible HACK.
253		 *
254		 * Sometimes LibAlias edits contents of TCP packet.
255		 * In this case it needs to recompute full TCP
256		 * checksum. However, the problem is that LibAlias
257		 * doesn't have any idea about checksum offloading
258		 * in kernel. To workaround this, we do not do
259		 * checksumming in LibAlias, but only mark the
260		 * packets in th_x2 field. If we receive a marked
261		 * packet, we calculate correct checksum for it
262		 * aware of offloading.
263		 *
264		 * Why do I do such a terrible hack instead of
265		 * recalculating checksum for each packet?
266		 * Because the previous checksum was not checked!
267		 * Recalculating checksums for EVERY packet will
268		 * hide ALL transmission errors. Yes, marked packets
269		 * still suffer from this problem. But, sigh, natd(8)
270		 * has this problem, too.
271		 */
272
273		if (th->th_x2) {
274			th->th_x2 = 0;
275			ip->ip_len = ntohs(ip->ip_len);
276			th->th_sum = in_pseudo(ip->ip_src.s_addr,
277			    ip->ip_dst.s_addr, htons(IPPROTO_TCP +
278			    ip->ip_len - (ip->ip_hl << 2)));
279
280			if ((m->m_pkthdr.csum_flags & CSUM_TCP) == 0) {
281				m->m_pkthdr.csum_data = offsetof(struct tcphdr,
282				    th_sum);
283				in_delayed_cksum(m);
284			}
285			ip->ip_len = htons(ip->ip_len);
286		}
287	}
288
289	if (hook == priv->in)
290		NG_FWD_ITEM_HOOK(error, item, priv->out);
291	else
292		NG_FWD_ITEM_HOOK(error, item, priv->in);
293
294	return (error);
295}
296
297static int
298ng_nat_shutdown(node_p node)
299{
300	const priv_p priv = NG_NODE_PRIVATE(node);
301
302	NG_NODE_SET_PRIVATE(node, NULL);
303	NG_NODE_UNREF(node);
304	LibAliasUninit(priv->lib);
305	FREE(priv, M_NETGRAPH);
306
307	return (0);
308}
309
310static int
311ng_nat_disconnect(hook_p hook)
312{
313	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
314
315	priv->flags &= ~NGNAT_READY;
316
317	if (hook == priv->out)
318		priv->out = NULL;
319	if (hook == priv->in)
320		priv->in = NULL;
321
322	if (priv->out == NULL && priv->in == NULL)
323		ng_rmnode_self(NG_HOOK_NODE(hook));
324
325	return (0);
326}
327
328