xref: /freebsd/sys/netgraph/ng_nat.c (revision f0a75d274af375d15b97b830966b99a02b7db911)
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 /* List of commands and how to convert arguments to/from ASCII. */
60 static 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. */
72 static 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 };
83 NETGRAPH_INIT(nat, &typestruct);
84 MODULE_DEPEND(ng_nat, libalias, 1, 1, 1);
85 
86 /* Information we store for each node. */
87 struct 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 };
94 typedef 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 
100 static int
101 ng_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 
135 static int
136 ng_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 
155 static int
156 ng_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 
200 static int
201 ng_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 
299 static int
300 ng_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 
312 static int
313 ng_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