xref: /linux/net/xfrm/xfrm_ipcomp.c (revision ef2233850edc4cc0d5fc6136fcdb004a1ddfa7db)
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(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 
90 out_free_req:
91 	acomp_request_free(req);
92 	return err;
93 }
94 
ipcomp_input_done2(struct sk_buff * skb,int err)95 static 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_reset_transport_header(skb);
101 
102 	return ipcomp_post_acomp(skb, err, 0) ?:
103 	       skb->len < (plen + sizeof(ip_comp_hdr)) ? -EINVAL :
104 	       ipch->nexthdr;
105 }
106 
ipcomp_input_done(void * data,int err)107 static 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 
ipcomp_setup_req(struct xfrm_state * x,struct sk_buff * skb,int minhead,int dlen)114 static 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 
ipcomp_decompress(struct xfrm_state * x,struct sk_buff * skb)199 static 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 
214 out:
215 	return ipcomp_input_done2(skb, err);
216 }
217 
ipcomp_input(struct xfrm_state * x,struct sk_buff * skb)218 int 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 }
232 EXPORT_SYMBOL_GPL(ipcomp_input);
233 
ipcomp_output_push(struct sk_buff * skb)234 static int ipcomp_output_push(struct sk_buff *skb)
235 {
236 	skb_push(skb, -skb_network_offset(skb));
237 	return 0;
238 }
239 
ipcomp_output_done2(struct xfrm_state * x,struct sk_buff * skb,int err)240 static 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;
255 out_ok:
256 	return ipcomp_output_push(skb);
257 }
258 
ipcomp_output_done(void * data,int err)259 static 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 
ipcomp_compress(struct xfrm_state * x,struct sk_buff * skb)272 static 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 
289 out:
290 	return ipcomp_output_done2(x, skb, err);
291 }
292 
ipcomp_output(struct xfrm_state * x,struct sk_buff * skb)293 int 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 }
304 EXPORT_SYMBOL_GPL(ipcomp_output);
305 
ipcomp_free_data(struct ipcomp_data * ipcd)306 static void ipcomp_free_data(struct ipcomp_data *ipcd)
307 {
308 	crypto_free_acomp(ipcd->tfm);
309 }
310 
ipcomp_destroy(struct xfrm_state * x)311 void ipcomp_destroy(struct xfrm_state *x)
312 {
313 	struct ipcomp_data *ipcd = x->data;
314 	if (!ipcd)
315 		return;
316 	xfrm_state_delete_tunnel(x);
317 	ipcomp_free_data(ipcd);
318 	kfree(ipcd);
319 }
320 EXPORT_SYMBOL_GPL(ipcomp_destroy);
321 
ipcomp_init_state(struct xfrm_state * x,struct netlink_ext_ack * extack)322 int ipcomp_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
323 {
324 	int err;
325 	struct ipcomp_data *ipcd;
326 	struct xfrm_algo_desc *calg_desc;
327 
328 	err = -EINVAL;
329 	if (!x->calg) {
330 		NL_SET_ERR_MSG(extack, "Missing required compression algorithm");
331 		goto out;
332 	}
333 
334 	if (x->encap) {
335 		NL_SET_ERR_MSG(extack, "IPComp is not compatible with encapsulation");
336 		goto out;
337 	}
338 
339 	err = -ENOMEM;
340 	ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
341 	if (!ipcd)
342 		goto out;
343 
344 	ipcd->tfm = crypto_alloc_acomp(x->calg->alg_name, 0, 0);
345 	if (IS_ERR(ipcd->tfm))
346 		goto error;
347 
348 	calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
349 	BUG_ON(!calg_desc);
350 	ipcd->threshold = calg_desc->uinfo.comp.threshold;
351 	x->data = ipcd;
352 	err = 0;
353 out:
354 	return err;
355 
356 error:
357 	ipcomp_free_data(ipcd);
358 	kfree(ipcd);
359 	goto out;
360 }
361 EXPORT_SYMBOL_GPL(ipcomp_init_state);
362 
363 MODULE_LICENSE("GPL");
364 MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
365 MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
366