xref: /freebsd/sys/netgraph/ng_nat.c (revision 1669d8afc64812c8d2d1d147ae1fd42ff441e1b1)
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$
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 
52 static ng_constructor_t	ng_nat_constructor;
53 static ng_rcvmsg_t	ng_nat_rcvmsg;
54 static ng_shutdown_t	ng_nat_shutdown;
55 static ng_newhook_t	ng_nat_newhook;
56 static ng_rcvdata_t	ng_nat_rcvdata;
57 static ng_disconnect_t	ng_nat_disconnect;
58 
59 static unsigned int	ng_nat_translate_flags(unsigned int x);
60 
61 /* Parse type for struct ng_nat_mode. */
62 static const struct ng_parse_struct_field ng_nat_mode_fields[]
63 	= NG_NAT_MODE_INFO;
64 static 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. */
70 static 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. */
96 static 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 };
107 NETGRAPH_INIT(nat, &typestruct);
108 MODULE_DEPEND(ng_nat, libalias, 1, 1, 1);
109 
110 /* Information we store for each node. */
111 struct 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 };
118 typedef 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 
124 static int
125 ng_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 
159 static int
160 ng_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 
178 static int
179 ng_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 
250 static int
251 ng_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 
346 send:
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 
355 static int
356 ng_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 
368 static int
369 ng_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 
386 static unsigned int
387 ng_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