1 2 /* 3 * ng_vjc.c 4 * 5 * Copyright (c) 1996-1999 Whistle Communications, Inc. 6 * All rights reserved. 7 * 8 * Subject to the following obligations and disclaimer of warranty, use and 9 * redistribution of this software, in source or object code forms, with or 10 * without modifications are expressly permitted by Whistle Communications; 11 * provided, however, that: 12 * 1. Any and all reproductions of the source or object code must include the 13 * copyright notice above and the following disclaimer of warranties; and 14 * 2. No rights are granted, in any manner or form, to use Whistle 15 * Communications, Inc. trademarks, including the mark "WHISTLE 16 * COMMUNICATIONS" on advertising, endorsements, or otherwise except as 17 * such appears in the above copyright notice or in the software. 18 * 19 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND 20 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO 21 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, 22 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF 23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. 24 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY 25 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS 26 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. 27 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES 28 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING 29 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 30 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR 31 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY 32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY 35 * OF SUCH DAMAGE. 36 * 37 * Author: Archie Cobbs <archie@freebsd.org> 38 * 39 * $FreeBSD$ 40 * $Whistle: ng_vjc.c,v 1.17 1999/11/01 09:24:52 julian Exp $ 41 */ 42 43 /* 44 * This node performs Van Jacobson IP header (de)compression. 45 * You must have included net/slcompress.c in your kernel compilation. 46 */ 47 48 #include <sys/param.h> 49 #include <sys/systm.h> 50 #include <sys/errno.h> 51 #include <sys/kernel.h> 52 #include <sys/mbuf.h> 53 #include <sys/malloc.h> 54 #include <sys/errno.h> 55 56 #include <netgraph/ng_message.h> 57 #include <netgraph/netgraph.h> 58 #include <netgraph/ng_parse.h> 59 #include <netgraph/ng_vjc.h> 60 61 #include <netinet/in.h> 62 #include <netinet/in_systm.h> 63 #include <netinet/ip.h> 64 #include <netinet/tcp.h> 65 66 #include <net/slcompress.h> 67 68 /* Check agreement with slcompress.c */ 69 #if MAX_STATES != NG_VJC_MAX_CHANNELS 70 #error NG_VJC_MAX_CHANNELS must be the same as MAX_STATES 71 #endif 72 73 /* Maximum length of a compressed TCP VJ header */ 74 #define MAX_VJHEADER 19 75 76 /* Node private data */ 77 struct ng_vjc_private { 78 struct ngm_vjc_config conf; 79 struct slcompress slc; 80 hook_p ip; 81 hook_p vjcomp; 82 hook_p vjuncomp; 83 hook_p vjip; 84 }; 85 typedef struct ng_vjc_private *priv_p; 86 87 #define ERROUT(x) do { error = (x); goto done; } while (0) 88 89 /* Netgraph node methods */ 90 static ng_constructor_t ng_vjc_constructor; 91 static ng_rcvmsg_t ng_vjc_rcvmsg; 92 static ng_shutdown_t ng_vjc_rmnode; 93 static ng_newhook_t ng_vjc_newhook; 94 static ng_rcvdata_t ng_vjc_rcvdata; 95 static ng_disconnect_t ng_vjc_disconnect; 96 97 /* Helper stuff */ 98 static struct mbuf *ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP); 99 100 /* Parse type for struct ngm_vjc_config */ 101 static const struct ng_parse_struct_info ng_vjc_config_type_info 102 = NG_VJC_CONFIG_TYPE_INFO; 103 static const struct ng_parse_type ng_vjc_config_type = { 104 &ng_parse_struct_type, 105 &ng_vjc_config_type_info 106 }; 107 108 /* Parse type for the 'last_cs' and 'cs_next' fields in struct slcompress, 109 which are pointers converted to integer indices, so parse them that way. */ 110 #if _MACHINE_ARCH == i386 111 #define NG_VJC_TSTATE_PTR_TYPE &ng_parse_uint32_type 112 #elif _MACHINE_ARCH == alpha 113 #define NG_VJC_TSTATE_PTR_TYPE &ng_parse_uint64_type 114 #else 115 #error Unspported _MACHINE_ARCH 116 #endif 117 118 /* Parse type for the 'cs_hdr' field in a struct cstate. Ideally we would 119 like to use a 'struct ip' type instead of a simple array of bytes. */ 120 static const struct ng_parse_fixedarray_info ng_vjc_cs_hdr_type_info = { 121 &ng_parse_hint8_type, 122 MAX_HDR 123 }; 124 static const struct ng_parse_type ng_vjc_cs_hdr_type = { 125 &ng_parse_fixedarray_type, 126 &ng_vjc_cs_hdr_type_info 127 }; 128 129 /* Parse type for a struct cstate */ 130 static const struct ng_parse_struct_info ng_vjc_cstate_type_info = { 131 { 132 { "cs_next", NG_VJC_TSTATE_PTR_TYPE }, 133 { "cs_hlen", &ng_parse_uint16_type }, 134 { "cs_id", &ng_parse_uint8_type }, 135 { "cs_filler", &ng_parse_uint8_type }, 136 { "cs_hdr", &ng_vjc_cs_hdr_type }, 137 { NULL }, 138 } 139 }; 140 static const struct ng_parse_type ng_vjc_cstate_type = { 141 &ng_parse_struct_type, 142 &ng_vjc_cstate_type_info 143 }; 144 145 /* Parse type for an array of MAX_STATES struct cstate's, ie, tstate & rstate */ 146 static const struct ng_parse_fixedarray_info ng_vjc_cstatearray_type_info = { 147 &ng_vjc_cstate_type, 148 MAX_STATES 149 }; 150 static const struct ng_parse_type ng_vjc_cstatearray_type = { 151 &ng_parse_fixedarray_type, 152 &ng_vjc_cstatearray_type_info 153 }; 154 155 /* Parse type for struct slcompress. Keep this in sync with the 156 definition of struct slcompress defined in <net/slcompress.h> */ 157 static const struct ng_parse_struct_info ng_vjc_slcompress_type_info = { 158 { 159 { "last_cs", NG_VJC_TSTATE_PTR_TYPE }, 160 { "last_recv", &ng_parse_uint8_type }, 161 { "last_xmit", &ng_parse_uint8_type }, 162 { "flags", &ng_parse_hint16_type }, 163 #ifndef SL_NO_STATS 164 { "sls_packets", &ng_parse_uint32_type }, 165 { "sls_compressed", &ng_parse_uint32_type }, 166 { "sls_searches", &ng_parse_uint32_type }, 167 { "sls_misses", &ng_parse_uint32_type }, 168 { "sls_uncompressedin", &ng_parse_uint32_type }, 169 { "sls_compressedin", &ng_parse_uint32_type }, 170 { "sls_errorin", &ng_parse_uint32_type }, 171 { "sls_tossed", &ng_parse_uint32_type }, 172 #endif 173 { "tstate", &ng_vjc_cstatearray_type }, 174 { "rstate", &ng_vjc_cstatearray_type }, 175 { NULL }, 176 } 177 }; 178 static const struct ng_parse_type ng_vjc_slcompress_type = { 179 &ng_parse_struct_type, 180 &ng_vjc_slcompress_type_info 181 }; 182 183 /* List of commands and how to convert arguments to/from ASCII */ 184 static const struct ng_cmdlist ng_vjc_cmds[] = { 185 { 186 NGM_VJC_COOKIE, 187 NGM_VJC_SET_CONFIG, 188 "setconfig", 189 &ng_vjc_config_type, 190 NULL 191 }, 192 { 193 NGM_VJC_COOKIE, 194 NGM_VJC_GET_CONFIG, 195 "getconfig", 196 NULL, 197 &ng_vjc_config_type, 198 }, 199 { 200 NGM_VJC_COOKIE, 201 NGM_VJC_GET_STATE, 202 "getstate", 203 NULL, 204 &ng_vjc_slcompress_type, 205 }, 206 { 207 NGM_VJC_COOKIE, 208 NGM_VJC_CLR_STATS, 209 "clrstats", 210 NULL, 211 NULL, 212 }, 213 { 214 NGM_VJC_COOKIE, 215 NGM_VJC_RECV_ERROR, 216 "recverror", 217 NULL, 218 NULL, 219 }, 220 { 0 } 221 }; 222 223 /* Node type descriptor */ 224 static struct ng_type ng_vjc_typestruct = { 225 NG_VERSION, 226 NG_VJC_NODE_TYPE, 227 NULL, 228 ng_vjc_constructor, 229 ng_vjc_rcvmsg, 230 ng_vjc_rmnode, 231 ng_vjc_newhook, 232 NULL, 233 NULL, 234 ng_vjc_rcvdata, 235 ng_vjc_rcvdata, 236 ng_vjc_disconnect, 237 ng_vjc_cmds 238 }; 239 NETGRAPH_INIT(vjc, &ng_vjc_typestruct); 240 241 /************************************************************************ 242 NETGRAPH NODE METHODS 243 ************************************************************************/ 244 245 /* 246 * Create a new node 247 */ 248 static int 249 ng_vjc_constructor(node_p *nodep) 250 { 251 priv_p priv; 252 int error; 253 254 /* Allocate private structure */ 255 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); 256 if (priv == NULL) 257 return (ENOMEM); 258 259 /* Call generic node constructor */ 260 if ((error = ng_make_node_common(&ng_vjc_typestruct, nodep))) { 261 FREE(priv, M_NETGRAPH); 262 return (error); 263 } 264 (*nodep)->private = priv; 265 266 /* Done */ 267 return (0); 268 } 269 270 /* 271 * Add a new hook 272 */ 273 static int 274 ng_vjc_newhook(node_p node, hook_p hook, const char *name) 275 { 276 const priv_p priv = (priv_p) node->private; 277 hook_p *hookp; 278 279 /* Get hook */ 280 if (strcmp(name, NG_VJC_HOOK_IP) == 0) 281 hookp = &priv->ip; 282 else if (strcmp(name, NG_VJC_HOOK_VJCOMP) == 0) 283 hookp = &priv->vjcomp; 284 else if (strcmp(name, NG_VJC_HOOK_VJUNCOMP) == 0) 285 hookp = &priv->vjuncomp; 286 else if (strcmp(name, NG_VJC_HOOK_VJIP) == 0) 287 hookp = &priv->vjip; 288 else 289 return (EINVAL); 290 291 /* See if already connected */ 292 if (*hookp) 293 return (EISCONN); 294 295 /* OK */ 296 *hookp = hook; 297 return (0); 298 } 299 300 /* 301 * Receive a control message 302 */ 303 static int 304 ng_vjc_rcvmsg(node_p node, struct ng_mesg *msg, 305 const char *raddr, struct ng_mesg **rptr, hook_p lasthook) 306 { 307 const priv_p priv = (priv_p) node->private; 308 struct ng_mesg *resp = NULL; 309 int error = 0; 310 311 /* Check type cookie */ 312 switch (msg->header.typecookie) { 313 case NGM_VJC_COOKIE: 314 switch (msg->header.cmd) { 315 case NGM_VJC_SET_CONFIG: 316 { 317 struct ngm_vjc_config *const c = 318 (struct ngm_vjc_config *) msg->data; 319 320 if (msg->header.arglen != sizeof(*c)) 321 ERROUT(EINVAL); 322 if ((priv->conf.enableComp || priv->conf.enableDecomp) 323 && (c->enableComp || c->enableDecomp)) 324 ERROUT(EALREADY); 325 if (c->enableComp) { 326 if (c->maxChannel > NG_VJC_MAX_CHANNELS - 1 327 || c->maxChannel < NG_VJC_MIN_CHANNELS - 1) 328 ERROUT(EINVAL); 329 } else 330 c->maxChannel = NG_VJC_MAX_CHANNELS - 1; 331 if (c->enableComp != 0 || c->enableDecomp != 0) { 332 bzero(&priv->slc, sizeof(priv->slc)); 333 sl_compress_init(&priv->slc, c->maxChannel); 334 } 335 priv->conf = *c; 336 break; 337 } 338 case NGM_VJC_GET_CONFIG: 339 { 340 struct ngm_vjc_config *conf; 341 342 NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); 343 if (resp == NULL) 344 ERROUT(ENOMEM); 345 conf = (struct ngm_vjc_config *)resp->data; 346 *conf = priv->conf; 347 break; 348 } 349 case NGM_VJC_GET_STATE: 350 { 351 const struct slcompress *const sl0 = &priv->slc; 352 struct slcompress *sl; 353 u_int16_t index; 354 int i; 355 356 /* Get response structure */ 357 NG_MKRESPONSE(resp, msg, sizeof(*sl), M_NOWAIT); 358 if (resp == NULL) 359 ERROUT(ENOMEM); 360 sl = (struct slcompress *)resp->data; 361 *sl = *sl0; 362 363 /* Replace pointers with integer indicies */ 364 if (sl->last_cs != NULL) { 365 index = sl0->last_cs - sl0->tstate; 366 bzero(&sl->last_cs, sizeof(sl->last_cs)); 367 *((u_int16_t *)&sl->last_cs) = index; 368 } 369 for (i = 0; i < MAX_STATES; i++) { 370 struct cstate *const cs = &sl->tstate[i]; 371 372 index = sl0->tstate[i].cs_next - sl0->tstate; 373 bzero(&cs->cs_next, sizeof(cs->cs_next)); 374 *((u_int16_t *)&cs->cs_next) = index; 375 } 376 break; 377 } 378 case NGM_VJC_CLR_STATS: 379 priv->slc.sls_packets = 0; 380 priv->slc.sls_compressed = 0; 381 priv->slc.sls_searches = 0; 382 priv->slc.sls_misses = 0; 383 priv->slc.sls_uncompressedin = 0; 384 priv->slc.sls_compressedin = 0; 385 priv->slc.sls_errorin = 0; 386 priv->slc.sls_tossed = 0; 387 break; 388 case NGM_VJC_RECV_ERROR: 389 sl_uncompress_tcp(NULL, 0, TYPE_ERROR, &priv->slc); 390 break; 391 default: 392 error = EINVAL; 393 break; 394 } 395 break; 396 default: 397 error = EINVAL; 398 break; 399 } 400 if (rptr) 401 *rptr = resp; 402 else if (resp) 403 FREE(resp, M_NETGRAPH); 404 405 done: 406 FREE(msg, M_NETGRAPH); 407 return (error); 408 } 409 410 /* 411 * Receive data 412 */ 413 static int 414 ng_vjc_rcvdata(hook_p hook, struct mbuf *m, meta_p meta, 415 struct mbuf **ret_m, meta_p *ret_meta) 416 { 417 const node_p node = hook->node; 418 const priv_p priv = (priv_p) node->private; 419 int error = 0; 420 421 if (hook == priv->ip) { /* outgoing packet */ 422 u_int type = TYPE_IP; 423 424 /* Compress packet if enabled and proto is TCP */ 425 if (priv->conf.enableComp) { 426 struct ip *ip; 427 428 if ((m = ng_vjc_pulluphdrs(m, 0)) == NULL) { 429 NG_FREE_META(meta); 430 return (ENOBUFS); 431 } 432 ip = mtod(m, struct ip *); 433 if (ip->ip_p == IPPROTO_TCP) { 434 const int origLen = m->m_len; 435 436 type = sl_compress_tcp(m, ip, 437 &priv->slc, priv->conf.compressCID); 438 m->m_pkthdr.len += m->m_len - origLen; 439 } 440 } 441 442 /* Dispatch to the appropriate outgoing hook */ 443 switch (type) { 444 case TYPE_IP: 445 hook = priv->vjip; 446 break; 447 case TYPE_UNCOMPRESSED_TCP: 448 hook = priv->vjuncomp; 449 break; 450 case TYPE_COMPRESSED_TCP: 451 hook = priv->vjcomp; 452 break; 453 default: 454 panic("%s: type=%d", __FUNCTION__, type); 455 } 456 } else if (hook == priv->vjcomp) { /* incoming compressed packet */ 457 int vjlen, need2pullup; 458 struct mbuf *hm; 459 u_char *hdr; 460 u_int hlen; 461 462 /* Are we decompressing? */ 463 if (!priv->conf.enableDecomp) { 464 NG_FREE_DATA(m, meta); 465 return (ENXIO); 466 } 467 468 /* Pull up the necessary amount from the mbuf */ 469 need2pullup = MAX_VJHEADER; 470 if (need2pullup > m->m_pkthdr.len) 471 need2pullup = m->m_pkthdr.len; 472 if (m->m_len < need2pullup 473 && (m = m_pullup(m, need2pullup)) == NULL) { 474 priv->slc.sls_errorin++; 475 NG_FREE_META(meta); 476 return (ENOBUFS); 477 } 478 479 /* Uncompress packet to reconstruct TCP/IP header */ 480 vjlen = sl_uncompress_tcp_core(mtod(m, u_char *), 481 m->m_len, m->m_pkthdr.len, TYPE_COMPRESSED_TCP, 482 &priv->slc, &hdr, &hlen); 483 if (vjlen <= 0) { 484 NG_FREE_DATA(m, meta); 485 return (EINVAL); 486 } 487 m_adj(m, vjlen); 488 489 /* Copy the reconstructed TCP/IP headers into a new mbuf */ 490 MGETHDR(hm, M_DONTWAIT, MT_DATA); 491 if (hm == NULL) { 492 priv->slc.sls_errorin++; 493 NG_FREE_DATA(m, meta); 494 return (ENOBUFS); 495 } 496 hm->m_len = 0; 497 hm->m_pkthdr.rcvif = NULL; 498 if (hlen > MHLEN) { /* unlikely, but can happen */ 499 MCLGET(hm, M_DONTWAIT); 500 if ((hm->m_flags & M_EXT) == 0) { 501 m_freem(hm); 502 priv->slc.sls_errorin++; 503 NG_FREE_DATA(m, meta); 504 return (ENOBUFS); 505 } 506 } 507 bcopy(hdr, mtod(hm, u_char *), hlen); 508 hm->m_len = hlen; 509 510 /* Glue TCP/IP headers and rest of packet together */ 511 hm->m_next = m; 512 hm->m_pkthdr.len = hlen + m->m_pkthdr.len; 513 m = hm; 514 hook = priv->ip; 515 } else if (hook == priv->vjuncomp) { /* incoming uncompressed pkt */ 516 u_char *hdr; 517 u_int hlen; 518 519 /* Are we decompressing? */ 520 if (!priv->conf.enableDecomp) { 521 NG_FREE_DATA(m, meta); 522 return (ENXIO); 523 } 524 525 /* Pull up IP+TCP headers */ 526 if ((m = ng_vjc_pulluphdrs(m, 1)) == NULL) { 527 NG_FREE_META(meta); 528 return (ENOBUFS); 529 } 530 531 /* Run packet through uncompressor */ 532 if (sl_uncompress_tcp_core(mtod(m, u_char *), 533 m->m_len, m->m_pkthdr.len, TYPE_UNCOMPRESSED_TCP, 534 &priv->slc, &hdr, &hlen) < 0) { 535 NG_FREE_DATA(m, meta); 536 return (EINVAL); 537 } 538 hook = priv->ip; 539 } else if (hook == priv->vjip) /* incoming regular packet (bypass) */ 540 hook = priv->ip; 541 else 542 panic("%s: unknown hook", __FUNCTION__); 543 544 /* Send result back out */ 545 NG_SEND_DATA(error, hook, m, meta); 546 return (error); 547 } 548 549 /* 550 * Shutdown node 551 */ 552 static int 553 ng_vjc_rmnode(node_p node) 554 { 555 const priv_p priv = (priv_p) node->private; 556 557 node->flags |= NG_INVALID; 558 ng_cutlinks(node); 559 ng_unname(node); 560 bzero(priv, sizeof(*priv)); 561 FREE(priv, M_NETGRAPH); 562 node->private = NULL; 563 ng_unref(node); 564 return (0); 565 } 566 567 /* 568 * Hook disconnection 569 */ 570 static int 571 ng_vjc_disconnect(hook_p hook) 572 { 573 const node_p node = hook->node; 574 const priv_p priv = node->private; 575 576 /* Zero out hook pointer */ 577 if (hook == priv->ip) 578 priv->ip = NULL; 579 else if (hook == priv->vjcomp) 580 priv->vjcomp = NULL; 581 else if (hook == priv->vjuncomp) 582 priv->vjuncomp = NULL; 583 else if (hook == priv->vjip) 584 priv->vjip = NULL; 585 else 586 panic("%s: unknown hook", __FUNCTION__); 587 588 /* Go away if no hooks left */ 589 if (node->numhooks == 0) 590 ng_rmnode(node); 591 return (0); 592 } 593 594 /************************************************************************ 595 HELPER STUFF 596 ************************************************************************/ 597 598 /* 599 * Pull up the full IP and TCP headers of a packet. If packet is not 600 * a TCP packet, just pull up the IP header. 601 */ 602 static struct mbuf * 603 ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP) 604 { 605 struct ip *ip; 606 struct tcphdr *tcp; 607 int ihlen, thlen; 608 609 if (m->m_len < sizeof(*ip) && (m = m_pullup(m, sizeof(*ip))) == NULL) 610 return (NULL); 611 ip = mtod(m, struct ip *); 612 if (!knownTCP && ip->ip_p != IPPROTO_TCP) 613 return (m); 614 ihlen = ip->ip_hl << 2; 615 if (m->m_len < ihlen + sizeof(*tcp)) { 616 if ((m = m_pullup(m, ihlen + sizeof(*tcp))) == NULL) 617 return (NULL); 618 ip = mtod(m, struct ip *); 619 } 620 tcp = (struct tcphdr *)((u_char *)ip + ihlen); 621 thlen = tcp->th_off << 2; 622 if (m->m_len < ihlen + thlen) 623 m = m_pullup(m, ihlen + thlen); 624 return (m); 625 } 626 627