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 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 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 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 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 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 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 238 static int ipcomp_output_push(struct sk_buff *skb) 239 { 240 skb_push(skb, -skb_network_offset(skb)); 241 return 0; 242 } 243 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 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 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 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 310 static void ipcomp_free_data(struct ipcomp_data *ipcd) 311 { 312 crypto_free_acomp(ipcd->tfm); 313 } 314 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 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