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