da952315c9
The truesize check has uncovered the fact that we forgot to update truesize after pskb_expand_head. Unfortunately pskb_expand_head can't update it for us because it's used in all sorts of different contexts, some of which would not allow truesize to be updated by itself. So the solution for now is to simply update it in IPComp. This patch also changes skb_put to __skb_put since we've just expanded tailroom by exactly that amount so we know it's there (but gcc does not). Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
501 lines
11 KiB
C
501 lines
11 KiB
C
/*
|
|
* IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173
|
|
*
|
|
* Copyright (C)2003 USAGI/WIDE Project
|
|
*
|
|
* Author Mitsuru KANDA <mk@linux-ipv6.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
/*
|
|
* [Memo]
|
|
*
|
|
* Outbound:
|
|
* The compression of IP datagram MUST be done before AH/ESP processing,
|
|
* fragmentation, and the addition of Hop-by-Hop/Routing header.
|
|
*
|
|
* Inbound:
|
|
* The decompression of IP datagram MUST be done after the reassembly,
|
|
* AH/ESP processing.
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <net/ip.h>
|
|
#include <net/xfrm.h>
|
|
#include <net/ipcomp.h>
|
|
#include <asm/scatterlist.h>
|
|
#include <asm/semaphore.h>
|
|
#include <linux/crypto.h>
|
|
#include <linux/pfkeyv2.h>
|
|
#include <linux/random.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/list.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <net/icmp.h>
|
|
#include <net/ipv6.h>
|
|
#include <net/protocol.h>
|
|
#include <linux/ipv6.h>
|
|
#include <linux/icmpv6.h>
|
|
#include <linux/mutex.h>
|
|
|
|
struct ipcomp6_tfms {
|
|
struct list_head list;
|
|
struct crypto_tfm **tfms;
|
|
int users;
|
|
};
|
|
|
|
static DEFINE_MUTEX(ipcomp6_resource_mutex);
|
|
static void **ipcomp6_scratches;
|
|
static int ipcomp6_scratch_users;
|
|
static LIST_HEAD(ipcomp6_tfms_list);
|
|
|
|
static int ipcomp6_input(struct xfrm_state *x, struct sk_buff *skb)
|
|
{
|
|
int err = -ENOMEM;
|
|
struct ipv6hdr *iph;
|
|
struct ipv6_comp_hdr *ipch;
|
|
int plen, dlen;
|
|
struct ipcomp_data *ipcd = x->data;
|
|
u8 *start, *scratch;
|
|
struct crypto_tfm *tfm;
|
|
int cpu;
|
|
|
|
if (skb_linearize_cow(skb))
|
|
goto out;
|
|
|
|
skb->ip_summed = CHECKSUM_NONE;
|
|
|
|
/* Remove ipcomp header and decompress original payload */
|
|
iph = skb->nh.ipv6h;
|
|
ipch = (void *)skb->data;
|
|
skb->h.raw = skb->nh.raw + sizeof(*ipch);
|
|
__skb_pull(skb, sizeof(*ipch));
|
|
|
|
/* decompression */
|
|
plen = skb->len;
|
|
dlen = IPCOMP_SCRATCH_SIZE;
|
|
start = skb->data;
|
|
|
|
cpu = get_cpu();
|
|
scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
|
|
tfm = *per_cpu_ptr(ipcd->tfms, cpu);
|
|
|
|
err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
|
|
if (err) {
|
|
err = -EINVAL;
|
|
goto out_put_cpu;
|
|
}
|
|
|
|
if (dlen < (plen + sizeof(struct ipv6_comp_hdr))) {
|
|
err = -EINVAL;
|
|
goto out_put_cpu;
|
|
}
|
|
|
|
err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC);
|
|
if (err) {
|
|
goto out_put_cpu;
|
|
}
|
|
|
|
skb->truesize += dlen - plen;
|
|
__skb_put(skb, dlen - plen);
|
|
memcpy(skb->data, scratch, dlen);
|
|
err = ipch->nexthdr;
|
|
|
|
out_put_cpu:
|
|
put_cpu();
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int ipcomp6_output(struct xfrm_state *x, struct sk_buff *skb)
|
|
{
|
|
int err;
|
|
struct ipv6hdr *top_iph;
|
|
int hdr_len;
|
|
struct ipv6_comp_hdr *ipch;
|
|
struct ipcomp_data *ipcd = x->data;
|
|
int plen, dlen;
|
|
u8 *start, *scratch;
|
|
struct crypto_tfm *tfm;
|
|
int cpu;
|
|
|
|
hdr_len = skb->h.raw - skb->data;
|
|
|
|
/* check whether datagram len is larger than threshold */
|
|
if ((skb->len - hdr_len) < ipcd->threshold) {
|
|
goto out_ok;
|
|
}
|
|
|
|
if (skb_linearize_cow(skb))
|
|
goto out_ok;
|
|
|
|
/* compression */
|
|
plen = skb->len - hdr_len;
|
|
dlen = IPCOMP_SCRATCH_SIZE;
|
|
start = skb->h.raw;
|
|
|
|
cpu = get_cpu();
|
|
scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
|
|
tfm = *per_cpu_ptr(ipcd->tfms, cpu);
|
|
|
|
err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
|
|
if (err || (dlen + sizeof(struct ipv6_comp_hdr)) >= plen) {
|
|
put_cpu();
|
|
goto out_ok;
|
|
}
|
|
memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
|
|
put_cpu();
|
|
pskb_trim(skb, hdr_len + dlen + sizeof(struct ip_comp_hdr));
|
|
|
|
/* insert ipcomp header and replace datagram */
|
|
top_iph = (struct ipv6hdr *)skb->data;
|
|
|
|
top_iph->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
|
|
|
|
ipch = (struct ipv6_comp_hdr *)start;
|
|
ipch->nexthdr = *skb->nh.raw;
|
|
ipch->flags = 0;
|
|
ipch->cpi = htons((u16 )ntohl(x->id.spi));
|
|
*skb->nh.raw = IPPROTO_COMP;
|
|
|
|
out_ok:
|
|
return 0;
|
|
}
|
|
|
|
static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
|
|
int type, int code, int offset, __u32 info)
|
|
{
|
|
u32 spi;
|
|
struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
|
|
struct ipv6_comp_hdr *ipcomph = (struct ipv6_comp_hdr*)(skb->data+offset);
|
|
struct xfrm_state *x;
|
|
|
|
if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG)
|
|
return;
|
|
|
|
spi = htonl(ntohs(ipcomph->cpi));
|
|
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, IPPROTO_COMP, AF_INET6);
|
|
if (!x)
|
|
return;
|
|
|
|
printk(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/" NIP6_FMT "\n",
|
|
spi, NIP6(iph->daddr));
|
|
xfrm_state_put(x);
|
|
}
|
|
|
|
static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
|
|
{
|
|
struct xfrm_state *t = NULL;
|
|
|
|
t = xfrm_state_alloc();
|
|
if (!t)
|
|
goto out;
|
|
|
|
t->id.proto = IPPROTO_IPV6;
|
|
t->id.spi = xfrm6_tunnel_alloc_spi((xfrm_address_t *)&x->props.saddr);
|
|
if (!t->id.spi)
|
|
goto error;
|
|
|
|
memcpy(t->id.daddr.a6, x->id.daddr.a6, sizeof(struct in6_addr));
|
|
memcpy(&t->sel, &x->sel, sizeof(t->sel));
|
|
t->props.family = AF_INET6;
|
|
t->props.mode = 1;
|
|
memcpy(t->props.saddr.a6, x->props.saddr.a6, sizeof(struct in6_addr));
|
|
|
|
if (xfrm_init_state(t))
|
|
goto error;
|
|
|
|
atomic_set(&t->tunnel_users, 1);
|
|
|
|
out:
|
|
return t;
|
|
|
|
error:
|
|
t->km.state = XFRM_STATE_DEAD;
|
|
xfrm_state_put(t);
|
|
t = NULL;
|
|
goto out;
|
|
}
|
|
|
|
static int ipcomp6_tunnel_attach(struct xfrm_state *x)
|
|
{
|
|
int err = 0;
|
|
struct xfrm_state *t = NULL;
|
|
u32 spi;
|
|
|
|
spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&x->props.saddr);
|
|
if (spi)
|
|
t = xfrm_state_lookup((xfrm_address_t *)&x->id.daddr,
|
|
spi, IPPROTO_IPV6, AF_INET6);
|
|
if (!t) {
|
|
t = ipcomp6_tunnel_create(x);
|
|
if (!t) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
xfrm_state_insert(t);
|
|
xfrm_state_hold(t);
|
|
}
|
|
x->tunnel = t;
|
|
atomic_inc(&t->tunnel_users);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static void ipcomp6_free_scratches(void)
|
|
{
|
|
int i;
|
|
void **scratches;
|
|
|
|
if (--ipcomp6_scratch_users)
|
|
return;
|
|
|
|
scratches = ipcomp6_scratches;
|
|
if (!scratches)
|
|
return;
|
|
|
|
for_each_possible_cpu(i) {
|
|
void *scratch = *per_cpu_ptr(scratches, i);
|
|
|
|
vfree(scratch);
|
|
}
|
|
|
|
free_percpu(scratches);
|
|
}
|
|
|
|
static void **ipcomp6_alloc_scratches(void)
|
|
{
|
|
int i;
|
|
void **scratches;
|
|
|
|
if (ipcomp6_scratch_users++)
|
|
return ipcomp6_scratches;
|
|
|
|
scratches = alloc_percpu(void *);
|
|
if (!scratches)
|
|
return NULL;
|
|
|
|
ipcomp6_scratches = scratches;
|
|
|
|
for_each_possible_cpu(i) {
|
|
void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE);
|
|
if (!scratch)
|
|
return NULL;
|
|
*per_cpu_ptr(scratches, i) = scratch;
|
|
}
|
|
|
|
return scratches;
|
|
}
|
|
|
|
static void ipcomp6_free_tfms(struct crypto_tfm **tfms)
|
|
{
|
|
struct ipcomp6_tfms *pos;
|
|
int cpu;
|
|
|
|
list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
|
|
if (pos->tfms == tfms)
|
|
break;
|
|
}
|
|
|
|
BUG_TRAP(pos);
|
|
|
|
if (--pos->users)
|
|
return;
|
|
|
|
list_del(&pos->list);
|
|
kfree(pos);
|
|
|
|
if (!tfms)
|
|
return;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct crypto_tfm *tfm = *per_cpu_ptr(tfms, cpu);
|
|
crypto_free_tfm(tfm);
|
|
}
|
|
free_percpu(tfms);
|
|
}
|
|
|
|
static struct crypto_tfm **ipcomp6_alloc_tfms(const char *alg_name)
|
|
{
|
|
struct ipcomp6_tfms *pos;
|
|
struct crypto_tfm **tfms;
|
|
int cpu;
|
|
|
|
/* This can be any valid CPU ID so we don't need locking. */
|
|
cpu = raw_smp_processor_id();
|
|
|
|
list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
|
|
struct crypto_tfm *tfm;
|
|
|
|
tfms = pos->tfms;
|
|
tfm = *per_cpu_ptr(tfms, cpu);
|
|
|
|
if (!strcmp(crypto_tfm_alg_name(tfm), alg_name)) {
|
|
pos->users++;
|
|
return tfms;
|
|
}
|
|
}
|
|
|
|
pos = kmalloc(sizeof(*pos), GFP_KERNEL);
|
|
if (!pos)
|
|
return NULL;
|
|
|
|
pos->users = 1;
|
|
INIT_LIST_HEAD(&pos->list);
|
|
list_add(&pos->list, &ipcomp6_tfms_list);
|
|
|
|
pos->tfms = tfms = alloc_percpu(struct crypto_tfm *);
|
|
if (!tfms)
|
|
goto error;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct crypto_tfm *tfm = crypto_alloc_tfm(alg_name, 0);
|
|
if (!tfm)
|
|
goto error;
|
|
*per_cpu_ptr(tfms, cpu) = tfm;
|
|
}
|
|
|
|
return tfms;
|
|
|
|
error:
|
|
ipcomp6_free_tfms(tfms);
|
|
return NULL;
|
|
}
|
|
|
|
static void ipcomp6_free_data(struct ipcomp_data *ipcd)
|
|
{
|
|
if (ipcd->tfms)
|
|
ipcomp6_free_tfms(ipcd->tfms);
|
|
ipcomp6_free_scratches();
|
|
}
|
|
|
|
static void ipcomp6_destroy(struct xfrm_state *x)
|
|
{
|
|
struct ipcomp_data *ipcd = x->data;
|
|
if (!ipcd)
|
|
return;
|
|
xfrm_state_delete_tunnel(x);
|
|
mutex_lock(&ipcomp6_resource_mutex);
|
|
ipcomp6_free_data(ipcd);
|
|
mutex_unlock(&ipcomp6_resource_mutex);
|
|
kfree(ipcd);
|
|
|
|
xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
|
|
}
|
|
|
|
static int ipcomp6_init_state(struct xfrm_state *x)
|
|
{
|
|
int err;
|
|
struct ipcomp_data *ipcd;
|
|
struct xfrm_algo_desc *calg_desc;
|
|
|
|
err = -EINVAL;
|
|
if (!x->calg)
|
|
goto out;
|
|
|
|
if (x->encap)
|
|
goto out;
|
|
|
|
err = -ENOMEM;
|
|
ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
|
|
if (!ipcd)
|
|
goto out;
|
|
|
|
x->props.header_len = 0;
|
|
if (x->props.mode)
|
|
x->props.header_len += sizeof(struct ipv6hdr);
|
|
|
|
mutex_lock(&ipcomp6_resource_mutex);
|
|
if (!ipcomp6_alloc_scratches())
|
|
goto error;
|
|
|
|
ipcd->tfms = ipcomp6_alloc_tfms(x->calg->alg_name);
|
|
if (!ipcd->tfms)
|
|
goto error;
|
|
mutex_unlock(&ipcomp6_resource_mutex);
|
|
|
|
if (x->props.mode) {
|
|
err = ipcomp6_tunnel_attach(x);
|
|
if (err)
|
|
goto error_tunnel;
|
|
}
|
|
|
|
calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
|
|
BUG_ON(!calg_desc);
|
|
ipcd->threshold = calg_desc->uinfo.comp.threshold;
|
|
x->data = ipcd;
|
|
err = 0;
|
|
out:
|
|
return err;
|
|
error_tunnel:
|
|
mutex_lock(&ipcomp6_resource_mutex);
|
|
error:
|
|
ipcomp6_free_data(ipcd);
|
|
mutex_unlock(&ipcomp6_resource_mutex);
|
|
kfree(ipcd);
|
|
|
|
goto out;
|
|
}
|
|
|
|
static struct xfrm_type ipcomp6_type =
|
|
{
|
|
.description = "IPCOMP6",
|
|
.owner = THIS_MODULE,
|
|
.proto = IPPROTO_COMP,
|
|
.init_state = ipcomp6_init_state,
|
|
.destructor = ipcomp6_destroy,
|
|
.input = ipcomp6_input,
|
|
.output = ipcomp6_output,
|
|
};
|
|
|
|
static struct inet6_protocol ipcomp6_protocol =
|
|
{
|
|
.handler = xfrm6_rcv,
|
|
.err_handler = ipcomp6_err,
|
|
.flags = INET6_PROTO_NOPOLICY,
|
|
};
|
|
|
|
static int __init ipcomp6_init(void)
|
|
{
|
|
if (xfrm_register_type(&ipcomp6_type, AF_INET6) < 0) {
|
|
printk(KERN_INFO "ipcomp6 init: can't add xfrm type\n");
|
|
return -EAGAIN;
|
|
}
|
|
if (inet6_add_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) {
|
|
printk(KERN_INFO "ipcomp6 init: can't add protocol\n");
|
|
xfrm_unregister_type(&ipcomp6_type, AF_INET6);
|
|
return -EAGAIN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ipcomp6_fini(void)
|
|
{
|
|
if (inet6_del_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0)
|
|
printk(KERN_INFO "ipv6 ipcomp close: can't remove protocol\n");
|
|
if (xfrm_unregister_type(&ipcomp6_type, AF_INET6) < 0)
|
|
printk(KERN_INFO "ipv6 ipcomp close: can't remove xfrm type\n");
|
|
}
|
|
|
|
module_init(ipcomp6_init);
|
|
module_exit(ipcomp6_fini);
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173");
|
|
MODULE_AUTHOR("Mitsuru KANDA <mk@linux-ipv6.org>");
|
|
|
|
|