summaryrefslogtreecommitdiffstats
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)
{
- if (xp->security)
+ struct xfrm_sec_ctx *ctx = xp->security;
+ struct xfrm_selector *sel = &xp->selector;
+
+ if (ctx)
audit_log_format(audit_buf, " sec_alg=%u sec_doi=%u sec_obj=%s",
- xp->security->ctx_alg, xp->security->ctx_doi,
- xp->security->ctx_str);
+ ctx->ctx_alg, ctx->ctx_doi, ctx->ctx_str);
- switch(xp->selector.family) {
+ switch(sel->family) {
case AF_INET:
- audit_log_format(audit_buf, " src=%u.%u.%u.%u dst=%u.%u.%u.%u",
- NIPQUAD(xp->selector.saddr.a4),
- NIPQUAD(xp->selector.daddr.a4));
+ audit_log_format(audit_buf, " src=" NIPQUAD_FMT,
+ NIPQUAD(sel->saddr.a4));
+ if (sel->prefixlen_s != 32)
+ audit_log_format(audit_buf, " src_prefixlen=%d",
+ sel->prefixlen_s);
+ audit_log_format(audit_buf, " dst=" NIPQUAD_FMT,
+ NIPQUAD(sel->daddr.a4));
+ if (sel->prefixlen_d != 32)
+ audit_log_format(audit_buf, " dst_prefixlen=%d",
+ sel->prefixlen_d);
break;
case AF_INET6:
- {
- struct in6_addr saddr6, daddr6;
-
- memcpy(&saddr6, xp->selector.saddr.a6,
- sizeof(struct in6_addr));
- memcpy(&daddr6, xp->selector.daddr.a6,
- sizeof(struct in6_addr));
- audit_log_format(audit_buf,
- " src=" NIP6_FMT " dst=" NIP6_FMT,
- NIP6(saddr6), NIP6(daddr6));
- }
+ audit_log_format(audit_buf, " src=" NIP6_FMT,
+ NIP6(*(struct in6_addr *)sel->saddr.a6));
+ if (sel->prefixlen_s != 128)
+ audit_log_format(audit_buf, " src_prefixlen=%d",
+ sel->prefixlen_s);
+ audit_log_format(audit_buf, " dst=" NIP6_FMT,
+ NIP6(*(struct in6_addr *)sel->daddr.a6));
+ if (sel->prefixlen_d != 128)
+ audit_log_format(audit_buf, " dst_prefixlen=%d",
+ sel->prefixlen_d);
break;
}
}
-void
-xfrm_audit_policy_add(struct xfrm_policy *xp, int result, u32 auid, u32 sid)
+void xfrm_audit_policy_add(struct xfrm_policy *xp, int result,
+ u32 auid, u32 secid)
{
struct audit_buffer *audit_buf;
- extern int audit_enabled;
- if (audit_enabled == 0)
- return;
- audit_buf = xfrm_audit_start(auid, sid);
+ audit_buf = xfrm_audit_start("SPD-add");
if (audit_buf == NULL)
return;
- audit_log_format(audit_buf, " op=SPD-add res=%u", result);
+ xfrm_audit_helper_usrinfo(auid, secid, audit_buf);
+ audit_log_format(audit_buf, " res=%u", result);
xfrm_audit_common_policyinfo(xp, audit_buf);
audit_log_end(audit_buf);
}
EXPORT_SYMBOL_GPL(xfrm_audit_policy_add);
-void
-xfrm_audit_policy_delete(struct xfrm_policy *xp, int result, u32 auid, u32 sid)
+void xfrm_audit_policy_delete(struct xfrm_policy *xp, int result,
+ u32 auid, u32 secid)
{
struct audit_buffer *audit_buf;
- extern int audit_enabled;
- if (audit_enabled == 0)
- return;
- audit_buf = xfrm_audit_start(auid, sid);
+ audit_buf = xfrm_audit_start("SPD-delete");
if (audit_buf == NULL)
return;
- audit_log_format(audit_buf, " op=SPD-delete res=%u", result);
+ xfrm_audit_helper_usrinfo(auid, secid, audit_buf);
+ audit_log_format(audit_buf, " res=%u", result);
xfrm_audit_common_policyinfo(xp, audit_buf);
audit_log_end(audit_buf);
}
diff --git a/net/xfrm/xfrm_proc.c b/net/xfrm/xfrm_proc.c
new file mode 100644
index 00000000000..31d035415ec
--- /dev/null
+++ b/net/xfrm/xfrm_proc.c
@@ -0,0 +1,96 @@
+/*
+ * xfrm_proc.c
+ *
+ * Copyright (C)2006-2007 USAGI/WIDE Project
+ *
+ * Authors: Masahide NAKAMURA <nakam@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.
+ */
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <net/snmp.h>
+#include <net/xfrm.h>
+
+static struct snmp_mib xfrm_mib_list[] = {
+ SNMP_MIB_ITEM("XfrmInError", LINUX_MIB_XFRMINERROR),
+ SNMP_MIB_ITEM("XfrmInBufferError", LINUX_MIB_XFRMINBUFFERERROR),
+ SNMP_MIB_ITEM("XfrmInHdrError", LINUX_MIB_XFRMINHDRERROR),
+ SNMP_MIB_ITEM("XfrmInNoStates", LINUX_MIB_XFRMINNOSTATES),
+ SNMP_MIB_ITEM("XfrmInStateProtoError", LINUX_MIB_XFRMINSTATEPROTOERROR),
+ SNMP_MIB_ITEM("XfrmInStateModeError", LINUX_MIB_XFRMINSTATEMODEERROR),
+ SNMP_MIB_ITEM("XfrmInSeqOutOfWindow", LINUX_MIB_XFRMINSEQOUTOFWINDOW),
+ SNMP_MIB_ITEM("XfrmInStateExpired", LINUX_MIB_XFRMINSTATEEXPIRED),
+ SNMP_MIB_ITEM("XfrmInStateMismatch", LINUX_MIB_XFRMINSTATEMISMATCH),
+ SNMP_MIB_ITEM("XfrmInStateInvalid", LINUX_MIB_XFRMINSTATEINVALID),
+ SNMP_MIB_ITEM("XfrmInTmplMismatch", LINUX_MIB_XFRMINTMPLMISMATCH),
+ SNMP_MIB_ITEM("XfrmInNoPols", LINUX_MIB_XFRMINNOPOLS),
+ SNMP_MIB_ITEM("XfrmInPolBlock", LINUX_MIB_XFRMINPOLBLOCK),
+ SNMP_MIB_ITEM("XfrmInPolError", LINUX_MIB_XFRMINPOLERROR),
+ SNMP_MIB_ITEM("XfrmOutError", LINUX_MIB_XFRMOUTERROR),
+ SNMP_MIB_ITEM("XfrmOutBundleGenError", LINUX_MIB_XFRMOUTBUNDLEGENERROR),
+ SNMP_MIB_ITEM("XfrmOutBundleCheckError", LINUX_MIB_XFRMOUTBUNDLECHECKERROR),
+ SNMP_MIB_ITEM("XfrmOutNoStates", LINUX_MIB_XFRMOUTNOSTATES),
+ SNMP_MIB_ITEM("XfrmOutStateProtoError", LINUX_MIB_XFRMOUTSTATEPROTOERROR),
+ SNMP_MIB_ITEM("XfrmOutStateModeError", LINUX_MIB_XFRMOUTSTATEMODEERROR),
+ SNMP_MIB_ITEM("XfrmOutStateExpired", LINUX_MIB_XFRMOUTSTATEEXPIRED),
+ SNMP_MIB_ITEM("XfrmOutPolBlock", LINUX_MIB_XFRMOUTPOLBLOCK),
+ SNMP_MIB_ITEM("XfrmOutPolDead", LINUX_MIB_XFRMOUTPOLDEAD),
+ SNMP_MIB_ITEM("XfrmOutPolError", LINUX_MIB_XFRMOUTPOLERROR),
+ SNMP_MIB_SENTINEL
+};
+
+static unsigned long
+fold_field(void *mib[], int offt)
+{
+ unsigned long res = 0;
+ int i;
+
+ for_each_possible_cpu(i) {
+ res += *(((unsigned long *)per_cpu_ptr(mib[0], i)) + offt);
+ res += *(((unsigned long *)per_cpu_ptr(mib[1], i)) + offt);
+ }
+ return res;
+}
+
+static int xfrm_statistics_seq_show(struct seq_file *seq, void *v)
+{
+ int i;
+ for (i=0; xfrm_mib_list[i].name; i++)
+ seq_printf(seq, "%-24s\t%lu\n", xfrm_mib_list[i].name,
+ fold_field((void **)xfrm_statistics,
+ xfrm_mib_list[i].entry));
+ return 0;
+}
+
+static int xfrm_statistics_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, xfrm_statistics_seq_show, NULL);
+}
+
+static struct file_operations xfrm_statistics_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = xfrm_statistics_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int __init xfrm_proc_init(void)
+{
+ int rc = 0;
+
+ if (!proc_net_fops_create(&init_net, "xfrm_stat", S_IRUGO,
+ &xfrm_statistics_seq_fops))
+ goto stat_fail;
+
+ out:
+ return rc;
+
+ stat_fail:
+ rc = -ENOMEM;
+ goto out;
+}
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index f26aaaca1fa..3003503d0c9 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -19,6 +19,7 @@
#include <linux/ipsec.h>
#include <linux/module.h>
#include <linux/cache.h>
+#include <linux/audit.h>
#include <asm/uaccess.h>
#include "xfrm_hash.h"
@@ -60,6 +61,13 @@ static unsigned int xfrm_state_genid;
static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family);
static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo);
+#ifdef CONFIG_AUDITSYSCALL
+static void xfrm_audit_state_replay(struct xfrm_state *x,
+ struct sk_buff *skb, __be32 net_seq);
+#else
+#define xfrm_audit_state_replay(x, s, sq) do { ; } while (0)
+#endif /* CONFIG_AUDITSYSCALL */
+
static inline unsigned int xfrm_dst_hash(xfrm_address_t *daddr,
xfrm_address_t *saddr,
u32 reqid,
@@ -203,6 +211,7 @@ static struct xfrm_state_afinfo *xfrm_state_lock_afinfo(unsigned int family)
}
static void xfrm_state_unlock_afinfo(struct xfrm_state_afinfo *afinfo)
+ __releases(xfrm_state_afinfo_lock)
{
write_unlock_bh(&xfrm_state_afinfo_lock);
}
@@ -504,12 +513,9 @@ struct xfrm_state *xfrm_state_alloc(void)
INIT_HLIST_NODE(&x->bydst);
INIT_HLIST_NODE(&x->bysrc);
INIT_HLIST_NODE(&x->byspi);
- init_timer(&x->timer);
- x->timer.function = xfrm_timer_handler;
- x->timer.data = (unsigned long)x;
- init_timer(&x->rtimer);
- x->rtimer.function = xfrm_replay_timer_handler;
- x->rtimer.data = (unsigned long)x;
+ setup_timer(&x->timer, xfrm_timer_handler, (unsigned long)x);
+ setup_timer(&x->rtimer, xfrm_replay_timer_handler,
+ (unsigned long)x);
x->curlft.add_time = get_seconds();
x->lft.soft_byte_limit = XFRM_INF;
x->lft.soft_packet_limit = XFRM_INF;
@@ -759,7 +765,7 @@ xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
struct xfrm_policy *pol, int *err,
unsigned short family)
{
- unsigned int h = xfrm_dst_hash(daddr, saddr, tmpl->reqid, family);
+ unsigned int h;
struct hlist_node *entry;
struct xfrm_state *x, *x0;
int acquire_in_progress = 0;
@@ -767,6 +773,7 @@ xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
struct xfrm_state *best = NULL;
spin_lock_bh(&xfrm_state_lock);
+ h = xfrm_dst_hash(daddr, saddr, tmpl->reqid, family);
hlist_for_each_entry(x, entry, xfrm_state_bydst+h, bydst) {
if (x->props.family == family &&
x->props.reqid == tmpl->reqid &&
@@ -868,11 +875,12 @@ struct xfrm_state *
xfrm_stateonly_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
unsigned short family, u8 mode, u8 proto, u32 reqid)
{
- unsigned int h = xfrm_dst_hash(daddr, saddr, reqid, family);
+ unsigned int h;
struct xfrm_state *rx = NULL, *x = NULL;
struct hlist_node *entry;
spin_lock(&xfrm_state_lock);
+ h = xfrm_dst_hash(daddr, saddr, reqid, family);
hlist_for_each_entry(x, entry, xfrm_state_bydst+h, bydst) {
if (x->props.family == family &&
x->props.reqid == reqid &&
@@ -1092,7 +1100,7 @@ out:
EXPORT_SYMBOL(xfrm_state_add);
#ifdef CONFIG_XFRM_MIGRATE
-struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig, int *errp)
+static struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig, int *errp)
{
int err = -ENOMEM;
struct xfrm_state *x = xfrm_state_alloc();
@@ -1167,7 +1175,6 @@ struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig, int *errp)
kfree(x);
return NULL;
}
-EXPORT_SYMBOL(xfrm_state_clone);
/* xfrm_state_lock is held */
struct xfrm_state * xfrm_migrate_state_find(struct xfrm_migrate *m)
@@ -1609,13 +1616,14 @@ static void xfrm_replay_timer_handler(unsigned long data)
spin_unlock(&x->lock);
}
-int xfrm_replay_check(struct xfrm_state *x, __be32 net_seq)
+int xfrm_replay_check(struct xfrm_state *x,
+ struct sk_buff *skb, __be32 net_seq)
{
u32 diff;
u32 seq = ntohl(net_seq);
if (unlikely(seq == 0))
- return -EINVAL;
+ goto err;
if (likely(seq > x->replay.seq))
return 0;
@@ -1624,14 +1632,18 @@ int xfrm_replay_check(struct xfrm_state *x, __be32 net_seq)
if (diff >= min_t(unsigned int, x->props.replay_window,
sizeof(x->replay.bitmap) * 8)) {
x->stats.replay_window++;
- return -EINVAL;
+ goto err;
}
if (x->replay.bitmap & (1U << diff)) {
x->stats.replay++;
- return -EINVAL;
+ goto err;
}
return 0;
+
+err:
+ xfrm_audit_state_replay(x, skb, net_seq);
+ return -EINVAL;
}
EXPORT_SYMBOL(xfrm_replay_check);
@@ -1657,7 +1669,7 @@ void xfrm_replay_advance(struct xfrm_state *x, __be32 net_seq)
}
EXPORT_SYMBOL(xfrm_replay_advance);
-static struct list_head xfrm_km_list = LIST_HEAD_INIT(xfrm_km_list);
+static LIST_HEAD(xfrm_km_list);
static DEFINE_RWLOCK(xfrm_km_lock);
void km_policy_notify(struct xfrm_policy *xp, int dir, struct km_event *c)
@@ -1897,6 +1909,7 @@ static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family)
}
static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo)
+ __releases(xfrm_state_afinfo_lock)
{
read_unlock(&xfrm_state_afinfo_lock);
}
@@ -1996,73 +2009,172 @@ void __init xfrm_state_init(void)
}
#ifdef CONFIG_AUDITSYSCALL
-static inline void xfrm_audit_common_stateinfo(struct xfrm_state *x,
- struct audit_buffer *audit_buf)
+static void xfrm_audit_helper_sainfo(struct xfrm_state *x,
+ struct audit_buffer *audit_buf)
{
- if (x->security)
+ struct xfrm_sec_ctx *ctx = x->security;
+ u32 spi = ntohl(x->id.spi);
+
+ if (ctx)
audit_log_format(audit_buf, " sec_alg=%u sec_doi=%u sec_obj=%s",
- x->security->ctx_alg, x->security->ctx_doi,
- x->security->ctx_str);
+ ctx->ctx_alg, ctx->ctx_doi, ctx->ctx_str);
switch(x->props.family) {
case AF_INET:
- audit_log_format(audit_buf, " src=%u.%u.%u.%u dst=%u.%u.%u.%u",
+ audit_log_format(audit_buf,
+ " src=" NIPQUAD_FMT " dst=" NIPQUAD_FMT,
NIPQUAD(x->props.saddr.a4),
NIPQUAD(x->id.daddr.a4));
break;
case AF_INET6:
- {
- struct in6_addr saddr6, daddr6;
-
- memcpy(&saddr6, x->props.saddr.a6,
- sizeof(struct in6_addr));
- memcpy(&daddr6, x->id.daddr.a6,
- sizeof(struct in6_addr));
- audit_log_format(audit_buf,
- " src=" NIP6_FMT " dst=" NIP6_FMT,
- NIP6(saddr6), NIP6(daddr6));
- }
+ audit_log_format(audit_buf,
+ " src=" NIP6_FMT " dst=" NIP6_FMT,
+ NIP6(*(struct in6_addr *)x->props.saddr.a6),
+ NIP6(*(struct in6_addr *)x->id.daddr.a6));
break;
}
+
+ audit_log_format(audit_buf, " spi=%u(0x%x)", spi, spi);
}
-void
-xfrm_audit_state_add(struct xfrm_state *x, int result, u32 auid, u32 sid)
+static void xfrm_audit_helper_pktinfo(struct sk_buff *skb, u16 family,
+ struct audit_buffer *audit_buf)
+{
+ struct iphdr *iph4;
+ struct ipv6hdr *iph6;
+
+ switch (family) {
+ case AF_INET:
+ iph4 = ip_hdr(skb);
+ audit_log_format(audit_buf,
+ " src=" NIPQUAD_FMT " dst=" NIPQUAD_FMT,
+ NIPQUAD(iph4->saddr),
+ NIPQUAD(iph4->daddr));
+ break;
+ case AF_INET6:
+ iph6 = ipv6_hdr(skb);
+ audit_log_format(audit_buf,
+ " src=" NIP6_FMT " dst=" NIP6_FMT
+ " flowlbl=0x%x%x%x",
+ NIP6(iph6->saddr),
+ NIP6(iph6->daddr),
+ iph6->flow_lbl[0] & 0x0f,
+ iph6->flow_lbl[1],
+ iph6->flow_lbl[2]);
+ break;
+ }
+}
+
+void xfrm_audit_state_add(struct xfrm_state *x, int result,
+ u32 auid, u32 secid)
{
struct audit_buffer *audit_buf;
- u32 spi;
- extern int audit_enabled;
- if (audit_enabled == 0)
+ audit_buf = xfrm_audit_start("SAD-add");
+ if (audit_buf == NULL)
return;
- audit_buf = xfrm_audit_start(auid, sid);
+ xfrm_audit_helper_usrinfo(auid, secid, audit_buf);
+ xfrm_audit_helper_sainfo(x, audit_buf);
+ audit_log_format(audit_buf, " res=%u", result);
+ audit_log_end(audit_buf);
+}
+EXPORT_SYMBOL_GPL(xfrm_audit_state_add);
+
+void xfrm_audit_state_delete(struct xfrm_state *x, int result,
+ u32 auid, u32 secid)
+{
+ struct audit_buffer *audit_buf;
+
+ audit_buf = xfrm_audit_start("SAD-delete");
if (audit_buf == NULL)
return;
- audit_log_format(audit_buf, " op=SAD-add res=%u",result);
- xfrm_audit_common_stateinfo(x, audit_buf);
+ xfrm_audit_helper_usrinfo(auid, secid, audit_buf);
+ xfrm_audit_helper_sainfo(x, audit_buf);
+ audit_log_format(audit_buf, " res=%u", result);
+ audit_log_end(audit_buf);
+}
+EXPORT_SYMBOL_GPL(xfrm_audit_state_delete);
+
+void xfrm_audit_state_replay_overflow(struct xfrm_state *x,
+ struct sk_buff *skb)
+{
+ struct audit_buffer *audit_buf;
+ u32 spi;
+
+ audit_buf = xfrm_audit_start("SA-replay-overflow");
+ if (audit_buf == NULL)
+ return;
+ xfrm_audit_helper_pktinfo(skb, x->props.family, audit_buf);
+ /* don't record the sequence number because it's inherent in this kind
+ * of audit message */
spi = ntohl(x->id.spi);
audit_log_format(audit_buf, " spi=%u(0x%x)", spi, spi);
audit_log_end(audit_buf);
}
-EXPORT_SYMBOL_GPL(xfrm_audit_state_add);
+EXPORT_SYMBOL_GPL(xfrm_audit_state_replay_overflow);
-void
-xfrm_audit_state_delete(struct xfrm_state *x, int result, u32 auid, u32 sid)
+static void xfrm_audit_state_replay(struct xfrm_state *x,
+ struct sk_buff *skb, __be32 net_seq)
{
struct audit_buffer *audit_buf;
u32 spi;
- extern int audit_enabled;
- if (audit_enabled == 0)
- return;
- audit_buf = xfrm_audit_start(auid, sid);
+ audit_buf = xfrm_audit_start("SA-replayed-pkt");
if (audit_buf == NULL)
return;
- audit_log_format(audit_buf, " op=SAD-delete res=%u",result);
- xfrm_audit_common_stateinfo(x, audit_buf);
+ xfrm_audit_helper_pktinfo(skb, x->props.family, audit_buf);
spi = ntohl(x->id.spi);
- audit_log_format(audit_buf, " spi=%u(0x%x)", spi, spi);
+ audit_log_format(audit_buf, " spi=%u(0x%x) seqno=%u",
+ spi, spi, ntohl(net_seq));
audit_log_end(audit_buf);
}
-EXPORT_SYMBOL_GPL(xfrm_audit_state_delete);
+
+void xfrm_audit_state_notfound_simple(struct sk_buff *skb, u16 family)
+{
+ struct audit_buffer *audit_buf;
+
+ audit_buf = xfrm_audit_start("SA-notfound");
+ if (audit_buf == NULL)
+ return;
+ xfrm_audit_helper_pktinfo(skb, family, audit_buf);
+ audit_log_end(audit_buf);
+}
+EXPORT_SYMBOL_GPL(xfrm_audit_state_notfound_simple);
+
+void xfrm_audit_state_notfound(struct sk_buff *skb, u16 family,
+ __be32 net_spi, __be32 net_seq)
+{
+ struct audit_buffer *audit_buf;
+ u32 spi;
+
+ audit_buf = xfrm_audit_start("SA-notfound");
+ if (audit_buf == NULL)
+ return;
+ xfrm_audit_helper_pktinfo(skb, family, audit_buf);
+ spi = ntohl(net_spi);
+ audit_log_format(audit_buf, " spi=%u(0x%x) seqno=%u",
+ spi, spi, ntohl(net_seq));
+ audit_log_end(audit_buf);
+}
+EXPORT_SYMBOL_GPL(xfrm_audit_state_notfound);
+
+void xfrm_audit_state_icvfail(struct xfrm_state *x,
+ struct sk_buff *skb, u8 proto)
+{
+ struct audit_buffer *audit_buf;
+ __be32 net_spi;
+ __be32 net_seq;
+
+ audit_buf = xfrm_audit_start("SA-icv-failure");
+ if (audit_buf == NULL)
+ return;
+ xfrm_audit_helper_pktinfo(skb, x->props.family, audit_buf);
+ if (xfrm_parse_spi(skb, proto, &net_spi, &net_seq) == 0) {
+ u32 spi = ntohl(net_spi);
+ audit_log_format(audit_buf, " spi=%u(0x%x) seqno=%u",
+ spi, spi, ntohl(net_seq));
+ }
+ audit_log_end(audit_buf);
+}
+EXPORT_SYMBOL_GPL(xfrm_audit_state_icvfail);
#endif /* CONFIG_AUDITSYSCALL */
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index c4f6419b176..e0ccdf26781 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1043,7 +1043,7 @@ static struct xfrm_policy *xfrm_policy_construct(struct xfrm_userpolicy_info *p,
return xp;
error:
*errp = err;
- kfree(xp);
+ xfrm_policy_destroy(xp);
return NULL;
}
@@ -1986,8 +1986,8 @@ static inline size_t xfrm_sa_len(struct xfrm_state *x)
if (x->coaddr)
l += nla_total_size(sizeof(*x->coaddr));
- /* Must count this as this may become non-zero behind our back. */
- l += nla_total_size(sizeof(x->lastused));
+ /* Must count x->lastused as it may become non-zero behind our back. */
+ l += nla_total_size(sizeof(u64));
return l;
}
@@ -2420,7 +2420,7 @@ static void __exit xfrm_user_exit(void)
xfrm_unregister_km(&netlink_mgr);
rcu_assign_pointer(xfrm_nl, NULL);
synchronize_rcu();
- sock_release(nlsk->sk_socket);
+ netlink_kernel_release(nlsk);
}
module_init(xfrm_user_init);