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