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(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 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 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 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 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 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 234 static int ipcomp_output_push(struct sk_buff *skb) 235 { 236 skb_push(skb, -skb_network_offset(skb)); 237 return 0; 238 } 239 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 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 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 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 306 static void ipcomp_free_data(struct ipcomp_data *ipcd) 307 { 308 crypto_free_acomp(ipcd->tfm); 309 } 310 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 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