Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * IP Payload Compression Protocol (IPComp) - RFC3173.
0004  *
0005  * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
0006  * Copyright (c) 2003-2008 Herbert Xu <herbert@gondor.apana.org.au>
0007  *
0008  * Todo:
0009  *   - Tunable compression parameters.
0010  *   - Compression stats.
0011  *   - Adaptive compression.
0012  */
0013 
0014 #include <linux/crypto.h>
0015 #include <linux/err.h>
0016 #include <linux/list.h>
0017 #include <linux/module.h>
0018 #include <linux/mutex.h>
0019 #include <linux/percpu.h>
0020 #include <linux/slab.h>
0021 #include <linux/smp.h>
0022 #include <linux/vmalloc.h>
0023 #include <net/ip.h>
0024 #include <net/ipcomp.h>
0025 #include <net/xfrm.h>
0026 
0027 struct ipcomp_tfms {
0028     struct list_head list;
0029     struct crypto_comp * __percpu *tfms;
0030     int users;
0031 };
0032 
0033 static DEFINE_MUTEX(ipcomp_resource_mutex);
0034 static void * __percpu *ipcomp_scratches;
0035 static int ipcomp_scratch_users;
0036 static LIST_HEAD(ipcomp_tfms_list);
0037 
0038 static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
0039 {
0040     struct ipcomp_data *ipcd = x->data;
0041     const int plen = skb->len;
0042     int dlen = IPCOMP_SCRATCH_SIZE;
0043     const u8 *start = skb->data;
0044     u8 *scratch = *this_cpu_ptr(ipcomp_scratches);
0045     struct crypto_comp *tfm = *this_cpu_ptr(ipcd->tfms);
0046     int err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
0047     int len;
0048 
0049     if (err)
0050         return err;
0051 
0052     if (dlen < (plen + sizeof(struct ip_comp_hdr)))
0053         return -EINVAL;
0054 
0055     len = dlen - plen;
0056     if (len > skb_tailroom(skb))
0057         len = skb_tailroom(skb);
0058 
0059     __skb_put(skb, len);
0060 
0061     len += plen;
0062     skb_copy_to_linear_data(skb, scratch, len);
0063 
0064     while ((scratch += len, dlen -= len) > 0) {
0065         skb_frag_t *frag;
0066         struct page *page;
0067 
0068         if (WARN_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS))
0069             return -EMSGSIZE;
0070 
0071         frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;
0072         page = alloc_page(GFP_ATOMIC);
0073 
0074         if (!page)
0075             return -ENOMEM;
0076 
0077         __skb_frag_set_page(frag, page);
0078 
0079         len = PAGE_SIZE;
0080         if (dlen < len)
0081             len = dlen;
0082 
0083         skb_frag_off_set(frag, 0);
0084         skb_frag_size_set(frag, len);
0085         memcpy(skb_frag_address(frag), scratch, len);
0086 
0087         skb->truesize += len;
0088         skb->data_len += len;
0089         skb->len += len;
0090 
0091         skb_shinfo(skb)->nr_frags++;
0092     }
0093 
0094     return 0;
0095 }
0096 
0097 int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb)
0098 {
0099     int nexthdr;
0100     int err = -ENOMEM;
0101     struct ip_comp_hdr *ipch;
0102 
0103     if (skb_linearize_cow(skb))
0104         goto out;
0105 
0106     skb->ip_summed = CHECKSUM_NONE;
0107 
0108     /* Remove ipcomp header and decompress original payload */
0109     ipch = (void *)skb->data;
0110     nexthdr = ipch->nexthdr;
0111 
0112     skb->transport_header = skb->network_header + sizeof(*ipch);
0113     __skb_pull(skb, sizeof(*ipch));
0114     err = ipcomp_decompress(x, skb);
0115     if (err)
0116         goto out;
0117 
0118     err = nexthdr;
0119 
0120 out:
0121     return err;
0122 }
0123 EXPORT_SYMBOL_GPL(ipcomp_input);
0124 
0125 static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
0126 {
0127     struct ipcomp_data *ipcd = x->data;
0128     const int plen = skb->len;
0129     int dlen = IPCOMP_SCRATCH_SIZE;
0130     u8 *start = skb->data;
0131     struct crypto_comp *tfm;
0132     u8 *scratch;
0133     int err;
0134 
0135     local_bh_disable();
0136     scratch = *this_cpu_ptr(ipcomp_scratches);
0137     tfm = *this_cpu_ptr(ipcd->tfms);
0138     err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
0139     if (err)
0140         goto out;
0141 
0142     if ((dlen + sizeof(struct ip_comp_hdr)) >= plen) {
0143         err = -EMSGSIZE;
0144         goto out;
0145     }
0146 
0147     memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
0148     local_bh_enable();
0149 
0150     pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr));
0151     return 0;
0152 
0153 out:
0154     local_bh_enable();
0155     return err;
0156 }
0157 
0158 int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
0159 {
0160     int err;
0161     struct ip_comp_hdr *ipch;
0162     struct ipcomp_data *ipcd = x->data;
0163 
0164     if (skb->len < ipcd->threshold) {
0165         /* Don't bother compressing */
0166         goto out_ok;
0167     }
0168 
0169     if (skb_linearize_cow(skb))
0170         goto out_ok;
0171 
0172     err = ipcomp_compress(x, skb);
0173 
0174     if (err) {
0175         goto out_ok;
0176     }
0177 
0178     /* Install ipcomp header, convert into ipcomp datagram. */
0179     ipch = ip_comp_hdr(skb);
0180     ipch->nexthdr = *skb_mac_header(skb);
0181     ipch->flags = 0;
0182     ipch->cpi = htons((u16 )ntohl(x->id.spi));
0183     *skb_mac_header(skb) = IPPROTO_COMP;
0184 out_ok:
0185     skb_push(skb, -skb_network_offset(skb));
0186     return 0;
0187 }
0188 EXPORT_SYMBOL_GPL(ipcomp_output);
0189 
0190 static void ipcomp_free_scratches(void)
0191 {
0192     int i;
0193     void * __percpu *scratches;
0194 
0195     if (--ipcomp_scratch_users)
0196         return;
0197 
0198     scratches = ipcomp_scratches;
0199     if (!scratches)
0200         return;
0201 
0202     for_each_possible_cpu(i)
0203         vfree(*per_cpu_ptr(scratches, i));
0204 
0205     free_percpu(scratches);
0206 }
0207 
0208 static void * __percpu *ipcomp_alloc_scratches(void)
0209 {
0210     void * __percpu *scratches;
0211     int i;
0212 
0213     if (ipcomp_scratch_users++)
0214         return ipcomp_scratches;
0215 
0216     scratches = alloc_percpu(void *);
0217     if (!scratches)
0218         return NULL;
0219 
0220     ipcomp_scratches = scratches;
0221 
0222     for_each_possible_cpu(i) {
0223         void *scratch;
0224 
0225         scratch = vmalloc_node(IPCOMP_SCRATCH_SIZE, cpu_to_node(i));
0226         if (!scratch)
0227             return NULL;
0228         *per_cpu_ptr(scratches, i) = scratch;
0229     }
0230 
0231     return scratches;
0232 }
0233 
0234 static void ipcomp_free_tfms(struct crypto_comp * __percpu *tfms)
0235 {
0236     struct ipcomp_tfms *pos;
0237     int cpu;
0238 
0239     list_for_each_entry(pos, &ipcomp_tfms_list, list) {
0240         if (pos->tfms == tfms)
0241             break;
0242     }
0243 
0244     WARN_ON(list_entry_is_head(pos, &ipcomp_tfms_list, list));
0245 
0246     if (--pos->users)
0247         return;
0248 
0249     list_del(&pos->list);
0250     kfree(pos);
0251 
0252     if (!tfms)
0253         return;
0254 
0255     for_each_possible_cpu(cpu) {
0256         struct crypto_comp *tfm = *per_cpu_ptr(tfms, cpu);
0257         crypto_free_comp(tfm);
0258     }
0259     free_percpu(tfms);
0260 }
0261 
0262 static struct crypto_comp * __percpu *ipcomp_alloc_tfms(const char *alg_name)
0263 {
0264     struct ipcomp_tfms *pos;
0265     struct crypto_comp * __percpu *tfms;
0266     int cpu;
0267 
0268 
0269     list_for_each_entry(pos, &ipcomp_tfms_list, list) {
0270         struct crypto_comp *tfm;
0271 
0272         /* This can be any valid CPU ID so we don't need locking. */
0273         tfm = this_cpu_read(*pos->tfms);
0274 
0275         if (!strcmp(crypto_comp_name(tfm), alg_name)) {
0276             pos->users++;
0277             return pos->tfms;
0278         }
0279     }
0280 
0281     pos = kmalloc(sizeof(*pos), GFP_KERNEL);
0282     if (!pos)
0283         return NULL;
0284 
0285     pos->users = 1;
0286     INIT_LIST_HEAD(&pos->list);
0287     list_add(&pos->list, &ipcomp_tfms_list);
0288 
0289     pos->tfms = tfms = alloc_percpu(struct crypto_comp *);
0290     if (!tfms)
0291         goto error;
0292 
0293     for_each_possible_cpu(cpu) {
0294         struct crypto_comp *tfm = crypto_alloc_comp(alg_name, 0,
0295                                 CRYPTO_ALG_ASYNC);
0296         if (IS_ERR(tfm))
0297             goto error;
0298         *per_cpu_ptr(tfms, cpu) = tfm;
0299     }
0300 
0301     return tfms;
0302 
0303 error:
0304     ipcomp_free_tfms(tfms);
0305     return NULL;
0306 }
0307 
0308 static void ipcomp_free_data(struct ipcomp_data *ipcd)
0309 {
0310     if (ipcd->tfms)
0311         ipcomp_free_tfms(ipcd->tfms);
0312     ipcomp_free_scratches();
0313 }
0314 
0315 void ipcomp_destroy(struct xfrm_state *x)
0316 {
0317     struct ipcomp_data *ipcd = x->data;
0318     if (!ipcd)
0319         return;
0320     xfrm_state_delete_tunnel(x);
0321     mutex_lock(&ipcomp_resource_mutex);
0322     ipcomp_free_data(ipcd);
0323     mutex_unlock(&ipcomp_resource_mutex);
0324     kfree(ipcd);
0325 }
0326 EXPORT_SYMBOL_GPL(ipcomp_destroy);
0327 
0328 int ipcomp_init_state(struct xfrm_state *x)
0329 {
0330     int err;
0331     struct ipcomp_data *ipcd;
0332     struct xfrm_algo_desc *calg_desc;
0333 
0334     err = -EINVAL;
0335     if (!x->calg)
0336         goto out;
0337 
0338     if (x->encap)
0339         goto out;
0340 
0341     err = -ENOMEM;
0342     ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
0343     if (!ipcd)
0344         goto out;
0345 
0346     mutex_lock(&ipcomp_resource_mutex);
0347     if (!ipcomp_alloc_scratches())
0348         goto error;
0349 
0350     ipcd->tfms = ipcomp_alloc_tfms(x->calg->alg_name);
0351     if (!ipcd->tfms)
0352         goto error;
0353     mutex_unlock(&ipcomp_resource_mutex);
0354 
0355     calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
0356     BUG_ON(!calg_desc);
0357     ipcd->threshold = calg_desc->uinfo.comp.threshold;
0358     x->data = ipcd;
0359     err = 0;
0360 out:
0361     return err;
0362 
0363 error:
0364     ipcomp_free_data(ipcd);
0365     mutex_unlock(&ipcomp_resource_mutex);
0366     kfree(ipcd);
0367     goto out;
0368 }
0369 EXPORT_SYMBOL_GPL(ipcomp_init_state);
0370 
0371 MODULE_LICENSE("GPL");
0372 MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
0373 MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");