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 */
77struct 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};
85typedef struct ng_vjc_private *priv_p;
86
87#define ERROUT(x)	do { error = (x); goto done; } while (0)
88
89/* Netgraph node methods */
90static ng_constructor_t	ng_vjc_constructor;
91static ng_rcvmsg_t	ng_vjc_rcvmsg;
92static ng_shutdown_t	ng_vjc_shutdown;
93static ng_newhook_t	ng_vjc_newhook;
94static ng_rcvdata_t	ng_vjc_rcvdata;
95static ng_disconnect_t	ng_vjc_disconnect;
96
97/* Helper stuff */
98static struct mbuf *ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP);
99
100/* Parse type for struct ngm_vjc_config */
101static const struct ng_parse_struct_field ng_vjc_config_type_fields[]
102	= NG_VJC_CONFIG_TYPE_INFO;
103static 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. */
118static const struct ng_parse_fixedarray_info ng_vjc_cs_hdr_type_info = {
119	&ng_parse_hint8_type,
120	MAX_HDR
121};
122static 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 */
128static 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};
136static 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 */
142static const struct ng_parse_fixedarray_info ng_vjc_cstatearray_type_info = {
143	&ng_vjc_cstate_type,
144	MAX_STATES
145};
146static 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> */
153static 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};
172static 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 */
178static 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 */
218static 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};
229NETGRAPH_INIT(vjc, &ng_vjc_typestruct);
230
231/************************************************************************
232			NETGRAPH NODE METHODS
233 ************************************************************************/
234
235/*
236 * Create a new node
237 */
238static int
239ng_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 */
258static int
259ng_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 */
288static int
289ng_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	}
386done:
387	NG_RESPOND_MSG(error, node, item, resp);
388	NG_FREE_MSG(msg);
389	return (error);
390}
391
392/*
393 * Receive data
394 */
395static int
396ng_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 */
540static int
541ng_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 */
555static int
556ng_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 */
588static struct mbuf *
589ng_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