aboutsummaryrefslogtreecommitdiff
path: root/net/xfrm
diff options
context:
space:
mode:
Diffstat (limited to 'net/xfrm')
-rw-r--r--net/xfrm/Kconfig11
-rw-r--r--net/xfrm/Makefile1
-rw-r--r--net/xfrm/xfrm_algo.c2
-rw-r--r--net/xfrm/xfrm_hash.c9
-rw-r--r--net/xfrm/xfrm_input.c176
-rw-r--r--net/xfrm/xfrm_output.c154
-rw-r--r--net/xfrm/xfrm_policy.c431
-rw-r--r--net/xfrm/xfrm_proc.c96
-rw-r--r--net/xfrm/xfrm_state.c214
-rw-r--r--net/xfrm/xfrm_user.c8
10 files changed, 920 insertions, 182 deletions
diff --git a/net/xfrm/Kconfig b/net/xfrm/Kconfig
index 577a4f821b9..8f9dbec319b 100644
--- a/net/xfrm/Kconfig
+++ b/net/xfrm/Kconfig
@@ -3,6 +3,7 @@
#
config XFRM
bool
+ select CRYPTO
depends on NET
config XFRM_USER
@@ -35,6 +36,16 @@ config XFRM_MIGRATE
If unsure, say N.
+config XFRM_STATISTICS
+ bool "Transformation statistics (EXPERIMENTAL)"
+ depends on XFRM && PROC_FS && EXPERIMENTAL
+ ---help---
+ This statistics is not a SNMP/MIB specification but shows
+ statistics about transformation error (or almost error) factor
+ at packet processing for developer.
+
+ If unsure, say N.
+
config NET_KEY
tristate "PF_KEY sockets"
select XFRM
diff --git a/net/xfrm/Makefile b/net/xfrm/Makefile
index 45744a3d3a5..332cfb0ff56 100644
--- a/net/xfrm/Makefile
+++ b/net/xfrm/Makefile
@@ -4,5 +4,6 @@
obj-$(CONFIG_XFRM) := xfrm_policy.o xfrm_state.o xfrm_hash.o \
xfrm_input.o xfrm_output.o xfrm_algo.o
+obj-$(CONFIG_XFRM_STATISTICS) += xfrm_proc.o
obj-$(CONFIG_XFRM_USER) += xfrm_user.o
diff --git a/net/xfrm/xfrm_algo.c b/net/xfrm/xfrm_algo.c
index 1686f64c435..b5c5347aed6 100644
--- a/net/xfrm/xfrm_algo.c
+++ b/net/xfrm/xfrm_algo.c
@@ -486,7 +486,6 @@ EXPORT_SYMBOL_GPL(xfrm_ealg_get_byidx);
*/
void xfrm_probe_algs(void)
{
-#ifdef CONFIG_CRYPTO
int i, status;
BUG_ON(in_softirq());
@@ -511,7 +510,6 @@ void xfrm_probe_algs(void)
if (calg_list[i].available != status)
calg_list[i].available = status;
}
-#endif
}
EXPORT_SYMBOL_GPL(xfrm_probe_algs);
diff --git a/net/xfrm/xfrm_hash.c b/net/xfrm/xfrm_hash.c
index 55ab5792af5..a2023ec5232 100644
--- a/net/xfrm/xfrm_hash.c
+++ b/net/xfrm/xfrm_hash.c
@@ -17,17 +17,14 @@ struct hlist_head *xfrm_hash_alloc(unsigned int sz)
struct hlist_head *n;
if (sz <= PAGE_SIZE)
- n = kmalloc(sz, GFP_KERNEL);
+ n = kzalloc(sz, GFP_KERNEL);
else if (hashdist)
- n = __vmalloc(sz, GFP_KERNEL, PAGE_KERNEL);
+ n = __vmalloc(sz, GFP_KERNEL | __GFP_ZERO, PAGE_KERNEL);
else
n = (struct hlist_head *)
- __get_free_pages(GFP_KERNEL | __GFP_NOWARN,
+ __get_free_pages(GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO,
get_order(sz));
- if (n)
- memset(n, 0, sz);
-
return n;
}
diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index cb97fda1b6d..039e7019c48 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -9,6 +9,8 @@
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <net/dst.h>
#include <net/ip.h>
#include <net/xfrm.h>
@@ -81,6 +83,180 @@ int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
}
EXPORT_SYMBOL(xfrm_parse_spi);
+int xfrm_prepare_input(struct xfrm_state *x, struct sk_buff *skb)
+{
+ int err;
+
+ err = x->outer_mode->afinfo->extract_input(x, skb);
+ if (err)
+ return err;
+
+ skb->protocol = x->inner_mode->afinfo->eth_proto;
+ return x->inner_mode->input2(x, skb);
+}
+EXPORT_SYMBOL(xfrm_prepare_input);
+
+int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
+{
+ int err;
+ __be32 seq;
+ struct xfrm_state *x;
+ xfrm_address_t *daddr;
+ unsigned int family;
+ int decaps = 0;
+ int async = 0;
+
+ /* A negative encap_type indicates async resumption. */
+ if (encap_type < 0) {
+ async = 1;
+ x = xfrm_input_state(skb);
+ seq = XFRM_SKB_CB(skb)->seq;
+ goto resume;
+ }
+
+ /* Allocate new secpath or COW existing one. */
+ if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
+ struct sec_path *sp;
+
+ sp = secpath_dup(skb->sp);
+ if (!sp) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINERROR);
+ goto drop;
+ }
+ if (skb->sp)
+ secpath_put(skb->sp);
+ skb->sp = sp;
+ }
+
+ daddr = (xfrm_address_t *)(skb_network_header(skb) +
+ XFRM_SPI_SKB_CB(skb)->daddroff);
+ family = XFRM_SPI_SKB_CB(skb)->family;
+
+ seq = 0;
+ if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
+ goto drop;
+ }
+
+ do {
+ if (skb->sp->len == XFRM_MAX_DEPTH) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINBUFFERERROR);
+ goto drop;
+ }
+
+ x = xfrm_state_lookup(daddr, spi, nexthdr, family);
+ if (x == NULL) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINNOSTATES);
+ xfrm_audit_state_notfound(skb, family, spi, seq);
+ goto drop;
+ }
+
+ skb->sp->xvec[skb->sp->len++] = x;
+
+ spin_lock(&x->lock);
+ if (unlikely(x->km.state != XFRM_STATE_VALID)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEINVALID);
+ goto drop_unlock;
+ }
+
+ if ((x->encap ? x->encap->encap_type : 0) != encap_type) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEINVALID);
+ goto drop_unlock;
+ }
+
+ if (x->props.replay_window && xfrm_replay_check(x, skb, seq)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSEQOUTOFWINDOW);
+ goto drop_unlock;
+ }
+
+ if (xfrm_state_check_expire(x)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEEXPIRED);
+ goto drop_unlock;
+ }
+
+ spin_unlock(&x->lock);
+
+ XFRM_SKB_CB(skb)->seq = seq;
+
+ nexthdr = x->type->input(x, skb);
+
+ if (nexthdr == -EINPROGRESS)
+ return 0;
+
+resume:
+ spin_lock(&x->lock);
+ if (nexthdr <= 0) {
+ if (nexthdr == -EBADMSG) {
+ xfrm_audit_state_icvfail(x, skb,
+ x->type->proto);
+ x->stats.integrity_failed++;
+ }
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEPROTOERROR);
+ goto drop_unlock;
+ }
+
+ /* only the first xfrm gets the encap type */
+ encap_type = 0;
+
+ if (x->props.replay_window)
+ xfrm_replay_advance(x, seq);
+
+ x->curlft.bytes += skb->len;
+ x->curlft.packets++;
+
+ spin_unlock(&x->lock);
+
+ XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;
+
+ if (x->inner_mode->input(x, skb)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEMODEERROR);
+ goto drop;
+ }
+
+ if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) {
+ decaps = 1;
+ break;
+ }
+
+ /*
+ * We need the inner address. However, we only get here for
+ * transport mode so the outer address is identical.
+ */
+ daddr = &x->id.daddr;
+ family = x->outer_mode->afinfo->family;
+
+ err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
+ if (err < 0) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
+ goto drop;
+ }
+ } while (!err);
+
+ nf_reset(skb);
+
+ if (decaps) {
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ netif_rx(skb);
+ return 0;
+ } else {
+ return x->inner_mode->afinfo->transport_finish(skb, async);
+ }
+
+drop_unlock:
+ spin_unlock(&x->lock);
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+EXPORT_SYMBOL(xfrm_input);
+
+int xfrm_input_resume(struct sk_buff *skb, int nexthdr)
+{
+ return xfrm_input(skb, nexthdr, 0, -1);
+}
+EXPORT_SYMBOL(xfrm_input_resume);
+
void __init xfrm_input_init(void)
{
secpath_cachep = kmem_cache_create("secpath_cache",
diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
index f4bfd6c4565..f4a1047a557 100644
--- a/net/xfrm/xfrm_output.c
+++ b/net/xfrm/xfrm_output.c
@@ -12,14 +12,18 @@
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/netdevice.h>
+#include <linux/netfilter.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <net/dst.h>
#include <net/xfrm.h>
+static int xfrm_output2(struct sk_buff *skb);
+
static int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
{
- int nhead = x->props.header_len + LL_RESERVED_SPACE(skb->dst->dev)
+ struct dst_entry *dst = skb->dst;
+ int nhead = dst->header_len + LL_RESERVED_SPACE(dst->dev)
- skb_headroom(skb);
if (nhead > 0)
@@ -29,54 +33,63 @@ static int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
return 0;
}
-static int xfrm_state_check(struct xfrm_state *x, struct sk_buff *skb)
-{
- int err = xfrm_state_check_expire(x);
- if (err < 0)
- goto err;
- err = xfrm_state_check_space(x, skb);
-err:
- return err;
-}
-
-int xfrm_output(struct sk_buff *skb)
+static int xfrm_output_one(struct sk_buff *skb, int err)
{
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
- int err;
- if (skb->ip_summed == CHECKSUM_PARTIAL) {
- err = skb_checksum_help(skb);
- if (err)
- goto error_nolock;
- }
+ if (err <= 0)
+ goto resume;
do {
+ err = xfrm_state_check_space(x, skb);
+ if (err) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTERROR);
+ goto error_nolock;
+ }
+
+ err = x->outer_mode->output(x, skb);
+ if (err) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTSTATEMODEERROR);
+ goto error_nolock;
+ }
+
spin_lock_bh(&x->lock);
- err = xfrm_state_check(x, skb);
- if (err)
+ err = xfrm_state_check_expire(x);
+ if (err) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTSTATEEXPIRED);
goto error;
+ }
if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
XFRM_SKB_CB(skb)->seq = ++x->replay.oseq;
+ if (unlikely(x->replay.oseq == 0)) {
+ x->replay.oseq--;
+ xfrm_audit_state_replay_overflow(x, skb);
+ err = -EOVERFLOW;
+ goto error;
+ }
if (xfrm_aevent_is_on())
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}
- err = x->outer_mode->output(x, skb);
- if (err)
- goto error;
-
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock_bh(&x->lock);
err = x->type->output(x, skb);
- if (err)
+ if (err == -EINPROGRESS)
+ goto out_exit;
+
+resume:
+ if (err) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTSTATEPROTOERROR);
goto error_nolock;
+ }
if (!(skb->dst = dst_pop(dst))) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTERROR);
err = -EHOSTUNREACH;
goto error_nolock;
}
@@ -86,10 +99,97 @@ int xfrm_output(struct sk_buff *skb)
err = 0;
-error_nolock:
+out_exit:
return err;
error:
spin_unlock_bh(&x->lock);
- goto error_nolock;
+error_nolock:
+ kfree_skb(skb);
+ goto out_exit;
+}
+
+int xfrm_output_resume(struct sk_buff *skb, int err)
+{
+ while (likely((err = xfrm_output_one(skb, err)) == 0)) {
+ struct xfrm_state *x;
+
+ nf_reset(skb);
+
+ err = skb->dst->ops->local_out(skb);
+ if (unlikely(err != 1))
+ goto out;
+
+ x = skb->dst->xfrm;
+ if (!x)
+ return dst_output(skb);
+
+ err = nf_hook(x->inner_mode->afinfo->family,
+ NF_INET_POST_ROUTING, skb,
+ NULL, skb->dst->dev, xfrm_output2);
+ if (unlikely(err != 1))
+ goto out;
+ }
+
+ if (err == -EINPROGRESS)
+ err = 0;
+
+out:
+ return err;
+}
+EXPORT_SYMBOL_GPL(xfrm_output_resume);
+
+static int xfrm_output2(struct sk_buff *skb)
+{
+ return xfrm_output_resume(skb, 1);
+}
+
+static int xfrm_output_gso(struct sk_buff *skb)
+{
+ struct sk_buff *segs;
+
+ segs = skb_gso_segment(skb, 0);
+ kfree_skb(skb);
+ if (unlikely(IS_ERR(segs)))
+ return PTR_ERR(segs);
+
+ do {
+ struct sk_buff *nskb = segs->next;
+ int err;
+
+ segs->next = NULL;
+ err = xfrm_output2(segs);
+
+ if (unlikely(err)) {
+ while ((segs = nskb)) {
+ nskb = segs->next;
+ segs->next = NULL;
+ kfree_skb(segs);
+ }
+ return err;
+ }
+
+ segs = nskb;
+ } while (segs);
+
+ return 0;
+}
+
+int xfrm_output(struct sk_buff *skb)
+{
+ int err;
+
+ if (skb_is_gso(skb))
+ return xfrm_output_gso(skb);
+
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ err = skb_checksum_help(skb);
+ if (err) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTERROR);
+ kfree_skb(skb);
+ return err;
+ }
+ }
+
+ return xfrm_output2(skb);
}
EXPORT_SYMBOL_GPL(xfrm_output);
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 26b846e11bf..47219f98053 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -13,6 +13,7 @@
*
*/
+#include <linux/err.h>
#include <linux/slab.h>
#include <linux/kmod.h>
#include <linux/list.h>
@@ -23,13 +24,23 @@
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/cache.h>
+#include <linux/audit.h>
+#include <net/dst.h>
#include <net/xfrm.h>
#include <net/ip.h>
+#ifdef CONFIG_XFRM_STATISTICS
+#include <net/snmp.h>
+#endif
#include "xfrm_hash.h"
int sysctl_xfrm_larval_drop __read_mostly;
+#ifdef CONFIG_XFRM_STATISTICS
+DEFINE_SNMP_STAT(struct linux_xfrm_mib, xfrm_statistics) __read_mostly;
+EXPORT_SYMBOL(xfrm_statistics);
+#endif
+
DEFINE_MUTEX(xfrm_cfg_mutex);
EXPORT_SYMBOL(xfrm_cfg_mutex);
@@ -49,6 +60,7 @@ static DEFINE_SPINLOCK(xfrm_policy_gc_lock);
static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family);
static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo);
+static void xfrm_init_pmtu(struct dst_entry *dst);
static inline int
__xfrm4_selector_match(struct xfrm_selector *sel, struct flowi *fl)
@@ -84,23 +96,27 @@ int xfrm_selector_match(struct xfrm_selector *sel, struct flowi *fl,
return 0;
}
-int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl,
- unsigned short family)
+static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x, int tos,
+ int family)
{
- struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- int err = 0;
+ xfrm_address_t *saddr = &x->props.saddr;
+ xfrm_address_t *daddr = &x->id.daddr;
+ struct xfrm_policy_afinfo *afinfo;
+ struct dst_entry *dst;
+
+ if (x->type->flags & XFRM_TYPE_LOCAL_COADDR)
+ saddr = x->coaddr;
+ if (x->type->flags & XFRM_TYPE_REMOTE_COADDR)
+ daddr = x->coaddr;
+ afinfo = xfrm_policy_get_afinfo(family);
if (unlikely(afinfo == NULL))
- return -EAFNOSUPPORT;
+ return ERR_PTR(-EAFNOSUPPORT);
- if (likely(afinfo->dst_lookup != NULL))
- err = afinfo->dst_lookup(dst, fl);
- else
- err = -EINVAL;
+ dst = afinfo->dst_lookup(tos, saddr, daddr);
xfrm_policy_put_afinfo(afinfo);
- return err;
+ return dst;
}
-EXPORT_SYMBOL(xfrm_dst_lookup);
static inline unsigned long make_jiffies(long secs)
{
@@ -196,9 +212,8 @@ struct xfrm_policy *xfrm_policy_alloc(gfp_t gfp)
INIT_HLIST_NODE(&policy->byidx);
rwlock_init(&policy->lock);
atomic_set(&policy->refcnt, 1);
- init_timer(&policy->timer);
- policy->timer.data = (unsigned long)policy;
- policy->timer.function = xfrm_policy_timer;
+ setup_timer(&policy->timer, xfrm_policy_timer,
+ (unsigned long)policy);
}
return policy;
}
@@ -206,7 +221,7 @@ EXPORT_SYMBOL(xfrm_policy_alloc);
/* Destroy xfrm_policy: descendant resources must be released to this moment. */
-void __xfrm_policy_destroy(struct xfrm_policy *policy)
+void xfrm_policy_destroy(struct xfrm_policy *policy)
{
BUG_ON(!policy->dead);
@@ -218,7 +233,7 @@ void __xfrm_policy_destroy(struct xfrm_policy *policy)
security_xfrm_policy_free(policy);
kfree(policy);
}
-EXPORT_SYMBOL(__xfrm_policy_destroy);
+EXPORT_SYMBOL(xfrm_policy_destroy);
static void xfrm_policy_gc_kill(struct xfrm_policy *policy)
{
@@ -1230,24 +1245,185 @@ xfrm_find_bundle(struct flowi *fl, struct xfrm_policy *policy, unsigned short fa
return x;
}
-/* Allocate chain of dst_entry's, attach known xfrm's, calculate
- * all the metrics... Shortly, bundle a bundle.
- */
+static inline int xfrm_get_tos(struct flowi *fl, int family)
+{
+ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
+ int tos;
-static int
-xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
- struct flowi *fl, struct dst_entry **dst_p,
- unsigned short family)
+ if (!afinfo)
+ return -EINVAL;
+
+ tos = afinfo->get_tos(fl);
+
+ xfrm_policy_put_afinfo(afinfo);
+
+ return tos;
+}
+
+static inline struct xfrm_dst *xfrm_alloc_dst(int family)
{
- int err;
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
- if (unlikely(afinfo == NULL))
+ struct xfrm_dst *xdst;
+
+ if (!afinfo)
+ return ERR_PTR(-EINVAL);
+
+ xdst = dst_alloc(afinfo->dst_ops) ?: ERR_PTR(-ENOBUFS);
+
+ xfrm_policy_put_afinfo(afinfo);
+
+ return xdst;
+}
+
+static inline int xfrm_init_path(struct xfrm_dst *path, struct dst_entry *dst,
+ int nfheader_len)
+{
+ struct xfrm_policy_afinfo *afinfo =
+ xfrm_policy_get_afinfo(dst->ops->family);
+ int err;
+
+ if (!afinfo)
return -EINVAL;
- err = afinfo->bundle_create(policy, xfrm, nx, fl, dst_p);
+
+ err = afinfo->init_path(path, dst, nfheader_len);
+
xfrm_policy_put_afinfo(afinfo);
+
return err;
}
+static inline int xfrm_fill_dst(struct xfrm_dst *xdst, struct net_device *dev)
+{
+ struct xfrm_policy_afinfo *afinfo =
+ xfrm_policy_get_afinfo(xdst->u.dst.ops->family);
+ int err;
+
+ if (!afinfo)
+ return -EINVAL;
+
+ err = afinfo->fill_dst(xdst, dev);
+
+ xfrm_policy_put_afinfo(afinfo);
+
+ return err;
+}
+
+/* Allocate chain of dst_entry's, attach known xfrm's, calculate
+ * all the metrics... Shortly, bundle a bundle.
+ */
+
+static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
+ struct xfrm_state **xfrm, int nx,
+ struct flowi *fl,
+ struct dst_entry *dst)
+{
+ unsigned long now = jiffies;
+ struct net_device *dev;
+ struct dst_entry *dst_prev = NULL;
+ struct dst_entry *dst0 = NULL;
+ int i = 0;
+ int err;
+ int header_len = 0;
+ int nfheader_len = 0;
+ int trailer_len = 0;
+ int tos;
+ int family = policy->selector.family;
+
+ tos = xfrm_get_tos(fl, family);
+ err = tos;
+ if (tos < 0)
+ goto put_states;
+
+ dst_hold(dst);
+
+ for (; i < nx; i++) {
+ struct xfrm_dst *xdst = xfrm_alloc_dst(family);
+ struct dst_entry *dst1 = &xdst->u.dst;
+
+ err = PTR_ERR(xdst);
+ if (IS_ERR(xdst)) {
+ dst_release(dst);
+ goto put_states;
+ }
+
+ if (!dst_prev)
+ dst0 = dst1;
+ else {
+ dst_prev->child = dst_clone(dst1);
+ dst1->flags |= DST_NOHASH;
+ }
+
+ xdst->route = dst;
+ memcpy(&dst1->metrics, &dst->metrics, sizeof(dst->metrics));
+
+ if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
+ family = xfrm[i]->props.family;
+ dst = xfrm_dst_lookup(xfrm[i], tos, family);
+ err = PTR_ERR(dst);
+ if (IS_ERR(dst))
+ goto put_states;
+ } else
+ dst_hold(dst);
+
+ dst1->xfrm = xfrm[i];
+ xdst->genid = xfrm[i]->genid;
+
+ dst1->obsolete = -1;
+ dst1->flags |= DST_HOST;
+ dst1->lastuse = now;
+
+ dst1->input = dst_discard;
+ dst1->output = xfrm[i]->outer_mode->afinfo->output;
+
+ dst1->next = dst_prev;
+ dst_prev = dst1;
+
+ header_len += xfrm[i]->props.header_len;
+ if (xfrm[i]->type->flags & XFRM_TYPE_NON_FRAGMENT)
+ nfheader_len += xfrm[i]->props.header_len;
+ trailer_len += xfrm[i]->props.trailer_len;
+ }
+
+ dst_prev->child = dst;
+ dst0->path = dst;
+
+ err = -ENODEV;
+ dev = dst->dev;
+ if (!dev)
+ goto free_dst;
+
+ /* Copy neighbout for reachability confirmation */
+ dst0->neighbour = neigh_clone(dst->neighbour);
+
+ xfrm_init_path((struct xfrm_dst *)dst0, dst, nfheader_len);
+ xfrm_init_pmtu(dst_prev);
+
+ for (dst_prev = dst0; dst_prev != dst; dst_prev = dst_prev->child) {
+ struct xfrm_dst *xdst = (struct xfrm_dst *)dst_prev;
+
+ err = xfrm_fill_dst(xdst, dev);
+ if (err)
+ goto free_dst;
+
+ dst_prev->header_len = header_len;
+ dst_prev->trailer_len = trailer_len;
+ header_len -= xdst->u.dst.xfrm->props.header_len;
+ trailer_len -= xdst->u.dst.xfrm->props.trailer_len;
+ }
+
+out:
+ return dst0;
+
+put_states:
+ for (; i < nx; i++)
+ xfrm_state_put(xfrm[i]);
+free_dst:
+ if (dst0)
+ dst_free(dst0);
+ dst0 = ERR_PTR(err);
+ goto out;
+}
+
static int inline
xfrm_dst_alloc_copy(void **target, void *src, int size)
{
@@ -1319,36 +1495,46 @@ restart:
if (sk && sk->sk_policy[XFRM_POLICY_OUT]) {
policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
err = PTR_ERR(policy);
- if (IS_ERR(policy))
+ if (IS_ERR(policy)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
goto dropdst;
+ }
}
if (!policy) {
/* To accelerate a bit... */
if ((dst_orig->flags & DST_NOXFRM) ||
!xfrm_policy_count[XFRM_POLICY_OUT])
- return 0;
+ goto nopol;
policy = flow_cache_lookup(fl, dst_orig->ops->family,
dir, xfrm_policy_lookup);
err = PTR_ERR(policy);
- if (IS_ERR(policy))
+ if (IS_ERR(policy)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
goto dropdst;
+ }
}
if (!policy)
- return 0;
+ goto nopol;
family = dst_orig->ops->family;
- policy->curlft.use_time = get_seconds();
pols[0] = policy;
npols ++;
xfrm_nr += pols[0]->xfrm_nr;
+ err = -ENOENT;
+ if ((flags & XFRM_LOOKUP_ICMP) && !(policy->flags & XFRM_POLICY_ICMP))
+ goto error;
+
+ policy->curlft.use_time = get_seconds();
+
switch (policy->action) {
default:
case XFRM_POLICY_BLOCK:
/* Prohibit the flow */
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLBLOCK);
err = -EPERM;
goto error;
@@ -1368,6 +1554,7 @@ restart:
*/
dst = xfrm_find_bundle(fl, policy, family);
if (IS_ERR(dst)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
err = PTR_ERR(dst);
goto error;
}
@@ -1382,10 +1569,12 @@ restart:
XFRM_POLICY_OUT);
if (pols[1]) {
if (IS_ERR(pols[1])) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
err = PTR_ERR(pols[1]);
goto error;
}
if (pols[1]->action == XFRM_POLICY_BLOCK) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLBLOCK);
err = -EPERM;
goto error;
}
@@ -1416,10 +1605,11 @@ restart:
/* EREMOTE tells the caller to generate
* a one-shot blackhole route.
*/
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
xfrm_pol_put(policy);
return -EREMOTE;
}
- if (err == -EAGAIN && flags) {
+ if (err == -EAGAIN && (flags & XFRM_LOOKUP_WAIT)) {
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&km_waitq, &wait);
@@ -1431,6 +1621,7 @@ restart:
nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
if (nx == -EAGAIN && signal_pending(current)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
err = -ERESTART;
goto error;
}
@@ -1441,8 +1632,10 @@ restart:
}
err = nx;
}
- if (err < 0)
+ if (err < 0) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
goto error;
+ }
}
if (nx == 0) {
/* Flow passes not transformed. */
@@ -1450,13 +1643,10 @@ restart:
return 0;
}
- dst = dst_orig;
- err = xfrm_bundle_create(policy, xfrm, nx, fl, &dst, family);
-
- if (unlikely(err)) {
- int i;
- for (i=0; i<nx; i++)
- xfrm_state_put(xfrm[i]);
+ dst = xfrm_bundle_create(policy, xfrm, nx, fl, dst_orig);
+ err = PTR_ERR(dst);
+ if (IS_ERR(dst)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLEGENERROR);
goto error;
}
@@ -1477,6 +1667,10 @@ restart:
if (dst)
dst_free(dst);
+ if (pol_dead)
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLDEAD);
+ else
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
err = -EHOSTUNREACH;
goto error;
}
@@ -1489,6 +1683,7 @@ restart:
write_unlock_bh(&policy->lock);
if (dst)
dst_free(dst);
+ XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
goto error;
}
@@ -1508,6 +1703,12 @@ dropdst:
dst_release(dst_orig);
*dst_p = NULL;
return err;
+
+nopol:
+ err = -ENOENT;
+ if (flags & XFRM_LOOKUP_ICMP)
+ goto dropdst;
+ return 0;
}
EXPORT_SYMBOL(__xfrm_lookup);
@@ -1591,8 +1792,8 @@ xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int start,
return start;
}
-int
-xfrm_decode_session(struct sk_buff *skb, struct flowi *fl, unsigned short family)
+int __xfrm_decode_session(struct sk_buff *skb, struct flowi *fl,
+ unsigned int family, int reverse)
{
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
int err;
@@ -1600,12 +1801,12 @@ xfrm_decode_session(struct sk_buff *skb, struct flowi *fl, unsigned short family
if (unlikely(afinfo == NULL))
return -EAFNOSUPPORT;
- afinfo->decode_session(skb, fl);
+ afinfo->decode_session(skb, fl, reverse);
err = security_xfrm_decode_session(skb, &fl->secid);
xfrm_policy_put_afinfo(afinfo);
return err;
}
-EXPORT_SYMBOL(xfrm_decode_session);
+EXPORT_SYMBOL(__xfrm_decode_session);
static inline int secpath_has_nontransport(struct sec_path *sp, int k, int *idxp)
{
@@ -1627,12 +1828,20 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
int npols = 0;
int xfrm_nr;
int pi;
+ int reverse;
struct flowi fl;
- u8 fl_dir = policy_to_flow_dir(dir);
+ u8 fl_dir;
int xerr_idx = -1;
- if (xfrm_decode_session(skb, &fl, family) < 0)
+ reverse = dir & ~XFRM_POLICY_MASK;
+ dir &= XFRM_POLICY_MASK;
+ fl_dir = policy_to_flow_dir(dir);
+
+ if (__xfrm_decode_session(skb, &fl, family, reverse) < 0) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
return 0;
+ }
+
nf_nat_decode_session(skb, &fl, family);
/* First, check used SA against their selectors. */
@@ -1641,28 +1850,35 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
for (i=skb->sp->len-1; i>=0; i--) {
struct xfrm_state *x = skb->sp->xvec[i];
- if (!xfrm_selector_match(&x->sel, &fl, family))
+ if (!xfrm_selector_match(&x->sel, &fl, family)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEMISMATCH);
return 0;
+ }
}
}
pol = NULL;
if (sk && sk->sk_policy[dir]) {
pol = xfrm_sk_policy_lookup(sk, dir, &fl);
- if (IS_ERR(pol))
+ if (IS_ERR(pol)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
return 0;
+ }
}
if (!pol)
pol = flow_cache_lookup(&fl, family, fl_dir,
xfrm_policy_lookup);
- if (IS_ERR(pol))
+ if (IS_ERR(pol)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
return 0;
+ }
if (!pol) {
if (skb->sp && secpath_has_nontransport(skb->sp, 0, &xerr_idx)) {
xfrm_secpath_reject(xerr_idx, skb, &fl);
+ XFRM_INC_STATS(LINUX_MIB_XFRMINNOPOLS);
return 0;
}
return 1;
@@ -1678,8 +1894,10 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
&fl, family,
XFRM_POLICY_IN);
if (pols[1]) {
- if (IS_ERR(pols[1]))
+ if (IS_ERR(pols[1])) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
return 0;
+ }
pols[1]->curlft.use_time = get_seconds();
npols ++;
}
@@ -1700,10 +1918,14 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
for (pi = 0; pi < npols; pi++) {
if (pols[pi] != pol &&
- pols[pi]->action != XFRM_POLICY_ALLOW)
+ pols[pi]->action != XFRM_POLICY_ALLOW) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINPOLBLOCK);
goto reject;
- if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH)
+ }
+ if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINBUFFERERROR);
goto reject_error;
+ }
for (i = 0; i < pols[pi]->xfrm_nr; i++)
tpp[ti++] = &pols[pi]->xfrm_vec[i];
}
@@ -1725,16 +1947,20 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
if (k < -1)
/* "-2 - errored_index" returned */
xerr_idx = -(2+k);
+ XFRM_INC_STATS(LINUX_MIB_XFRMINTMPLMISMATCH);
goto reject;
}
}
- if (secpath_has_nontransport(sp, k, &xerr_idx))
+ if (secpath_has_nontransport(sp, k, &xerr_idx)) {
+ XFRM_INC_STATS(LINUX_MIB_XFRMINTMPLMISMATCH);
goto reject;
+ }
xfrm_pols_put(pols, npols);
return 1;
}
+ XFRM_INC_STATS(LINUX_MIB_XFRMINPOLBLOCK);
reject:
xfrm_secpath_reject(xerr_idx, skb, &fl);
@@ -1748,8 +1974,11 @@ int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
{
struct flowi fl;
- if (xfrm_decode_session(skb, &fl, family) < 0)
+ if (xfrm_decode_session(skb, &fl, family) < 0) {
+ /* XXX: we should have something like FWDHDRERROR here. */
+ XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
return 0;
+ }
return xfrm_lookup(&skb->dst, &fl, NULL, 0) == 0;
}
@@ -1793,7 +2022,7 @@ static int stale_bundle(struct dst_entry *dst)
void xfrm_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
{
while ((dst = dst->child) && dst->xfrm && dst->dev == dev) {
- dst->dev = init_net.loopback_dev;
+ dst->dev = dev->nd_net->loopback_dev;
dev_hold(dst->dev);
dev_put(dev);
}
@@ -1882,7 +2111,7 @@ static int xfrm_flush_bundles(void)
return 0;
}
-void xfrm_init_pmtu(struct dst_entry *dst)
+static void xfrm_init_pmtu(struct dst_entry *dst)
{
do {
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
@@ -1903,8 +2132,6 @@ void xfrm_init_pmtu(struct dst_entry *dst)
} while ((dst = dst->next));
}
-EXPORT_SYMBOL(xfrm_init_pmtu);
-
/* Check that the bundle accepts the flow and its components are
* still valid.
*/
@@ -2082,6 +2309,16 @@ static struct notifier_block xfrm_dev_notifier = {
0
};
+#ifdef CONFIG_XFRM_STATISTICS
+static int __init xfrm_statistics_init(void)
+{
+ if (snmp_mib_init((void **)xfrm_statistics,
+ sizeof(struct linux_xfrm_mib)) < 0)
+ return -ENOMEM;
+ return 0;
+}
+#endif
+
static void __init xfrm_policy_init(void)
{
unsigned int hmask, sz;
@@ -2118,71 +2355,81 @@ static void __init xfrm_policy_init(void)
void __init xfrm_init(void)
{
+#ifdef CONFIG_XFRM_STATISTICS
+ xfrm_statistics_init();
+#endif
xfrm_state_init();
xfrm_policy_init();
xfrm_input_init();
+#ifdef CONFIG_XFRM_STATISTICS
+ xfrm_proc_init();
+#endif
}
#ifdef CONFIG_AUDITSYSCALL
-static inline void xfrm_audit_common_policyinfo(struct xfrm_policy *xp,
- struct audit_buffer *audit_buf)
+static void xfrm_audit_common_policyinfo(struct xfrm_policy *xp,
+ struct audit_buffer *audit_buf)