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