1/*	$NetBSD: ipcomp_output.c,v 1.29 2009/03/18 16:00:23 cegger Exp $	*/
2/*	$KAME: ipcomp_output.c,v 1.24 2001/07/26 06:53:18 jinmei Exp $	*/
3
4/*
5 * Copyright (C) 1999 WIDE Project.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following 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 * 3. Neither the name of the project nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33/*
34 * RFC2393 IP payload compression protocol (IPComp).
35 */
36
37#include <sys/cdefs.h>
38__KERNEL_RCSID(0, "$NetBSD: ipcomp_output.c,v 1.29 2009/03/18 16:00:23 cegger Exp $");
39
40#include "opt_inet.h"
41
42#include <sys/param.h>
43#include <sys/systm.h>
44#include <sys/malloc.h>
45#include <sys/mbuf.h>
46#include <sys/domain.h>
47#include <sys/protosw.h>
48#include <sys/socket.h>
49#include <sys/errno.h>
50#include <sys/time.h>
51#include <sys/kernel.h>
52#include <sys/syslog.h>
53
54#include <net/if.h>
55#include <net/route.h>
56#include <net/netisr.h>
57#include <net/zlib.h>
58#include <sys/cpu.h>
59
60#include <netinet/in.h>
61#include <netinet/in_systm.h>
62#include <netinet/in_var.h>
63#include <netinet/ip.h>
64#include <netinet/ip_var.h>
65#include <netinet/ip_ecn.h>
66
67#ifdef INET6
68#include <netinet/ip6.h>
69#include <netinet6/ip6_var.h>
70#endif
71#include <netinet6/ipcomp.h>
72
73#include <netinet6/ipsec.h>
74#include <netinet6/ipsec_private.h>
75#include <netkey/key.h>
76#include <netkey/keydb.h>
77
78#include <net/net_osdep.h>
79
80static int ipcomp_output(struct mbuf *, u_char *, struct mbuf *,
81	struct ipsecrequest *, int);
82
83/*
84 * Modify the packet so that the payload is compressed.
85 * The mbuf (m) must start with IPv4 or IPv6 header.
86 * On failure, free the given mbuf and return non-zero.
87 *
88 * on invocation:
89 *	m   nexthdrp md
90 *	v   v        v
91 *	IP ......... payload
92 * during the encryption:
93 *	m   nexthdrp mprev md
94 *	v   v        v     v
95 *	IP ............... ipcomp payload
96 *	                   <-----><----->
97 *	                   complen  plen
98 *	<-> hlen
99 *	<-----------------> compoff
100 */
101static int
102ipcomp_output(struct mbuf *m, u_char *nexthdrp, struct mbuf *md,
103	struct ipsecrequest *isr, int af)
104{
105	struct mbuf *n;
106	struct mbuf *md0;
107	struct mbuf *mcopy;
108	struct mbuf *mprev;
109	struct ipcomp *ipcomp;
110	struct secasvar *sav = isr->sav;
111	const struct ipcomp_algorithm *algo;
112	u_int16_t cpi;		/* host order */
113	size_t plen0, plen;	/* payload length to be compressed */
114	size_t compoff;
115	int afnumber;
116	int error = 0;
117	percpu_t *stat;
118
119	switch (af) {
120#ifdef INET
121	case AF_INET:
122		afnumber = 4;
123		stat = ipsecstat_percpu;
124		break;
125#endif
126#ifdef INET6
127	case AF_INET6:
128		afnumber = 6;
129		stat = ipsec6stat_percpu;
130		break;
131#endif
132	default:
133		ipseclog((LOG_ERR, "ipcomp_output: unsupported af %d\n", af));
134		return 0;	/* no change at all */
135	}
136
137	/* grab parameters */
138	algo = ipcomp_algorithm_lookup(sav->alg_enc);
139	if ((ntohl(sav->spi) & ~0xffff) != 0 || !algo) {
140		_NET_STATINC(stat, IPSEC_STAT_OUT_INVAL);
141		error = EINVAL;
142		goto fail1;
143	}
144	if ((sav->flags & SADB_X_EXT_RAWCPI) == 0)
145		cpi = sav->alg_enc;
146	else
147		cpi = ntohl(sav->spi) & 0xffff;
148
149	/* compute original payload length */
150	plen = 0;
151	for (n = md; n; n = n->m_next)
152		plen += n->m_len;
153
154	/* if the payload is short enough, we don't need to compress */
155	if (plen < algo->minplen)
156		return 0;
157
158	/*
159	 * retain the original packet for two purposes:
160	 * (1) we need to backout our changes when compression is not necessary.
161	 * (2) byte lifetime computation should use the original packet.
162	 *     see RFC2401 page 23.
163	 * compromise two m_copym().  we will be going through every byte of
164	 * the payload during compression process anyways.
165	 */
166	mcopy = m_copym(m, 0, M_COPYALL, M_NOWAIT);
167	if (mcopy == NULL) {
168		error = ENOBUFS;
169		goto fail1;
170	}
171	md0 = m_copym(md, 0, M_COPYALL, M_NOWAIT);
172	if (md0 == NULL) {
173		error = ENOBUFS;
174		goto fail2;
175	}
176	plen0 = plen;
177
178	/* make the packet over-writable */
179	for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next)
180		;
181	if (mprev == NULL || mprev->m_next != md) {
182		ipseclog((LOG_DEBUG, "ipcomp%d_output: md is not in chain\n",
183		    afnumber));
184		_NET_STATINC(stat, IPSEC_STAT_OUT_INVAL);
185		error = EINVAL;
186		goto fail3;
187	}
188	mprev->m_next = NULL;
189	if ((md = ipsec_copypkt(md)) == NULL) {
190		error = ENOBUFS;
191		goto fail3;
192	}
193	mprev->m_next = md;
194
195	/* compress data part */
196	if ((*algo->compress)(m, md, &plen) || mprev->m_next == NULL) {
197		ipseclog((LOG_ERR, "packet compression failure\n"));
198		m = NULL;
199		_NET_STATINC(stat, IPSEC_STAT_OUT_INVAL);
200		error = EINVAL;
201		goto fail3;
202	}
203	_NET_STATINC(stat, IPSEC_STAT_OUT_COMPHIST + sav->alg_enc);
204	md = mprev->m_next;
205
206	/*
207	 * if the packet became bigger, meaningless to use IPComp.
208	 * we've only wasted our CPU time.
209	 */
210	if (plen0 < plen) {
211		m_freem(md);
212		m_freem(mcopy);
213		mprev->m_next = md0;
214		return 0;
215	}
216
217	/*
218	 * no need to backout change beyond here.
219	 */
220	m_freem(md0);
221	md0 = NULL;
222
223	m->m_pkthdr.len -= plen0;
224	m->m_pkthdr.len += plen;
225
226    {
227	/*
228	 * insert IPComp header.
229	 */
230#ifdef INET
231	struct ip *ip = NULL;
232#endif
233#ifdef INET6
234	struct ip6_hdr *ip6 = NULL;
235#endif
236	size_t hlen = 0;	/* ip header len */
237	size_t complen = sizeof(struct ipcomp);
238
239	switch (af) {
240#ifdef INET
241	case AF_INET:
242		ip = mtod(m, struct ip *);
243		hlen = ip->ip_hl << 2;
244		break;
245#endif
246#ifdef INET6
247	case AF_INET6:
248		ip6 = mtod(m, struct ip6_hdr *);
249		hlen = sizeof(*ip6);
250		break;
251#endif
252	}
253
254	compoff = m->m_pkthdr.len - plen;
255
256	/*
257	 * grow the mbuf to accommodate ipcomp header.
258	 * before: IP ... payload
259	 * after:  IP ... ipcomp payload
260	 */
261	if (M_LEADINGSPACE(md) < complen) {
262		MGET(n, M_DONTWAIT, MT_DATA);
263		if (!n) {
264			error = ENOBUFS;
265			goto fail2;
266		}
267		n->m_len = complen;
268		mprev->m_next = n;
269		n->m_next = md;
270		m->m_pkthdr.len += complen;
271		ipcomp = mtod(n, struct ipcomp *);
272	} else {
273		md->m_len += complen;
274		md->m_data -= complen;
275		m->m_pkthdr.len += complen;
276		ipcomp = mtod(md, struct ipcomp *);
277	}
278
279	memset(ipcomp, 0, sizeof(*ipcomp));
280	ipcomp->comp_nxt = *nexthdrp;
281	*nexthdrp = IPPROTO_IPCOMP;
282	ipcomp->comp_cpi = htons(cpi);
283	switch (af) {
284#ifdef INET
285	case AF_INET:
286		if (compoff + complen + plen < IP_MAXPACKET)
287			ip->ip_len = htons(compoff + complen + plen);
288		else {
289			ipseclog((LOG_ERR,
290			    "IPv4 ESP output: size exceeds limit\n"));
291			IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
292			error = EMSGSIZE;
293			goto fail2;
294		}
295		break;
296#endif
297#ifdef INET6
298	case AF_INET6:
299		/* total packet length will be computed in ip6_output() */
300		break;
301#endif
302	}
303    }
304
305	if (!m) {
306		ipseclog((LOG_DEBUG,
307		    "NULL mbuf after compression in ipcomp%d_output",
308		    afnumber));
309		_NET_STATINC(stat, IPSEC_STAT_OUT_INVAL);
310	}
311		_NET_STATINC(stat, IPSEC_STAT_OUT_SUCCESS);
312
313	/* compute byte lifetime against original packet */
314	key_sa_recordxfer(sav, mcopy);
315	m_freem(mcopy);
316
317	return 0;
318
319fail3:
320	m_freem(md0);
321fail2:
322	m_freem(mcopy);
323fail1:
324	m_freem(m);
325#if 1
326	return error;
327#else
328	panic("something bad in ipcomp_output");
329#endif
330}
331
332#ifdef INET
333int
334ipcomp4_output(struct mbuf *m, struct ipsecrequest *isr)
335{
336	struct ip *ip;
337	if (m->m_len < sizeof(struct ip)) {
338		ipseclog((LOG_DEBUG, "ipcomp4_output: first mbuf too short\n"));
339		IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
340		m_freem(m);
341		return EINVAL;
342	}
343	ip = mtod(m, struct ip *);
344	/* XXX assumes that m->m_next points to payload */
345	return ipcomp_output(m, &ip->ip_p, m->m_next, isr, AF_INET);
346}
347#endif /* INET */
348
349#ifdef INET6
350int
351ipcomp6_output(struct mbuf *m, u_char *nexthdrp, struct mbuf *md,
352	struct ipsecrequest *isr)
353{
354	if (m->m_len < sizeof(struct ip6_hdr)) {
355		ipseclog((LOG_DEBUG, "ipcomp6_output: first mbuf too short\n"));
356		IPSEC6_STATINC(IPSEC_STAT_OUT_INVAL);
357		m_freem(m);
358		return EINVAL;
359	}
360	return ipcomp_output(m, nexthdrp, md, isr, AF_INET6);
361}
362#endif /* INET6 */
363