ng_vjc.c revision 70784
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: head/sys/netgraph/ng_vjc.c 70784 2001-01-08 05:34:06Z julian $
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_info ng_vjc_config_type_info
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_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. */
120static const struct ng_parse_fixedarray_info ng_vjc_cs_hdr_type_info = {
121	&ng_parse_hint8_type,
122	MAX_HDR
123};
124static 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 */
130static 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};
140static 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 */
146static const struct ng_parse_fixedarray_info ng_vjc_cstatearray_type_info = {
147	&ng_vjc_cstate_type,
148	MAX_STATES
149};
150static 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> */
157static 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};
178static 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 */
184static 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 */
224static struct ng_type ng_vjc_typestruct = {
225	NG_ABI_VERSION,
226	NG_VJC_NODE_TYPE,
227	NULL,
228	ng_vjc_constructor,
229	ng_vjc_rcvmsg,
230	ng_vjc_shutdown,
231	ng_vjc_newhook,
232	NULL,
233	NULL,
234	ng_vjc_rcvdata,
235	ng_vjc_disconnect,
236	ng_vjc_cmds
237};
238NETGRAPH_INIT(vjc, &ng_vjc_typestruct);
239
240/************************************************************************
241			NETGRAPH NODE METHODS
242 ************************************************************************/
243
244/*
245 * Create a new node
246 */
247static int
248ng_vjc_constructor(node_p node)
249{
250	priv_p priv;
251
252	/* Allocate private structure */
253	MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO);
254	if (priv == NULL)
255		return (ENOMEM);
256
257	NG_NODE_SET_PRIVATE(node, priv);
258
259	/* Done */
260	return (0);
261}
262
263/*
264 * Add a new hook
265 */
266static int
267ng_vjc_newhook(node_p node, hook_p hook, const char *name)
268{
269	const priv_p priv = NG_NODE_PRIVATE(node);
270	hook_p *hookp;
271
272	/* Get hook */
273	if (strcmp(name, NG_VJC_HOOK_IP) == 0)
274		hookp = &priv->ip;
275	else if (strcmp(name, NG_VJC_HOOK_VJCOMP) == 0)
276		hookp = &priv->vjcomp;
277	else if (strcmp(name, NG_VJC_HOOK_VJUNCOMP) == 0)
278		hookp = &priv->vjuncomp;
279	else if (strcmp(name, NG_VJC_HOOK_VJIP) == 0)
280		hookp = &priv->vjip;
281	else
282		return (EINVAL);
283
284	/* See if already connected */
285	if (*hookp)
286		return (EISCONN);
287
288	/* OK */
289	*hookp = hook;
290	return (0);
291}
292
293/*
294 * Receive a control message
295 */
296static int
297ng_vjc_rcvmsg(node_p node, item_p item, hook_p lasthook)
298{
299	const priv_p priv = NG_NODE_PRIVATE(node);
300	struct ng_mesg *resp = NULL;
301	int error = 0;
302	struct ng_mesg *msg;
303
304	NGI_GET_MSG(item, msg);
305	/* Check type cookie */
306	switch (msg->header.typecookie) {
307	case NGM_VJC_COOKIE:
308		switch (msg->header.cmd) {
309		case NGM_VJC_SET_CONFIG:
310		    {
311			struct ngm_vjc_config *const c =
312				(struct ngm_vjc_config *) msg->data;
313
314			if (msg->header.arglen != sizeof(*c))
315				ERROUT(EINVAL);
316			if ((priv->conf.enableComp || priv->conf.enableDecomp)
317			    && (c->enableComp || c->enableDecomp))
318				ERROUT(EALREADY);
319			if (c->enableComp) {
320				if (c->maxChannel > NG_VJC_MAX_CHANNELS - 1
321				    || c->maxChannel < NG_VJC_MIN_CHANNELS - 1)
322					ERROUT(EINVAL);
323			} else
324				c->maxChannel = NG_VJC_MAX_CHANNELS - 1;
325			if (c->enableComp != 0 || c->enableDecomp != 0) {
326				bzero(&priv->slc, sizeof(priv->slc));
327				sl_compress_init(&priv->slc, c->maxChannel);
328			}
329			priv->conf = *c;
330			break;
331		    }
332		case NGM_VJC_GET_CONFIG:
333		    {
334			struct ngm_vjc_config *conf;
335
336			NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT);
337			if (resp == NULL)
338				ERROUT(ENOMEM);
339			conf = (struct ngm_vjc_config *)resp->data;
340			*conf = priv->conf;
341			break;
342		    }
343		case NGM_VJC_GET_STATE:
344		    {
345			const struct slcompress *const sl0 = &priv->slc;
346			struct slcompress *sl;
347			u_int16_t index;
348			int i;
349
350			/* Get response structure */
351			NG_MKRESPONSE(resp, msg, sizeof(*sl), M_NOWAIT);
352			if (resp == NULL)
353				ERROUT(ENOMEM);
354			sl = (struct slcompress *)resp->data;
355			*sl = *sl0;
356
357			/* Replace pointers with integer indicies */
358			if (sl->last_cs != NULL) {
359				index = sl0->last_cs - sl0->tstate;
360				bzero(&sl->last_cs, sizeof(sl->last_cs));
361				*((u_int16_t *)&sl->last_cs) = index;
362			}
363			for (i = 0; i < MAX_STATES; i++) {
364				struct cstate *const cs = &sl->tstate[i];
365
366				index = sl0->tstate[i].cs_next - sl0->tstate;
367				bzero(&cs->cs_next, sizeof(cs->cs_next));
368				*((u_int16_t *)&cs->cs_next) = index;
369			}
370			break;
371		    }
372		case NGM_VJC_CLR_STATS:
373			priv->slc.sls_packets = 0;
374			priv->slc.sls_compressed = 0;
375			priv->slc.sls_searches = 0;
376			priv->slc.sls_misses = 0;
377			priv->slc.sls_uncompressedin = 0;
378			priv->slc.sls_compressedin = 0;
379			priv->slc.sls_errorin = 0;
380			priv->slc.sls_tossed = 0;
381			break;
382		case NGM_VJC_RECV_ERROR:
383			sl_uncompress_tcp(NULL, 0, TYPE_ERROR, &priv->slc);
384			break;
385		default:
386			error = EINVAL;
387			break;
388		}
389		break;
390	default:
391		error = EINVAL;
392		break;
393	}
394done:
395	NG_RESPOND_MSG(error, node, item, resp);
396	NG_FREE_MSG(msg);
397	return (error);
398}
399
400/*
401 * Receive data
402 */
403static int
404ng_vjc_rcvdata(hook_p hook, item_p item)
405{
406	const node_p node = NG_HOOK_NODE(hook);
407	const priv_p priv = NG_NODE_PRIVATE(node);
408	int error = 0;
409	struct mbuf *m;
410
411	NGI_GET_M(item, m);
412	if (hook == priv->ip) {			/* outgoing packet */
413		u_int type = TYPE_IP;
414
415		/* Compress packet if enabled and proto is TCP */
416		if (priv->conf.enableComp) {
417			struct ip *ip;
418
419			if ((m = ng_vjc_pulluphdrs(m, 0)) == NULL) {
420				NG_FREE_ITEM(item);
421				return (ENOBUFS);
422			}
423			ip = mtod(m, struct ip *);
424			if (ip->ip_p == IPPROTO_TCP) {
425				const int origLen = m->m_len;
426
427				type = sl_compress_tcp(m, ip,
428				    &priv->slc, priv->conf.compressCID);
429				m->m_pkthdr.len += m->m_len - origLen;
430			}
431		}
432
433		/* Dispatch to the appropriate outgoing hook */
434		switch (type) {
435		case TYPE_IP:
436			hook = priv->vjip;
437			break;
438		case TYPE_UNCOMPRESSED_TCP:
439			hook = priv->vjuncomp;
440			break;
441		case TYPE_COMPRESSED_TCP:
442			hook = priv->vjcomp;
443			break;
444		default:
445			panic("%s: type=%d", __FUNCTION__, type);
446		}
447	} else if (hook == priv->vjcomp) {	/* incoming compressed packet */
448		int vjlen, need2pullup;
449		struct mbuf *hm;
450		u_char *hdr;
451		u_int hlen;
452
453		/* Are we decompressing? */
454		if (!priv->conf.enableDecomp) {
455			NG_FREE_M(m);
456			NG_FREE_ITEM(item);
457			return (ENXIO);
458		}
459
460		/* Pull up the necessary amount from the mbuf */
461		need2pullup = MAX_VJHEADER;
462		if (need2pullup > m->m_pkthdr.len)
463			need2pullup = m->m_pkthdr.len;
464		if (m->m_len < need2pullup
465		    && (m = m_pullup(m, need2pullup)) == NULL) {
466			priv->slc.sls_errorin++;
467			NG_FREE_ITEM(item);
468			return (ENOBUFS);
469		}
470
471		/* Uncompress packet to reconstruct TCP/IP header */
472		vjlen = sl_uncompress_tcp_core(mtod(m, u_char *),
473		    m->m_len, m->m_pkthdr.len, TYPE_COMPRESSED_TCP,
474		    &priv->slc, &hdr, &hlen);
475		if (vjlen <= 0) {
476			NG_FREE_M(m);
477			NG_FREE_ITEM(item);
478			return (EINVAL);
479		}
480		m_adj(m, vjlen);
481
482		/* Copy the reconstructed TCP/IP headers into a new mbuf */
483		MGETHDR(hm, M_DONTWAIT, MT_DATA);
484		if (hm == NULL) {
485			priv->slc.sls_errorin++;
486			NG_FREE_M(m);
487			NG_FREE_ITEM(item);
488			return (ENOBUFS);
489		}
490		hm->m_len = 0;
491		hm->m_pkthdr.rcvif = NULL;
492		if (hlen > MHLEN) {		/* unlikely, but can happen */
493			MCLGET(hm, M_DONTWAIT);
494			if ((hm->m_flags & M_EXT) == 0) {
495				m_freem(hm);
496				priv->slc.sls_errorin++;
497				NG_FREE_M(m);
498				NG_FREE_ITEM(item);
499				return (ENOBUFS);
500			}
501		}
502		bcopy(hdr, mtod(hm, u_char *), hlen);
503		hm->m_len = hlen;
504
505		/* Glue TCP/IP headers and rest of packet together */
506		hm->m_next = m;
507		hm->m_pkthdr.len = hlen + m->m_pkthdr.len;
508		m = hm;
509		hook = priv->ip;
510	} else if (hook == priv->vjuncomp) {	/* incoming uncompressed pkt */
511		u_char *hdr;
512		u_int hlen;
513
514		/* Are we decompressing? */
515		if (!priv->conf.enableDecomp) {
516			NG_FREE_M(m);
517			NG_FREE_ITEM(item);
518			return (ENXIO);
519		}
520
521		/* Pull up IP+TCP headers */
522		if ((m = ng_vjc_pulluphdrs(m, 1)) == NULL) {
523			NG_FREE_ITEM(item);
524			return (ENOBUFS);
525		}
526
527		/* Run packet through uncompressor */
528		if (sl_uncompress_tcp_core(mtod(m, u_char *),
529		    m->m_len, m->m_pkthdr.len, TYPE_UNCOMPRESSED_TCP,
530		    &priv->slc, &hdr, &hlen) < 0) {
531			NG_FREE_M(m);
532			NG_FREE_ITEM(item);
533			return (EINVAL);
534		}
535		hook = priv->ip;
536	} else if (hook == priv->vjip)	/* incoming regular packet (bypass) */
537		hook = priv->ip;
538	else
539		panic("%s: unknown hook", __FUNCTION__);
540
541	/* Send result back out */
542	NG_FWD_NEW_DATA(error, item, hook, m);
543	return (error);
544}
545
546/*
547 * Shutdown node
548 */
549static int
550ng_vjc_shutdown(node_p node)
551{
552	const priv_p priv = NG_NODE_PRIVATE(node);
553
554	bzero(priv, sizeof(*priv));
555	FREE(priv, M_NETGRAPH);
556	NG_NODE_SET_PRIVATE(node, NULL);
557	NG_NODE_UNREF(node);
558	return (0);
559}
560
561/*
562 * Hook disconnection
563 */
564static int
565ng_vjc_disconnect(hook_p hook)
566{
567	const node_p node = NG_HOOK_NODE(hook);
568	const priv_p priv = NG_NODE_PRIVATE(node);
569
570	/* Zero out hook pointer */
571	if (hook == priv->ip)
572		priv->ip = NULL;
573	else if (hook == priv->vjcomp)
574		priv->vjcomp = NULL;
575	else if (hook == priv->vjuncomp)
576		priv->vjuncomp = NULL;
577	else if (hook == priv->vjip)
578		priv->vjip = NULL;
579	else
580		panic("%s: unknown hook", __FUNCTION__);
581
582	/* Go away if no hooks left */
583	if ((NG_NODE_NUMHOOKS(node) == 0)
584	&& (NG_NODE_IS_VALID(node)))
585		ng_rmnode_self(node);
586	return (0);
587}
588
589/************************************************************************
590			HELPER STUFF
591 ************************************************************************/
592
593/*
594 * Pull up the full IP and TCP headers of a packet. If packet is not
595 * a TCP packet, just pull up the IP header.
596 */
597static struct mbuf *
598ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP)
599{
600	struct ip *ip;
601	struct tcphdr *tcp;
602	int ihlen, thlen;
603
604	if (m->m_len < sizeof(*ip) && (m = m_pullup(m, sizeof(*ip))) == NULL)
605		return (NULL);
606	ip = mtod(m, struct ip *);
607	if (!knownTCP && ip->ip_p != IPPROTO_TCP)
608		return (m);
609	ihlen = ip->ip_hl << 2;
610	if (m->m_len < ihlen + sizeof(*tcp)) {
611		if ((m = m_pullup(m, ihlen + sizeof(*tcp))) == NULL)
612			return (NULL);
613		ip = mtod(m, struct ip *);
614	}
615	tcp = (struct tcphdr *)((u_char *)ip + ihlen);
616	thlen = tcp->th_off << 2;
617	if (m->m_len < ihlen + thlen)
618		m = m_pullup(m, ihlen + thlen);
619	return (m);
620}
621
622