Skip to content

Commit

Permalink
[XFRM]: Speed up xfrm_policy and xfrm_state walking
Browse files Browse the repository at this point in the history
Change xfrm_policy and xfrm_state walking algorithm from O(n^2) to O(n).
This is achieved adding the entries to one more list which is used
solely for walking the entries.

This also fixes some races where the dump can have duplicate or missing
entries when the SPD/SADB is modified during an ongoing dump.

Dumping SADB with 20000 entries using "time ip xfrm state" the sys
time dropped from 1.012s to 0.080s.

Signed-off-by: Timo Teras <timo.teras@iki.fi>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Timo Teras authored and David S. Miller committed Feb 29, 2008
1 parent 1e04d53 commit 4c563f7
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 85 deletions.
3 changes: 2 additions & 1 deletion include/linux/xfrm.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ enum
{
XFRM_POLICY_TYPE_MAIN = 0,
XFRM_POLICY_TYPE_SUB = 1,
XFRM_POLICY_TYPE_MAX = 2
XFRM_POLICY_TYPE_MAX = 2,
XFRM_POLICY_TYPE_ANY = 255
};

enum
Expand Down
52 changes: 50 additions & 2 deletions include/net/xfrm.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ extern struct mutex xfrm_cfg_mutex;
struct xfrm_state
{
/* Note: bydst is re-used during gc */
struct list_head all;
struct hlist_node bydst;
struct hlist_node bysrc;
struct hlist_node byspi;
Expand Down Expand Up @@ -424,6 +425,7 @@ struct xfrm_tmpl
struct xfrm_policy
{
struct xfrm_policy *next;
struct list_head bytype;
struct hlist_node bydst;
struct hlist_node byidx;

Expand Down Expand Up @@ -1160,6 +1162,18 @@ struct xfrm6_tunnel {
int priority;
};

struct xfrm_state_walk {
struct xfrm_state *state;
int count;
u8 proto;
};

struct xfrm_policy_walk {
struct xfrm_policy *policy;
int count;
u8 type, cur_type;
};

extern void xfrm_init(void);
extern void xfrm4_init(void);
extern void xfrm_state_init(void);
Expand All @@ -1184,7 +1198,23 @@ static inline void xfrm6_fini(void)
extern int xfrm_proc_init(void);
#endif

extern int xfrm_state_walk(u8 proto, int (*func)(struct xfrm_state *, int, void*), void *);
static inline void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto)
{
walk->proto = proto;
walk->state = NULL;
walk->count = 0;
}

static inline void xfrm_state_walk_done(struct xfrm_state_walk *walk)
{
if (walk->state != NULL) {
xfrm_state_put(walk->state);
walk->state = NULL;
}
}

extern int xfrm_state_walk(struct xfrm_state_walk *walk,
int (*func)(struct xfrm_state *, int, void*), void *);
extern struct xfrm_state *xfrm_state_alloc(void);
extern struct xfrm_state *xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
struct flowi *fl, struct xfrm_tmpl *tmpl,
Expand Down Expand Up @@ -1306,7 +1336,25 @@ static inline int xfrm4_udp_encap_rcv(struct sock *sk, struct sk_buff *skb)
#endif

struct xfrm_policy *xfrm_policy_alloc(gfp_t gfp);
extern int xfrm_policy_walk(u8 type, int (*func)(struct xfrm_policy *, int, int, void*), void *);

static inline void xfrm_policy_walk_init(struct xfrm_policy_walk *walk, u8 type)
{
walk->cur_type = XFRM_POLICY_TYPE_MAIN;
walk->type = type;
walk->policy = NULL;
walk->count = 0;
}

static inline void xfrm_policy_walk_done(struct xfrm_policy_walk *walk)
{
if (walk->policy != NULL) {
xfrm_pol_put(walk->policy);
walk->policy = NULL;
}
}

extern int xfrm_policy_walk(struct xfrm_policy_walk *walk,
int (*func)(struct xfrm_policy *, int, int, void*), void *);
int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl);
struct xfrm_policy *xfrm_policy_bysel_ctx(u8 type, int dir,
struct xfrm_selector *sel,
Expand Down
24 changes: 20 additions & 4 deletions net/key/af_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -1742,12 +1742,18 @@ static int pfkey_dump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr
{
u8 proto;
struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
struct xfrm_state_walk walk;
int rc;

proto = pfkey_satype2proto(hdr->sadb_msg_satype);
if (proto == 0)
return -EINVAL;

return xfrm_state_walk(proto, dump_sa, &data);
xfrm_state_walk_init(&walk, proto);
rc = xfrm_state_walk(&walk, dump_sa, &data);
xfrm_state_walk_done(&walk);

return rc;
}

static int pfkey_promisc(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
Expand Down Expand Up @@ -1780,16 +1786,20 @@ static int check_reqid(struct xfrm_policy *xp, int dir, int count, void *ptr)

static u32 gen_reqid(void)
{
struct xfrm_policy_walk walk;
u32 start;
int rc;
static u32 reqid = IPSEC_MANUAL_REQID_MAX;

start = reqid;
do {
++reqid;
if (reqid == 0)
reqid = IPSEC_MANUAL_REQID_MAX+1;
if (xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, check_reqid,
(void*)&reqid) != -EEXIST)
xfrm_policy_walk_init(&walk, XFRM_POLICY_TYPE_MAIN);
rc = xfrm_policy_walk(&walk, check_reqid, (void*)&reqid);
xfrm_policy_walk_done(&walk);
if (rc != -EEXIST)
return reqid;
} while (reqid != start);
return 0;
Expand Down Expand Up @@ -2665,8 +2675,14 @@ static int dump_sp(struct xfrm_policy *xp, int dir, int count, void *ptr)
static int pfkey_spddump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
{
struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
struct xfrm_policy_walk walk;
int rc;

xfrm_policy_walk_init(&walk, XFRM_POLICY_TYPE_MAIN);
rc = xfrm_policy_walk(&walk, dump_sp, &data);
xfrm_policy_walk_done(&walk);

return xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, dump_sp, &data);
return rc;
}

static int key_notify_policy_flush(struct km_event *c)
Expand Down
79 changes: 46 additions & 33 deletions net/xfrm/xfrm_policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ EXPORT_SYMBOL(xfrm_cfg_mutex);

static DEFINE_RWLOCK(xfrm_policy_lock);

static struct list_head xfrm_policy_bytype[XFRM_POLICY_TYPE_MAX];
unsigned int xfrm_policy_count[XFRM_POLICY_MAX*2];
EXPORT_SYMBOL(xfrm_policy_count);

Expand Down Expand Up @@ -208,6 +209,7 @@ struct xfrm_policy *xfrm_policy_alloc(gfp_t gfp)
policy = kzalloc(sizeof(struct xfrm_policy), gfp);

if (policy) {
INIT_LIST_HEAD(&policy->bytype);
INIT_HLIST_NODE(&policy->bydst);
INIT_HLIST_NODE(&policy->byidx);
rwlock_init(&policy->lock);
Expand All @@ -230,6 +232,10 @@ void xfrm_policy_destroy(struct xfrm_policy *policy)
if (del_timer(&policy->timer))
BUG();

write_lock_bh(&xfrm_policy_lock);
list_del(&policy->bytype);
write_unlock_bh(&xfrm_policy_lock);

security_xfrm_policy_free(policy);
kfree(policy);
}
Expand Down Expand Up @@ -584,6 +590,7 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
policy->curlft.use_time = 0;
if (!mod_timer(&policy->timer, jiffies + HZ))
xfrm_pol_hold(policy);
list_add_tail(&policy->bytype, &xfrm_policy_bytype[policy->type]);
write_unlock_bh(&xfrm_policy_lock);

if (delpol)
Expand Down Expand Up @@ -822,57 +829,60 @@ int xfrm_policy_flush(u8 type, struct xfrm_audit *audit_info)
}
EXPORT_SYMBOL(xfrm_policy_flush);

