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 struct mbuf * m_megapullup(struct mbuf *, int); 60 61 /* List of commands and how to convert arguments to/from ASCII. */ 62 static const struct ng_cmdlist ng_nat_cmdlist[] = { 63 { 64 NGM_NAT_COOKIE, 65 NGM_NAT_SET_IPADDR, 66 "setaliasaddr", 67 &ng_parse_ipaddr_type, 68 NULL 69 }, 70 { 0 } 71 }; 72 73 /* Netgraph node type descriptor. */ 74 static struct ng_type typestruct = { 75 .version = NG_ABI_VERSION, 76 .name = NG_NAT_NODE_TYPE, 77 .constructor = ng_nat_constructor, 78 .rcvmsg = ng_nat_rcvmsg, 79 .shutdown = ng_nat_shutdown, 80 .newhook = ng_nat_newhook, 81 .rcvdata = ng_nat_rcvdata, 82 .disconnect = ng_nat_disconnect, 83 .cmdlist = ng_nat_cmdlist, 84 }; 85 NETGRAPH_INIT(nat, &typestruct); 86 MODULE_DEPEND(ng_nat, libalias, 1, 1, 1); 87 88 /* Information we store for each node. */ 89 struct ng_priv_priv { 90 node_p node; /* back pointer to node */ 91 hook_p in; /* hook for demasquerading */ 92 hook_p out; /* hook for masquerading */ 93 struct libalias *lib; /* libalias handler */ 94 uint32_t flags; /* status flags */ 95 }; 96 typedef struct ng_priv_priv *priv_p; 97 98 /* Values of flags */ 99 #define NGNAT_READY 0x1 /* We have everything to work */ 100 #define NGNAT_ADDR_DEFINED 0x2 /* NGM_NAT_SET_IPADDR happened */ 101 102 static int 103 ng_nat_constructor(node_p node) 104 { 105 priv_p priv; 106 107 /* Initialize private descriptor. */ 108 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, 109 M_NOWAIT | M_ZERO); 110 if (priv == NULL) 111 return (ENOMEM); 112 113 /* Init aliasing engine. */ 114 priv->lib = LibAliasInit(NULL); 115 if (priv->lib == NULL) { 116 FREE(priv, M_NETGRAPH); 117 return (ENOMEM); 118 } 119 120 /* Set same ports on. */ 121 (void )LibAliasSetMode(priv->lib, PKT_ALIAS_SAME_PORTS, 122 PKT_ALIAS_SAME_PORTS); 123 124 /* Link structs together. */ 125 NG_NODE_SET_PRIVATE(node, priv); 126 priv->node = node; 127 128 /* 129 * libalias is not thread safe, so our node 130 * must be single threaded. 131 */ 132 NG_NODE_FORCE_WRITER(node); 133 134 return (0); 135 } 136 137 static int 138 ng_nat_newhook(node_p node, hook_p hook, const char *name) 139 { 140 const priv_p priv = NG_NODE_PRIVATE(node); 141 142 if (strcmp(name, NG_NAT_HOOK_IN) == 0) { 143 priv->in = hook; 144 } else if (strcmp(name, NG_NAT_HOOK_OUT) == 0) { 145 priv->out = hook; 146 } else 147 return (EINVAL); 148 149 if (priv->out != NULL && 150 priv->in != NULL && 151 priv->flags & NGNAT_ADDR_DEFINED) 152 priv->flags |= NGNAT_READY; 153 154 return(0); 155 } 156 157 static int 158 ng_nat_rcvmsg(node_p node, item_p item, hook_p lasthook) 159 { 160 const priv_p priv = NG_NODE_PRIVATE(node); 161 struct ng_mesg *resp = NULL; 162 struct ng_mesg *msg; 163 int error = 0; 164 165 NGI_GET_MSG(item, msg); 166 167 switch (msg->header.typecookie) { 168 case NGM_NAT_COOKIE: 169 switch (msg->header.cmd) { 170 case NGM_NAT_SET_IPADDR: 171 { 172 struct in_addr *const ia = (struct in_addr *)msg->data; 173 174 if (msg->header.arglen < sizeof(*ia)) { 175 error = EINVAL; 176 break; 177 } 178 179 LibAliasSetAddress(priv->lib, *ia); 180 181 priv->flags |= NGNAT_ADDR_DEFINED; 182 if (priv->out != NULL && 183 priv->in != NULL) 184 priv->flags |= NGNAT_READY; 185 } 186 break; 187 default: 188 error = EINVAL; /* unknown command */ 189 break; 190 } 191 break; 192 default: 193 error = EINVAL; /* unknown cookie type */ 194 break; 195 } 196 197 NG_RESPOND_MSG(error, node, item, resp); 198 NG_FREE_MSG(msg); 199 return (error); 200 } 201 202 static int 203 ng_nat_rcvdata(hook_p hook, item_p item ) 204 { 205 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); 206 struct mbuf *m; 207 struct ip *ip; 208 int rval, error = 0; 209 char *c; 210 211 if (!(priv->flags & NGNAT_READY)) { 212 NG_FREE_ITEM(item); 213 return (ENXIO); 214 } 215 216 m = NGI_M(item); 217 218 if ((m = m_megapullup(m, m->m_pkthdr.len)) == NULL) { 219 NGI_M(item) = NULL; /* avoid double free */ 220 NG_FREE_ITEM(item); 221 return (ENOBUFS); 222 } 223 224 NGI_M(item) = m; 225 226 c = mtod(m, char *); 227 ip = mtod(m, struct ip *); 228 229 KASSERT(m->m_pkthdr.len == ntohs(ip->ip_len), 230 ("ng_nat: ip_len != m_pkthdr.len")); 231 232 if (hook == priv->in) { 233 rval = LibAliasIn(priv->lib, c, MCLBYTES); 234 if (rval != PKT_ALIAS_OK) { 235 NG_FREE_ITEM(item); 236 return (EINVAL); 237 } 238 } else if (hook == priv->out) { 239 rval = LibAliasOut(priv->lib, c, MCLBYTES); 240 if (rval != PKT_ALIAS_OK) { 241 NG_FREE_ITEM(item); 242 return (EINVAL); 243 } 244 } else 245 panic("ng_nat: unknown hook!\n"); 246 247 m->m_pkthdr.len = m->m_len = ntohs(ip->ip_len); 248 249 if ((ip->ip_off & htons(IP_OFFMASK)) == 0 && 250 ip->ip_p == IPPROTO_TCP) { 251 struct tcphdr *th = (struct tcphdr *)(ip + 1); 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 /* 331 * m_megapullup() function is a big hack. 332 * 333 * It allocates an mbuf with cluster and copies the whole 334 * chain into cluster, so that it is all contigous and the 335 * whole packet can be accessed via char pointer. 336 * 337 * This is required, because libalias doesn't have idea 338 * about mbufs. 339 */ 340 static struct mbuf * 341 m_megapullup(struct mbuf *m, int len) 342 { 343 struct mbuf *mcl; 344 caddr_t cp; 345 346 if (len > MCLBYTES) 347 goto bad; 348 349 if ((mcl = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR)) == NULL) 350 goto bad; 351 352 cp = mtod(mcl, caddr_t); 353 m_copydata(m, 0, len, cp); 354 m_move_pkthdr(mcl, m); 355 mcl->m_len = mcl->m_pkthdr.len; 356 m_freem(m); 357 358 return (mcl); 359 bad: 360 m_freem(m); 361 return (NULL); 362 } 363