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