int xfrm_policy_walk(u8 type, int (*func)(struct xfrm_policy *, int, int, void*),
int xfrm_policy_walk(struct xfrm_policy_walk *walk,
int (*func)(struct xfrm_policy *, int, int, void*),
void *data)
{
struct xfrm_policy *pol, *last = NULL;
struct hlist_node *entry;
int dir, last_dir = 0, count, error;
struct xfrm_policy *old, *pol, *last = NULL;
int error = 0;

if (walk->type >= XFRM_POLICY_TYPE_MAX &&
walk->type != XFRM_POLICY_TYPE_ANY)
return -EINVAL;

if (walk->policy == NULL && walk->count != 0)
return 0;

old = pol = walk->policy;
walk->policy = NULL;
read_lock_bh(&xfrm_policy_lock);
count = 0;

for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
struct hlist_head *table = xfrm_policy_bydst[dir].table;
int i;
for (; walk->cur_type < XFRM_POLICY_TYPE_MAX; walk->cur_type++) {
if (walk->type != walk->cur_type &&
walk->type != XFRM_POLICY_TYPE_ANY)
continue;

hlist_for_each_entry(pol, entry,
&xfrm_policy_inexact[dir], bydst) {
if (pol->type != type)
if (pol == NULL) {
pol = list_first_entry(&xfrm_policy_bytype[walk->cur_type],
struct xfrm_policy, bytype);
}
list_for_each_entry_from(pol, &xfrm_policy_bytype[walk->cur_type], bytype) {
if (pol->dead)
continue;
if (last) {
error = func(last, last_dir % XFRM_POLICY_MAX,
count, data);
if (error)
error = func(last, xfrm_policy_id2dir(last->index),
walk->count, data);
if (error) {
xfrm_pol_hold(last);
walk->policy = last;
goto out;
}
last = pol;
last_dir = dir;
count++;
}
for (i = xfrm_policy_bydst[dir].hmask; i >= 0; i--) {
hlist_for_each_entry(pol, entry, table + i, bydst) {
if (pol->type != type)
continue;
if (last) {
error = func(last, last_dir % XFRM_POLICY_MAX,
count, data);
if (error)
goto out;
}
last = pol;
last_dir = dir;
count++;
}
last = pol;
walk->count++;
}
pol = NULL;
}
if (count == 0) {
if (walk->count == 0) {
error = -ENOENT;
goto out;
}
error = func(last, last_dir % XFRM_POLICY_MAX, 0, data);
if (last)
error = func(last, xfrm_policy_id2dir(last->index), 0, data);
out:
read_unlock_bh(&xfrm_policy_lock);
if (old != NULL)
xfrm_pol_put(old);
return error;
}
EXPORT_SYMBOL(xfrm_policy_walk);
Expand Down Expand Up @@ -2365,6 +2375,9 @@ static void __init xfrm_policy_init(void)
panic("XFRM: failed to allocate bydst hash\n");
}

for (dir = 0; dir < XFRM_POLICY_TYPE_MAX; dir++)
INIT_LIST_HEAD(&xfrm_policy_bytype[dir]);

INIT_WORK(&xfrm_policy_gc_work, xfrm_policy_gc_task);
register_netdevice_notifier(&xfrm_dev_notifier);
}
Expand Down
53 changes: 36 additions & 17 deletions net/xfrm/xfrm_state.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ static DEFINE_SPINLOCK(xfrm_state_lock);
* Main use is finding SA after policy selected tunnel or transport mode.
* Also, it can be used by ah/esp icmp error handler to find offending SA.
*/
static LIST_HEAD(xfrm_state_all);
static struct hlist_head *xfrm_state_bydst __read_mostly;
static struct hlist_head *xfrm_state_bysrc __read_mostly;
static struct hlist_head *xfrm_state_byspi __read_mostly;
Expand Down Expand Up @@ -510,6 +511,7 @@ struct xfrm_state *xfrm_state_alloc(void)
if (x) {
atomic_set(&x->refcnt, 1);
atomic_set(&x->tunnel_users, 0);
INIT_LIST_HEAD(&x->all);
INIT_HLIST_NODE(&x->bydst);
INIT_HLIST_NODE(&x->bysrc);
INIT_HLIST_NODE(&x->byspi);
Expand All @@ -533,6 +535,10 @@ void __xfrm_state_destroy(struct xfrm_state *x)
{
BUG_TRAP(x->km.state == XFRM_STATE_DEAD);

spin_lock_bh(&xfrm_state_lock);
list_del(&x->all);
spin_unlock_bh(&xfrm_state_lock);

spin_lock_bh(&xfrm_state_gc_lock);
hlist_add_head(&x->bydst, &xfrm_state_gc_list);
spin_unlock_bh(&xfrm_state_gc_lock);
Expand Down Expand Up @@ -909,6 +915,8 @@ static void __xfrm_state_insert(struct xfrm_state *x)

x->genid = ++xfrm_state_genid;

list_add_tail(&x->all, &xfrm_state_all);

h = xfrm_dst_hash(&x->id.daddr, &x->props.saddr,
x->props.reqid, x->props.family);
hlist_add_head(&x->bydst, xfrm_state_bydst+h);
Expand Down Expand Up @@ -1518,36 +1526,47 @@ int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high)
}
EXPORT_SYMBOL(xfrm_alloc_spi);

int xfrm_state_walk(u8 proto, int (*func)(struct xfrm_state *, int, void*),
int xfrm_state_walk(struct xfrm_state_walk *walk,
int (*func)(struct xfrm_state *, int, void*),
void *data)
{
int i;
struct xfrm_state *x, *last = NULL;
struct hlist_node *entry;
int count = 0;
struct xfrm_state *old, *x, *last = NULL;
int err = 0;

if (walk->state == NULL && walk->count != 0)
return 0;

old = x = walk->state;
walk->state = NULL;
spin_lock_bh(&xfrm_state_lock);
for (i = 0; i <= xfrm_state_hmask; i++) {
hlist_for_each_entry(x, entry, xfrm_state_bydst+i, bydst) {
if (!xfrm_id_proto_match(x->id.proto, proto))
continue;
if (last) {
err = func(last, count, data);
if (err)
goto out;
if (x == NULL)
x = list_first_entry(&xfrm_state_all, struct xfrm_state, all);
list_for_each_entry_from(x, &xfrm_state_all, all) {
if (x->km.state == XFRM_STATE_DEAD)
continue;
if (!xfrm_id_proto_match(x->id.proto, walk->proto))
continue;
if (last) {
err = func(last, walk->count, data);
if (err) {
xfrm_state_hold(last);
walk->state = last;
goto out;
}
last = x;
count++;
}
last = x;
walk->count++;
}
if (count == 0) {
if (walk->count == 0) {
err = -ENOENT;
goto out;
}
err = func(last, 0, data);
if (last)
err = func(last, 0, data);
out:
spin_unlock_bh(&xfrm_state_lock);
if (old != NULL)
xfrm_state_put(old);
return err;
}
EXPORT_SYMBOL(xfrm_state_walk);
Expand Down
Loading

0 comments on commit 4c563f7

Please sign in to comment.