ipsec: add support of limited SA dump
authorNicolas Dichtel <nicolas.dichtel@6wind.com>
Fri, 14 Feb 2014 14:30:36 +0000 (15:30 +0100)
committerSteffen Klassert <steffen.klassert@secunet.com>
Mon, 17 Feb 2014 06:18:19 +0000 (07:18 +0100)
The goal of this patch is to allow userland to dump only a part of SA by
specifying a filter during the dump.
The kernel is in charge to filter SA, this avoids to generate useless netlink
traffic (it save also some cpu cycles). This is particularly useful when there
is a big number of SA set on the system.

Note that I removed the union in struct xfrm_state_walk to fix a problem on arm.
struct netlink_callback->args is defined as a array of 6 long and the first long
is used in xfrm code to flag the cb as initialized. Hence, we must have:
sizeof(struct xfrm_state_walk) <= sizeof(long) * 5.
With the union, it was false on arm (sizeof(struct xfrm_state_walk) was
sizeof(long) * 7), due to the padding.
In fact, whatever the arch is, this union seems useless, there will be always
padding after it. Removing it will not increase the size of this struct (and
reduce it on arm).

Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
include/net/xfrm.h
include/uapi/linux/pfkeyv2.h
include/uapi/linux/xfrm.h
net/key/af_key.c
net/xfrm/xfrm_state.c
net/xfrm/xfrm_user.c

index 5313ccfdeedf901e95c8f4e1f644f7492de0ce9b..45332acac02248261bcbc147f0142ed1ae57fb6c 100644 (file)
 struct xfrm_state_walk {
        struct list_head        all;
        u8                      state;
-       union {
-               u8              dying;
-               u8              proto;
-       };
+       u8                      dying;
+       u8                      proto;
        u32                     seq;
+       struct xfrm_filter      *filter;
 };
 
 /* Full description of state of transformer. */
@@ -1406,7 +1405,8 @@ static inline void xfrm_sysctl_fini(struct net *net)
 }
 #endif
 
-void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto);
+void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto,
+                         struct xfrm_filter *filter);
 int xfrm_state_walk(struct net *net, struct xfrm_state_walk *walk,
                    int (*func)(struct xfrm_state *, int, void*), void *);
 void xfrm_state_walk_done(struct xfrm_state_walk *walk, struct net *net);
index 0b80c806631fc12d26a11d9f6e5d1f3b65e9066a..ada7f0171cccd3174810d1c9c0d6ea566c558c0a 100644 (file)
@@ -235,6 +235,18 @@ struct sadb_x_kmaddress {
 } __attribute__((packed));
 /* sizeof(struct sadb_x_kmaddress) == 8 */
 
+/* To specify the SA dump filter */
+struct sadb_x_filter {
+       __u16   sadb_x_filter_len;
+       __u16   sadb_x_filter_exttype;
+       __u32   sadb_x_filter_saddr[4];
+       __u32   sadb_x_filter_daddr[4];
+       __u16   sadb_x_filter_family;
+       __u8    sadb_x_filter_splen;
+       __u8    sadb_x_filter_dplen;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_filter) == 40 */
+
 /* Message types */
 #define SADB_RESERVED          0
 #define SADB_GETSPI            1
