File Index
Files and directories
Sign in
FILE

xfrm_ipcomp.c

File preview with syntax highlighting.

linux-6.19.14/net/xfrm/xfrm_ipcomp.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * IP Payload Compression Protocol (IPComp) - RFC3173.
  4 *
  5 * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
  6 * Copyright (c) 2003-2025 Herbert Xu <herbert@gondor.apana.org.au>
  7 *
  8 * Todo:
  9 *   - Tunable compression parameters.
 10 *   - Compression stats.
 11 *   - Adaptive compression.
 12 */
 13
 14#include <crypto/acompress.h>
 15#include <linux/err.h>
 16#include <linux/module.h>
 17#include <linux/skbuff_ref.h>
 18#include <linux/slab.h>
 19#include <net/ipcomp.h>
 20#include <net/xfrm.h>
 21
 22#define IPCOMP_SCRATCH_SIZE 65400
 23
 24struct ipcomp_skb_cb {
 25	struct xfrm_skb_cb xfrm;
 26	struct acomp_req *req;
 27};
 28
 29struct ipcomp_data {
 30	u16 threshold;
 31	struct crypto_acomp *tfm;
 32};
 33
 34struct ipcomp_req_extra {
 35	struct xfrm_state *x;
 36	struct scatterlist sg[];
 37};
 38
 39static inline struct ipcomp_skb_cb *ipcomp_cb(struct sk_buff *skb)
 40{
 41	struct ipcomp_skb_cb *cb = (void *)skb->cb;
 42
 43	BUILD_BUG_ON(sizeof(*cb) > sizeof(skb->cb));
 44	return cb;
 45}
 46
 47static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
 48{
 49	struct acomp_req *req = ipcomp_cb(skb)->req;
 50	struct ipcomp_req_extra *extra;
 51	struct scatterlist *dsg;
 52	int len, dlen;
 53
 54	if (unlikely(err))
 55		goto out_free_req;
 56
 57	extra = acomp_request_extra(req);
 58	dsg = extra->sg;
 59	dlen = req->dlen;
 60
 61	pskb_trim_unique(skb, 0);
 62	__skb_put(skb, hlen);
 63
 64	/* Only update truesize on input. */
 65	if (!hlen)
 66		skb->truesize += dlen;
 67	skb->data_len = dlen;
 68	skb->len += dlen;
 69
 70	do {
 71		skb_frag_t *frag;
 72		struct page *page;
 73
 74		frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;
 75		page = sg_page(dsg);
 76		dsg = sg_next(dsg);
 77
 78		len = PAGE_SIZE;
 79		if (dlen < len)
 80			len = dlen;
 81
 82		skb_frag_fill_page_desc(frag, page, 0, len);
 83
 84		skb_shinfo(skb)->nr_frags++;
 85	} while ((dlen -= len));
 86
 87	for (; dsg; dsg = sg_next(dsg))
 88		__free_page(sg_page(dsg));
 89
 90out_free_req:
 91	acomp_request_free(req);
 92	return err;
 93}
 94
 95static int ipcomp_input_done2(struct sk_buff *skb, int err)
 96{
 97	struct ip_comp_hdr *ipch = ip_comp_hdr(skb);
 98	const int plen = skb->len;
 99
100	skb->transport_header = skb->network_header + sizeof(*ipch);
101
102	return ipcomp_post_acomp(skb, err, 0) ?:
103	       skb->len < (plen + sizeof(ip_comp_hdr)) ? -EINVAL :
104	       ipch->nexthdr;
105}
106
107static void ipcomp_input_done(void *data, int err)
108{
109	struct sk_buff *skb = data;
110
111	xfrm_input_resume(skb, ipcomp_input_done2(skb, err));
112}
113
114static struct acomp_req *ipcomp_setup_req(struct xfrm_state *x,
115					  struct sk_buff *skb, int minhead,
116					  int dlen)
117{
118	const int dnfrags = min(MAX_SKB_FRAGS, 16);
119	struct ipcomp_data *ipcd = x->data;
120	struct ipcomp_req_extra *extra;
121	struct scatterlist *sg, *dsg;
122	const int plen = skb->len;
123	struct crypto_acomp *tfm;
124	struct acomp_req *req;
125	int nfrags;
126	int total;
127	int err;
128	int i;
129
130	ipcomp_cb(skb)->req = NULL;
131
132	do {
133		struct sk_buff *trailer;
134
135		if (skb->len > PAGE_SIZE) {
136			if (skb_linearize_cow(skb))
137				return ERR_PTR(-ENOMEM);
138			nfrags = 1;
139			break;
140		}
141
142		if (!skb_cloned(skb) && skb_headlen(skb) >= minhead) {
143			if (!skb_is_nonlinear(skb)) {
144				nfrags = 1;
145				break;
146			} else if (!skb_has_frag_list(skb)) {
147				nfrags = skb_shinfo(skb)->nr_frags;
148				nfrags++;
149				break;
150			}
151		}
152
153		nfrags = skb_cow_data(skb, skb_headlen(skb) < minhead ?
154					   minhead - skb_headlen(skb) : 0,
155				      &trailer);
156		if (nfrags < 0)
157			return ERR_PTR(nfrags);
158	} while (0);
159
160	tfm = ipcd->tfm;
161	req = acomp_request_alloc_extra(
162		tfm, sizeof(*extra) + sizeof(*sg) * (nfrags + dnfrags),
163		GFP_ATOMIC);
164	ipcomp_cb(skb)->req = req;
165	if (!req)
166		return ERR_PTR(-ENOMEM);
167
168	extra = acomp_request_extra(req);
169	extra->x = x;
170
171	dsg = extra->sg;
172	sg = dsg + dnfrags;
173	sg_init_table(sg, nfrags);
174	err = skb_to_sgvec(skb, sg, 0, plen);
175	if (unlikely(err < 0))
176		return ERR_PTR(err);
177
178	sg_init_table(dsg, dnfrags);
179	total = 0;
180	for (i = 0; i < dnfrags && total < dlen; i++) {
181		struct page *page;
182
183		page = alloc_page(GFP_ATOMIC);
184		if (!page)
185			break;
186		sg_set_page(dsg + i, page, PAGE_SIZE, 0);
187		total += PAGE_SIZE;
188	}
189	if (!i)
190		return ERR_PTR(-ENOMEM);
191	sg_mark_end(dsg + i - 1);
192	dlen = min(dlen, total);
193
194	acomp_request_set_params(req, sg, dsg, plen, dlen);
195
196	return req;
197}
198
199static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
200{
201	struct acomp_req *req;
202	int err;
203
204	req = ipcomp_setup_req(x, skb, 0, IPCOMP_SCRATCH_SIZE);
205	err = PTR_ERR(req);
206	if (IS_ERR(req))
207		goto out;
208
209	acomp_request_set_callback(req, 0, ipcomp_input_done, skb);
210	err = crypto_acomp_decompress(req);
211	if (err == -EINPROGRESS)
212		return err;
213
214out:
215	return ipcomp_input_done2(skb, err);
216}
217
218int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb)
219{
220	struct ip_comp_hdr *ipch __maybe_unused;
221
222	if (!pskb_may_pull(skb, sizeof(*ipch)))
223		return -EINVAL;
224
225	skb->ip_summed = CHECKSUM_NONE;
226
227	/* Remove ipcomp header and decompress original payload */
228	__skb_pull(skb, sizeof(*ipch));
229
230	return ipcomp_decompress(x, skb);
231}
232EXPORT_SYMBOL_GPL(ipcomp_input);
233
234static int ipcomp_output_push(struct sk_buff *skb)
235{
236	skb_push(skb, -skb_network_offset(skb));
237	return 0;
238}
239
240static int ipcomp_output_done2(struct xfrm_state *x, struct sk_buff *skb,
241			       int err)
242{
243	struct ip_comp_hdr *ipch;
244
245	err = ipcomp_post_acomp(skb, err, sizeof(*ipch));
246	if (err)
247		goto out_ok;
248
249	/* Install ipcomp header, convert into ipcomp datagram. */
250	ipch = ip_comp_hdr(skb);
251	ipch->nexthdr = *skb_mac_header(skb);
252	ipch->flags = 0;
253	ipch->cpi = htons((u16 )ntohl(x->id.spi));
254	*skb_mac_header(skb) = IPPROTO_COMP;
255out_ok:
256	return ipcomp_output_push(skb);
257}
258
259static void ipcomp_output_done(void *data, int err)
260{
261	struct ipcomp_req_extra *extra;
262	struct sk_buff *skb = data;
263	struct acomp_req *req;
264
265	req = ipcomp_cb(skb)->req;
266	extra = acomp_request_extra(req);
267
268	xfrm_output_resume(skb_to_full_sk(skb), skb,
269			   ipcomp_output_done2(extra->x, skb, err));
270}
271
272static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
273{
274	struct ip_comp_hdr *ipch __maybe_unused;
275	struct acomp_req *req;
276	int err;
277
278	req = ipcomp_setup_req(x, skb, sizeof(*ipch),
279			       skb->len - sizeof(*ipch));
280	err = PTR_ERR(req);
281	if (IS_ERR(req))
282		goto out;
283
284	acomp_request_set_callback(req, 0, ipcomp_output_done, skb);
285	err = crypto_acomp_compress(req);
286	if (err == -EINPROGRESS)
287		return err;
288
289out:
290	return ipcomp_output_done2(x, skb, err);
291}
292
293int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
294{
295	struct ipcomp_data *ipcd = x->data;
296
297	if (skb->len < ipcd->threshold) {
298		/* Don't bother compressing */
299		return ipcomp_output_push(skb);
300	}
301
302	return ipcomp_compress(x, skb);
303}
304EXPORT_SYMBOL_GPL(ipcomp_output);
305
306static void ipcomp_free_data(struct ipcomp_data *ipcd)
307{
308	crypto_free_acomp(ipcd->tfm);
309}
310
311void ipcomp_destroy(struct xfrm_state *x)
312{
313	struct ipcomp_data *ipcd = x->data;
314	if (!ipcd)
315		return;
316	ipcomp_free_data(ipcd);
317	kfree(ipcd);
318}
319EXPORT_SYMBOL_GPL(ipcomp_destroy);
320
321int ipcomp_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
322{
323	int err;
324	struct ipcomp_data *ipcd;
325	struct xfrm_algo_desc *calg_desc;
326
327	err = -EINVAL;
328	if (!x->calg) {
329		NL_SET_ERR_MSG(extack, "Missing required compression algorithm");
330		goto out;
331	}
332
333	if (x->encap) {
334		NL_SET_ERR_MSG(extack, "IPComp is not compatible with encapsulation");
335		goto out;
336	}
337
338	err = -ENOMEM;
339	ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
340	if (!ipcd)
341		goto out;
342
343	ipcd->tfm = crypto_alloc_acomp(x->calg->alg_name, 0, 0);
344	if (IS_ERR(ipcd->tfm))
345		goto error;
346
347	calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
348	BUG_ON(!calg_desc);
349	ipcd->threshold = calg_desc->uinfo.comp.threshold;
350	x->data = ipcd;
351	err = 0;
352out:
353	return err;
354
355error:
356	ipcomp_free_data(ipcd);
357	kfree(ipcd);
358	goto out;
359}
360EXPORT_SYMBOL_GPL(ipcomp_init_state);
361
362MODULE_LICENSE("GPL");
363MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
364MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
~/ Read-only file preview.