1/*-
2 * Copyright (c) 2006 Alexander Motin <mav@alkar.net>
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 unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 * $FreeBSD$
28 */
29
30/*
31 * Deflate PPP compression netgraph node type.
32 */
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/kernel.h>
37#include <sys/mbuf.h>
38#include <sys/malloc.h>
39#include <sys/endian.h>
40#include <sys/errno.h>
41#include <sys/syslog.h>
42
43#include <net/zlib.h>
44
45#include <netgraph/ng_message.h>
46#include <netgraph/netgraph.h>
47#include <netgraph/ng_parse.h>
48#include <netgraph/ng_deflate.h>
49
50#include "opt_netgraph.h"
51
52static MALLOC_DEFINE(M_NETGRAPH_DEFLATE, "netgraph_deflate",
53    "netgraph deflate node");
54
55/* DEFLATE header length */
56#define DEFLATE_HDRLEN		2
57
58#define PROT_COMPD		0x00fd
59
60#define DEFLATE_BUF_SIZE	4096
61
62/* Node private data */
63struct ng_deflate_private {
64	struct ng_deflate_config cfg;		/* configuration */
65	u_char		inbuf[DEFLATE_BUF_SIZE];	/* input buffer */
66	u_char		outbuf[DEFLATE_BUF_SIZE];	/* output buffer */
67	z_stream 	cx;			/* compression context */
68	struct ng_deflate_stats stats;		/* statistics */
69	ng_ID_t		ctrlnode;		/* path to controlling node */
70	uint16_t	seqnum;			/* sequence number */
71	u_char		compress;		/* compress/decompress flag */
72};
73typedef struct ng_deflate_private *priv_p;
74
75/* Netgraph node methods */
76static ng_constructor_t	ng_deflate_constructor;
77static ng_rcvmsg_t	ng_deflate_rcvmsg;
78static ng_shutdown_t	ng_deflate_shutdown;
79static ng_newhook_t	ng_deflate_newhook;
80static ng_rcvdata_t	ng_deflate_rcvdata;
81static ng_disconnect_t	ng_deflate_disconnect;
82
83/* Helper functions */
84static void	*z_alloc(void *, u_int items, u_int size);
85static void	z_free(void *, void *ptr);
86static int	ng_deflate_compress(node_p node,
87		    struct mbuf *m, struct mbuf **resultp);
88static int	ng_deflate_decompress(node_p node,
89		    struct mbuf *m, struct mbuf **resultp);
90static void	ng_deflate_reset_req(node_p node);
91
92/* Parse type for struct ng_deflate_config. */
93static const struct ng_parse_struct_field ng_deflate_config_type_fields[]
94	= NG_DEFLATE_CONFIG_INFO;
95static const struct ng_parse_type ng_deflate_config_type = {
96	&ng_parse_struct_type,
97	ng_deflate_config_type_fields
98};
99
100/* Parse type for struct ng_deflate_stat. */
101static const struct ng_parse_struct_field ng_deflate_stats_type_fields[]
102	= NG_DEFLATE_STATS_INFO;
103static const struct ng_parse_type ng_deflate_stat_type = {
104	&ng_parse_struct_type,
105	ng_deflate_stats_type_fields
106};
107
108/* List of commands and how to convert arguments to/from ASCII. */
109static const struct ng_cmdlist ng_deflate_cmds[] = {
110	{
111	  NGM_DEFLATE_COOKIE,
112	  NGM_DEFLATE_CONFIG,
113	  "config",
114	  &ng_deflate_config_type,
115	  NULL
116	},
117	{
118	  NGM_DEFLATE_COOKIE,
119	  NGM_DEFLATE_RESETREQ,
120	  "resetreq",
121	  NULL,
122	  NULL
123	},
124	{
125	  NGM_DEFLATE_COOKIE,
126	  NGM_DEFLATE_GET_STATS,
127	  "getstats",
128	  NULL,
129	  &ng_deflate_stat_type
130	},
131	{
132	  NGM_DEFLATE_COOKIE,
133	  NGM_DEFLATE_CLR_STATS,
134	  "clrstats",
135	  NULL,
136	  NULL
137	},
138	{
139	  NGM_DEFLATE_COOKIE,
140	  NGM_DEFLATE_GETCLR_STATS,
141	  "getclrstats",
142	  NULL,
143	  &ng_deflate_stat_type
144	},
145	{ 0 }
146};
147
148/* Node type descriptor */
149static struct ng_type ng_deflate_typestruct = {
150	.version =	NG_ABI_VERSION,
151	.name =		NG_DEFLATE_NODE_TYPE,
152	.constructor =	ng_deflate_constructor,
153	.rcvmsg =	ng_deflate_rcvmsg,
154	.shutdown =	ng_deflate_shutdown,
155	.newhook =	ng_deflate_newhook,
156	.rcvdata =	ng_deflate_rcvdata,
157	.disconnect =	ng_deflate_disconnect,
158	.cmdlist =	ng_deflate_cmds,
159};
160NETGRAPH_INIT(deflate, &ng_deflate_typestruct);
161
162/* Depend on separate zlib module. */
163MODULE_DEPEND(ng_deflate, zlib, 1, 1, 1);
164
165#define ERROUT(x)	do { error = (x); goto done; } while (0)
166
167/************************************************************************
168			NETGRAPH NODE STUFF
169 ************************************************************************/
170
171/*
172 * Node type constructor
173 */
174static int
175ng_deflate_constructor(node_p node)
176{
177	priv_p priv;
178
179	/* Allocate private structure. */
180	priv = malloc(sizeof(*priv), M_NETGRAPH_DEFLATE, M_WAITOK | M_ZERO);
181
182	NG_NODE_SET_PRIVATE(node, priv);
183
184	/* This node is not thread safe. */
185	NG_NODE_FORCE_WRITER(node);
186
187	/* Done */
188	return (0);
189}
190
191/*
192 * Give our OK for a hook to be added.
193 */
194static int
195ng_deflate_newhook(node_p node, hook_p hook, const char *name)
196{
197	const priv_p priv = NG_NODE_PRIVATE(node);
198
199	if (NG_NODE_NUMHOOKS(node) > 0)
200		return (EINVAL);
201
202	if (strcmp(name, NG_DEFLATE_HOOK_COMP) == 0)
203		priv->compress = 1;
204	else if (strcmp(name, NG_DEFLATE_HOOK_DECOMP) == 0)
205		priv->compress = 0;
206	else
207		return (EINVAL);
208
209	return (0);
210}
211
212/*
213 * Receive a control message
214 */
215static int
216ng_deflate_rcvmsg(node_p node, item_p item, hook_p lasthook)
217{
218	const priv_p priv = NG_NODE_PRIVATE(node);
219	struct ng_mesg *resp = NULL;
220	int error = 0;
221	struct ng_mesg *msg;
222
223	NGI_GET_MSG(item, msg);
224
225	if (msg->header.typecookie != NGM_DEFLATE_COOKIE)
226		ERROUT(EINVAL);
227
228	switch (msg->header.cmd) {
229	case NGM_DEFLATE_CONFIG:
230	    {
231		struct ng_deflate_config *const cfg
232		    = (struct ng_deflate_config *)msg->data;
233
234		/* Check configuration. */
235		if (msg->header.arglen != sizeof(*cfg))
236			ERROUT(EINVAL);
237		if (cfg->enable) {
238		    if (cfg->windowBits < 8 || cfg->windowBits > 15)
239			ERROUT(EINVAL);
240		} else
241		    cfg->windowBits = 0;
242
243		/* Clear previous state. */
244		if (priv->cfg.enable) {
245			if (priv->compress)
246				deflateEnd(&priv->cx);
247			else
248				inflateEnd(&priv->cx);
249			priv->cfg.enable = 0;
250		}
251
252		/* Configuration is OK, reset to it. */
253		priv->cfg = *cfg;
254
255		if (priv->cfg.enable) {
256			priv->cx.next_in = NULL;
257			priv->cx.zalloc = z_alloc;
258			priv->cx.zfree = z_free;
259			int res;
260			if (priv->compress) {
261				if ((res = deflateInit2(&priv->cx,
262				    Z_DEFAULT_COMPRESSION, Z_DEFLATED,
263				    -cfg->windowBits, 8,
264				    Z_DEFAULT_STRATEGY)) != Z_OK) {
265					log(LOG_NOTICE,
266					    "deflateInit2: error %d, %s\n",
267					    res, priv->cx.msg);
268					priv->cfg.enable = 0;
269					ERROUT(ENOMEM);
270				}
271			} else {
272				if ((res = inflateInit2(&priv->cx,
273				    -cfg->windowBits)) != Z_OK) {
274					log(LOG_NOTICE,
275					    "inflateInit2: error %d, %s\n",
276					    res, priv->cx.msg);
277					priv->cfg.enable = 0;
278					ERROUT(ENOMEM);
279				}
280			}
281		}
282
283		/* Initialize other state. */
284		priv->seqnum = 0;
285
286		/* Save return address so we can send reset-req's */
287		priv->ctrlnode = NGI_RETADDR(item);
288		break;
289	    }
290
291	case NGM_DEFLATE_RESETREQ:
292		ng_deflate_reset_req(node);
293		break;
294
295	case NGM_DEFLATE_GET_STATS:
296	case NGM_DEFLATE_CLR_STATS:
297	case NGM_DEFLATE_GETCLR_STATS:
298		/* Create response if requested. */
299		if (msg->header.cmd != NGM_DEFLATE_CLR_STATS) {
300			NG_MKRESPONSE(resp, msg,
301			    sizeof(struct ng_deflate_stats), M_NOWAIT);
302			if (resp == NULL)
303				ERROUT(ENOMEM);
304			bcopy(&priv->stats, resp->data,
305			    sizeof(struct ng_deflate_stats));
306		}
307
308		/* Clear stats if requested. */
309		if (msg->header.cmd != NGM_DEFLATE_GET_STATS)
310			bzero(&priv->stats,
311			    sizeof(struct ng_deflate_stats));
312		break;
313
314	default:
315		error = EINVAL;
316		break;
317	}
318done:
319	NG_RESPOND_MSG(error, node, item, resp);
320	NG_FREE_MSG(msg);
321	return (error);
322}
323
324/*
325 * Receive incoming data on our hook.
326 */
327static int
328ng_deflate_rcvdata(hook_p hook, item_p item)
329{
330	const node_p node = NG_HOOK_NODE(hook);
331	const priv_p priv = NG_NODE_PRIVATE(node);
332	struct mbuf *m, *out;
333	int error;
334
335	if (!priv->cfg.enable) {
336		NG_FREE_ITEM(item);
337		return (ENXIO);
338	}
339
340	NGI_GET_M(item, m);
341	/* Compress */
342	if (priv->compress) {
343		if ((error = ng_deflate_compress(node, m, &out)) != 0) {
344			NG_FREE_ITEM(item);
345			log(LOG_NOTICE, "%s: error: %d\n", __func__, error);
346			return (error);
347		}
348
349	} else { /* Decompress */
350		if ((error = ng_deflate_decompress(node, m, &out)) != 0) {
351			NG_FREE_ITEM(item);
352			log(LOG_NOTICE, "%s: error: %d\n", __func__, error);
353			if (priv->ctrlnode != 0) {
354				struct ng_mesg *msg;
355
356				/* Need to send a reset-request. */
357				NG_MKMESSAGE(msg, NGM_DEFLATE_COOKIE,
358				    NGM_DEFLATE_RESETREQ, 0, M_NOWAIT);
359				if (msg == NULL)
360					return (error);
361				NG_SEND_MSG_ID(error, node, msg,
362					priv->ctrlnode, 0);
363			}
364			return (error);
365		}
366	}
367
368	NG_FWD_NEW_DATA(error, item, hook, out);
369	return (error);
370}
371
372/*
373 * Destroy node.
374 */
375static int
376ng_deflate_shutdown(node_p node)
377{
378	const priv_p priv = NG_NODE_PRIVATE(node);
379
380	/* Take down netgraph node. */
381	if (priv->cfg.enable) {
382	    if (priv->compress)
383		deflateEnd(&priv->cx);
384	    else
385		inflateEnd(&priv->cx);
386	}
387
388	free(priv, M_NETGRAPH_DEFLATE);
389	NG_NODE_SET_PRIVATE(node, NULL);
390	NG_NODE_UNREF(node);		/* let the node escape */
391	return (0);
392}
393
394/*
395 * Hook disconnection
396 */
397static int
398ng_deflate_disconnect(hook_p hook)
399{
400	const node_p node = NG_HOOK_NODE(hook);
401	const priv_p priv = NG_NODE_PRIVATE(node);
402
403	if (priv->cfg.enable) {
404	    if (priv->compress)
405		deflateEnd(&priv->cx);
406	    else
407		inflateEnd(&priv->cx);
408	    priv->cfg.enable = 0;
409	}
410
411	/* Go away if no longer connected. */
412	if ((NG_NODE_NUMHOOKS(node) == 0) && NG_NODE_IS_VALID(node))
413		ng_rmnode_self(node);
414	return (0);
415}
416
417/************************************************************************
418			HELPER STUFF
419 ************************************************************************/
420
421/*
422 * Space allocation and freeing routines for use by zlib routines.
423 */
424
425static void *
426z_alloc(void *notused, u_int items, u_int size)
427{
428
429	return (malloc(items * size, M_NETGRAPH_DEFLATE, M_NOWAIT));
430}
431
432static void
433z_free(void *notused, void *ptr)
434{
435
436	free(ptr, M_NETGRAPH_DEFLATE);
437}
438
439/*
440 * Compress/encrypt a packet and put the result in a new mbuf at *resultp.
441 * The original mbuf is not free'd.
442 */
443static int
444ng_deflate_compress(node_p node, struct mbuf *m, struct mbuf **resultp)
445{
446	const priv_p 	priv = NG_NODE_PRIVATE(node);
447	int 		outlen, inlen;
448	int 		rtn;
449
450	/* Initialize. */
451	*resultp = NULL;
452
453	inlen = m->m_pkthdr.len;
454
455	priv->stats.FramesPlain++;
456	priv->stats.InOctets+=inlen;
457
458	if (inlen > DEFLATE_BUF_SIZE) {
459		priv->stats.Errors++;
460		NG_FREE_M(m);
461		return (ENOMEM);
462	}
463
464	/* We must own the mbuf chain exclusively to modify it. */
465	m = m_unshare(m, M_NOWAIT);
466	if (m == NULL) {
467		priv->stats.Errors++;
468		return (ENOMEM);
469	}
470
471	/* Work with contiguous regions of memory. */
472	m_copydata(m, 0, inlen, (caddr_t)priv->inbuf);
473	outlen = DEFLATE_BUF_SIZE;
474
475	/* Compress "inbuf" into "outbuf". */
476	/* Prepare to compress. */
477	if (priv->inbuf[0] != 0) {
478		priv->cx.next_in = priv->inbuf;
479		priv->cx.avail_in = inlen;
480	} else {
481		priv->cx.next_in = priv->inbuf + 1; /* compress protocol */
482		priv->cx.avail_in = inlen - 1;
483	}
484	priv->cx.next_out = priv->outbuf + 2 + DEFLATE_HDRLEN;
485	priv->cx.avail_out = outlen - 2 - DEFLATE_HDRLEN;
486
487	/* Compress. */
488	rtn = deflate(&priv->cx, Z_PACKET_FLUSH);
489
490	/* Check return value. */
491	if (rtn != Z_OK) {
492		priv->stats.Errors++;
493		log(LOG_NOTICE, "ng_deflate: compression error: %d (%s)\n",
494		    rtn, priv->cx.msg);
495		NG_FREE_M(m);
496		return (EINVAL);
497	}
498
499	/* Calculate resulting size. */
500	outlen -= priv->cx.avail_out;
501
502	/* If we can't compress this packet, send it as-is. */
503	if (outlen > inlen) {
504		/* Return original packet uncompressed. */
505		*resultp = m;
506		priv->stats.FramesUncomp++;
507		priv->stats.OutOctets+=inlen;
508	} else {
509		/* Install header. */
510		be16enc(priv->outbuf, PROT_COMPD);
511		be16enc(priv->outbuf + 2, priv->seqnum);
512
513		/* Return packet in an mbuf. */
514		m_copyback(m, 0, outlen, (caddr_t)priv->outbuf);
515		if (m->m_pkthdr.len < outlen) {
516			m_freem(m);
517			priv->stats.Errors++;
518			return (ENOMEM);
519		} else if (outlen < m->m_pkthdr.len)
520			m_adj(m, outlen - m->m_pkthdr.len);
521		*resultp = m;
522		priv->stats.FramesComp++;
523		priv->stats.OutOctets+=outlen;
524	}
525
526	/* Update sequence number. */
527	priv->seqnum++;
528
529	return (0);
530}
531
532/*
533 * Decompress/decrypt packet and put the result in a new mbuf at *resultp.
534 * The original mbuf is not free'd.
535 */
536static int
537ng_deflate_decompress(node_p node, struct mbuf *m, struct mbuf **resultp)
538{
539	const priv_p 	priv = NG_NODE_PRIVATE(node);
540	int 		outlen, inlen;
541	int 		rtn;
542	uint16_t	proto;
543	int		offset;
544	uint16_t	rseqnum;
545
546	/* Initialize. */
547	*resultp = NULL;
548
549	inlen = m->m_pkthdr.len;
550
551	if (inlen > DEFLATE_BUF_SIZE) {
552		priv->stats.Errors++;
553		NG_FREE_M(m);
554		priv->seqnum = 0;
555		return (ENOMEM);
556	}
557
558	/* We must own the mbuf chain exclusively to modify it. */
559	m = m_unshare(m, M_NOWAIT);
560	if (m == NULL) {
561		priv->stats.Errors++;
562		return (ENOMEM);
563	}
564
565	/* Work with contiguous regions of memory. */
566	m_copydata(m, 0, inlen, (caddr_t)priv->inbuf);
567
568	/* Separate proto. */
569	if ((priv->inbuf[0] & 0x01) != 0) {
570		proto = priv->inbuf[0];
571		offset = 1;
572	} else {
573		proto = be16dec(priv->inbuf);
574		offset = 2;
575	}
576
577	priv->stats.InOctets += inlen;
578
579	/* Packet is compressed, so decompress. */
580	if (proto == PROT_COMPD) {
581		priv->stats.FramesComp++;
582
583		/* Check sequence number. */
584		rseqnum = be16dec(priv->inbuf + offset);
585		offset += 2;
586		if (rseqnum != priv->seqnum) {
587			priv->stats.Errors++;
588			log(LOG_NOTICE, "ng_deflate: wrong sequence: %u "
589			    "instead of %u\n", rseqnum, priv->seqnum);
590			NG_FREE_M(m);
591			priv->seqnum = 0;
592			return (EPIPE);
593		}
594
595		outlen = DEFLATE_BUF_SIZE;
596
597    		/* Decompress "inbuf" into "outbuf". */
598		/* Prepare to decompress. */
599		priv->cx.next_in = priv->inbuf + offset;
600		priv->cx.avail_in = inlen - offset;
601		/* Reserve space for protocol decompression. */
602		priv->cx.next_out = priv->outbuf + 1;
603		priv->cx.avail_out = outlen - 1;
604
605		/* Decompress. */
606		rtn = inflate(&priv->cx, Z_PACKET_FLUSH);
607
608		/* Check return value. */
609		if (rtn != Z_OK && rtn != Z_STREAM_END) {
610			priv->stats.Errors++;
611			NG_FREE_M(m);
612			priv->seqnum = 0;
613			log(LOG_NOTICE, "%s: decompression error: %d (%s)\n",
614			    __func__, rtn, priv->cx.msg);
615
616			switch (rtn) {
617			case Z_MEM_ERROR:
618				return (ENOMEM);
619			case Z_DATA_ERROR:
620				return (EIO);
621			default:
622				return (EINVAL);
623			}
624		}
625
626		/* Calculate resulting size. */
627		outlen -= priv->cx.avail_out;
628
629		/* Decompress protocol. */
630		if ((priv->outbuf[1] & 0x01) != 0) {
631			priv->outbuf[0] = 0;
632			/* Return packet in an mbuf. */
633			m_copyback(m, 0, outlen, (caddr_t)priv->outbuf);
634		} else {
635			outlen--;
636			/* Return packet in an mbuf. */
637			m_copyback(m, 0, outlen, (caddr_t)(priv->outbuf + 1));
638		}
639		if (m->m_pkthdr.len < outlen) {
640			m_freem(m);
641			priv->stats.Errors++;
642			priv->seqnum = 0;
643			return (ENOMEM);
644		} else if (outlen < m->m_pkthdr.len)
645			m_adj(m, outlen - m->m_pkthdr.len);
646		*resultp = m;
647		priv->stats.FramesPlain++;
648		priv->stats.OutOctets+=outlen;
649
650	} else { /* Packet is not compressed, just update dictionary. */
651		priv->stats.FramesUncomp++;
652		if (priv->inbuf[0] == 0) {
653		    priv->cx.next_in = priv->inbuf + 1; /* compress protocol */
654		    priv->cx.avail_in = inlen - 1;
655		} else {
656		    priv->cx.next_in = priv->inbuf;
657		    priv->cx.avail_in = inlen;
658		}
659
660		rtn = inflateIncomp(&priv->cx);
661
662		/* Check return value */
663		if (rtn != Z_OK) {
664			priv->stats.Errors++;
665			log(LOG_NOTICE, "%s: inflateIncomp error: %d (%s)\n",
666			    __func__, rtn, priv->cx.msg);
667			NG_FREE_M(m);
668			priv->seqnum = 0;
669			return (EINVAL);
670		}
671
672		*resultp = m;
673		priv->stats.FramesPlain++;
674		priv->stats.OutOctets += inlen;
675	}
676
677	/* Update sequence number. */
678	priv->seqnum++;
679
680	return (0);
681}
682
683/*
684 * The peer has sent us a CCP ResetRequest, so reset our transmit state.
685 */
686static void
687ng_deflate_reset_req(node_p node)
688{
689	const priv_p priv = NG_NODE_PRIVATE(node);
690
691	priv->seqnum = 0;
692	if (priv->cfg.enable) {
693	    if (priv->compress)
694		deflateReset(&priv->cx);
695	    else
696		inflateReset(&priv->cx);
697	}
698}
699
700