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