0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
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
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
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
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
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>");