@@ -358,7 +370,8 @@ struct sadb_x_kmaddress {
 #define SADB_X_EXT_SEC_CTX             24
 /* Used with MIGRATE to pass @ to IKE for negotiation */
 #define SADB_X_EXT_KMADDRESS           25
-#define SADB_EXT_MAX                   25
+#define SADB_X_EXT_FILTER              26
+#define SADB_EXT_MAX                   26
 
 /* Identity Extension values */
 #define SADB_IDENTTYPE_RESERVED        0
index a8cd6a4a297070052a0e3deddafd9a05bca62c91..6550c679584f46b39f1a2f22733460ba294b66f7 100644 (file)
@@ -298,6 +298,8 @@ enum xfrm_attr_type_t {
        XFRMA_TFCPAD,           /* __u32 */
        XFRMA_REPLAY_ESN_VAL,   /* struct xfrm_replay_esn */
        XFRMA_SA_EXTRA_FLAGS,   /* __u32 */
+       XFRMA_PROTO,            /* __u8 */
+       XFRMA_FILTER,           /* struct xfrm_filter */
        __XFRMA_MAX
 
 #define XFRMA_MAX (__XFRMA_MAX - 1)
@@ -474,6 +476,14 @@ struct xfrm_user_mapping {
        __be16                          new_sport;
 };
 
+struct xfrm_filter {
+       xfrm_address_t                  saddr;
+       xfrm_address_t                  daddr;
+       __u16                           family;
+       __u8                            splen;
+       __u8                            dplen;
+};
+
 #ifndef __KERNEL__
 /* backwards compatibility for userspace */
 #define XFRMGRP_ACQUIRE                1
index e1c69d024197bddc61664e7608ab1ac51022d535..f0879c19f45259b45b68f1ac64b510f898fe5aa0 100644 (file)
@@ -1798,6 +1798,7 @@ static void pfkey_dump_sa_done(struct pfkey_sock *pfk)
 static int pfkey_dump(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr, void * const *ext_hdrs)
 {
        u8 proto;
+       struct xfrm_filter *filter = NULL;
        struct pfkey_sock *pfk = pfkey_sk(sk);
 
        if (pfk->dump.dump != NULL)
@@ -1807,11 +1808,27 @@ static int pfkey_dump(struct sock *sk, struct sk_buff *skb, const struct sadb_ms
        if (proto == 0)
                return -EINVAL;
 
+       if (ext_hdrs[SADB_X_EXT_FILTER - 1]) {
+               struct sadb_x_filter *xfilter = ext_hdrs[SADB_X_EXT_FILTER - 1];
+
+               filter = kmalloc(sizeof(*filter), GFP_KERNEL);
+               if (filter == NULL)
+                       return -ENOMEM;
+
+               memcpy(&filter->saddr, &xfilter->sadb_x_filter_saddr,
+                      sizeof(xfrm_address_t));
+               memcpy(&filter->daddr, &xfilter->sadb_x_filter_daddr,
+                      sizeof(xfrm_address_t));
+               filter->family = xfilter->sadb_x_filter_family;
+               filter->splen = xfilter->sadb_x_filter_splen;
+               filter->dplen = xfilter->sadb_x_filter_dplen;
+       }
+
        pfk->dump.msg_version = hdr->sadb_msg_version;
        pfk->dump.msg_portid = hdr->sadb_msg_pid;
        pfk->dump.dump = pfkey_dump_sa;
        pfk->dump.done = pfkey_dump_sa_done;
-       xfrm_state_walk_init(&pfk->dump.u.state, proto);
+       xfrm_state_walk_init(&pfk->dump.u.state, proto, filter);
 
        return pfkey_do_dump(pfk);
 }
index 0bf12f665b9bbef941ca66a1718ea28854da1460..a750901ac3dbc09e06655f96482a94af42b70ca9 100644 (file)
@@ -1603,6 +1603,23 @@ unlock:
 }
 EXPORT_SYMBOL(xfrm_alloc_spi);
 
+static bool __xfrm_state_filter_match(struct xfrm_state *x,
+                                     struct xfrm_filter *filter)
+{
+       if (filter) {
+               if ((filter->family == AF_INET ||
+                    filter->family == AF_INET6) &&
+                   x->props.family != filter->family)
+                       return false;
+
+               return addr_match(&x->props.saddr, &filter->saddr,
+                                 filter->splen) &&
+                      addr_match(&x->id.daddr, &filter->daddr,
+                                 filter->dplen);
+       }
+       return true;
+}
+
 int xfrm_state_walk(struct net *net, struct xfrm_state_walk *walk,
                    int (*func)(struct xfrm_state *, int, void*),
                    void *data)
@@ -1625,6 +1642,8 @@ int xfrm_state_walk(struct net *net, struct xfrm_state_walk *walk,
                state = container_of(x, struct xfrm_state, km);
                if (!xfrm_id_proto_match(state->id.proto, walk->proto))
                        continue;
+               if (!__xfrm_state_filter_match(state, walk->filter))
+                       continue;
                err = func(state, walk->seq, data);
                if (err) {
                        list_move_tail(&walk->all, &x->all);
@@ -1643,17 +1662,21 @@ out:
 }
 EXPORT_SYMBOL(xfrm_state_walk);
 
-void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto)
+void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto,
+                         struct xfrm_filter *filter)
 {
        INIT_LIST_HEAD(&walk->all);
        walk->proto = proto;
        walk->state = XFRM_STATE_DEAD;
        walk->seq = 0;
+       walk->filter = filter;
 }
 EXPORT_SYMBOL(xfrm_state_walk_init);
 
 void xfrm_state_walk_done(struct xfrm_state_walk *walk, struct net *net)
 {
+       kfree(walk->filter);
+
        if (list_empty(&walk->all))
                return;
 
index d7694f25829460870ec6c92f4feb26a3c23d5740..023e5e7ea4c6fc762228772cc5211d94f8b9426d 100644 (file)
@@ -887,6 +887,7 @@ static int xfrm_dump_sa_done(struct netlink_callback *cb)
        return 0;
 }
 
+static const struct nla_policy xfrma_policy[XFRMA_MAX+1];
 static int xfrm_dump_sa(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net *net = sock_net(skb->sk);
@@ -902,8 +903,31 @@ static int xfrm_dump_sa(struct sk_buff *skb, struct netlink_callback *cb)
        info.nlmsg_flags = NLM_F_MULTI;
 
        if (!cb->args[0]) {
+               struct nlattr *attrs[XFRMA_MAX+1];
+               struct xfrm_filter *filter = NULL;
+               u8 proto = 0;
+               int err;
+
                cb->args[0] = 1;
-               xfrm_state_walk_init(walk, 0);
+
+               err = nlmsg_parse(cb->nlh, 0, attrs, XFRMA_MAX,
+                                 xfrma_policy);
+               if (err < 0)
+                       return err;
+
+               if (attrs[XFRMA_FILTER]) {
+                       filter = kmalloc(sizeof(*filter), GFP_KERNEL);
+                       if (filter == NULL)
+                               return -ENOMEM;
+
+                       memcpy(filter, nla_data(attrs[XFRMA_FILTER]),
+                              sizeof(*filter));
+               }
+
+               if (attrs[XFRMA_PROTO])
+                       proto = nla_get_u8(attrs[XFRMA_PROTO]);
+
+               xfrm_state_walk_init(walk, proto, filter);
        }
 
        (void) xfrm_state_walk(net, walk, dump_one_state, &info);
@@ -2309,6 +2333,8 @@ static const struct nla_policy xfrma_policy[XFRMA_MAX+1] = {
        [XFRMA_TFCPAD]          = { .type = NLA_U32 },
        [XFRMA_REPLAY_ESN_VAL]  = { .len = sizeof(struct xfrm_replay_state_esn) },
        [XFRMA_SA_EXTRA_FLAGS]  = { .type = NLA_U32 },
+       [XFRMA_PROTO]           = { .type = NLA_U8 },
+       [XFRMA_FILTER]          = { .len = sizeof(struct xfrm_filter) },
 };
 
 static const struct xfrm_link {