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