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