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