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