Commit 8a568f5b authored by Alexey Kuznetsov's avatar Alexey Kuznetsov Committed by David S. Miller

[IPSEC]: Policy timeout and pfkey acquire fixes.

- Implement policy timeouts.
- Make PF_KEY return proper error from KM acquire.
parent 0466165b
...@@ -195,6 +195,7 @@ struct xfrm_policy ...@@ -195,6 +195,7 @@ struct xfrm_policy
/* This lock only affects elements except for entry. */ /* This lock only affects elements except for entry. */
rwlock_t lock; rwlock_t lock;
atomic_t refcnt; atomic_t refcnt;
struct timer_list timer;
u32 priority; u32 priority;
u32 index; u32 index;
......
...@@ -204,6 +204,50 @@ void xfrm_put_type(struct xfrm_type *type) ...@@ -204,6 +204,50 @@ void xfrm_put_type(struct xfrm_type *type)
__MOD_DEC_USE_COUNT(type->owner); __MOD_DEC_USE_COUNT(type->owner);
} }
static inline unsigned long make_jiffies(long secs)
{
if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
return MAX_SCHEDULE_TIMEOUT-1;
else
return secs*HZ;
}
static void xfrm_policy_timer(unsigned long data)
{
struct xfrm_policy *xp = (struct xfrm_policy*)data;
unsigned long now = (unsigned long)xtime.tv_sec;
long next = LONG_MAX;
if (xp->dead)
goto out;
if (xp->lft.hard_add_expires_seconds) {
long tmo = xp->lft.hard_add_expires_seconds +
xp->curlft.add_time - now;
if (tmo <= 0)
goto expired;
if (tmo < next)
next = tmo;
}
if (next != LONG_MAX &&
!mod_timer(&xp->timer, jiffies + make_jiffies(next)))
atomic_inc(&xp->refcnt);
out:
xfrm_pol_put(xp);
return;
expired:
xfrm_pol_put(xp);
/* Not 100% correct. id can be recycled in theory */
xp = xfrm_policy_byid(0, xp->index, 1);
if (xp) {
xfrm_policy_kill(xp);
xfrm_pol_put(xp);
}
}
/* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2 /* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2
* SPD calls. * SPD calls.
...@@ -219,6 +263,9 @@ struct xfrm_policy *xfrm_policy_alloc(int gfp) ...@@ -219,6 +263,9 @@ struct xfrm_policy *xfrm_policy_alloc(int gfp)
memset(policy, 0, sizeof(struct xfrm_policy)); memset(policy, 0, sizeof(struct xfrm_policy));
atomic_set(&policy->refcnt, 1); atomic_set(&policy->refcnt, 1);
policy->lock = RW_LOCK_UNLOCKED; policy->lock = RW_LOCK_UNLOCKED;
init_timer(&policy->timer);
policy->timer.data = (unsigned long)policy;
policy->timer.function = xfrm_policy_timer;
} }
return policy; return policy;
} }
...@@ -233,6 +280,9 @@ void __xfrm_policy_destroy(struct xfrm_policy *policy) ...@@ -233,6 +280,9 @@ void __xfrm_policy_destroy(struct xfrm_policy *policy)
if (policy->bundles) if (policy->bundles)
BUG(); BUG();
if (del_timer(&policy->timer))
BUG();
kfree(policy); kfree(policy);
} }
...@@ -255,6 +305,9 @@ void xfrm_policy_kill(struct xfrm_policy *policy) ...@@ -255,6 +305,9 @@ void xfrm_policy_kill(struct xfrm_policy *policy)
dst_free(dst); dst_free(dst);
} }
if (del_timer(&policy->timer))
atomic_dec(&policy->refcnt);
out: out:
write_unlock_bh(&policy->lock); write_unlock_bh(&policy->lock);
} }
...@@ -302,6 +355,9 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl) ...@@ -302,6 +355,9 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
policy->index = pol ? pol->index : xfrm_gen_index(dir); policy->index = pol ? pol->index : xfrm_gen_index(dir);
policy->curlft.add_time = (unsigned long)xtime.tv_sec; policy->curlft.add_time = (unsigned long)xtime.tv_sec;
policy->curlft.use_time = 0; policy->curlft.use_time = 0;
if (policy->lft.hard_add_expires_seconds &&
!mod_timer(&policy->timer, jiffies + HZ))
atomic_inc(&policy->refcnt);
write_unlock_bh(&xfrm_policy_lock); write_unlock_bh(&xfrm_policy_lock);
if (pol) { if (pol) {
...@@ -380,7 +436,7 @@ int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*), ...@@ -380,7 +436,7 @@ int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*),
int count = 0; int count = 0;
int error = 0; int error = 0;
read_lock(&xfrm_policy_lock); read_lock_bh(&xfrm_policy_lock);
for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) { for (dir = 0; dir < 2*XFRM_POLICY_MAX; dir++) {
for (xp = xfrm_policy_list[dir]; xp; xp = xp->next) for (xp = xfrm_policy_list[dir]; xp; xp = xp->next)
count++; count++;
...@@ -400,7 +456,7 @@ int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*), ...@@ -400,7 +456,7 @@ int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*),
} }
out: out:
read_unlock(&xfrm_policy_lock); read_unlock_bh(&xfrm_policy_lock);
return error; return error;
} }
...@@ -411,7 +467,7 @@ struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl) ...@@ -411,7 +467,7 @@ struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl)
{ {
struct xfrm_policy *pol; struct xfrm_policy *pol;
read_lock(&xfrm_policy_lock); read_lock_bh(&xfrm_policy_lock);
for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) { for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) {
struct xfrm_selector *sel = &pol->selector; struct xfrm_selector *sel = &pol->selector;
...@@ -420,7 +476,7 @@ struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl) ...@@ -420,7 +476,7 @@ struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl)
break; break;
} }
} }
read_unlock(&xfrm_policy_lock); read_unlock_bh(&xfrm_policy_lock);
return pol; return pol;
} }
...@@ -428,14 +484,14 @@ struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi ...@@ -428,14 +484,14 @@ struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi
{ {
struct xfrm_policy *pol; struct xfrm_policy *pol;
read_lock(&xfrm_policy_lock); read_lock_bh(&xfrm_policy_lock);
if ((pol = sk->policy[dir]) != NULL) { if ((pol = sk->policy[dir]) != NULL) {
if (xfrm4_selector_match(&pol->selector, fl)) if (xfrm4_selector_match(&pol->selector, fl))
atomic_inc(&pol->refcnt); atomic_inc(&pol->refcnt);
else else
pol = NULL; pol = NULL;
} }
read_unlock(&xfrm_policy_lock); read_unlock_bh(&xfrm_policy_lock);
return pol; return pol;
} }
...@@ -727,7 +783,6 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, ...@@ -727,7 +783,6 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
return 0; return 0;
} }
if (!policy->curlft.use_time)
policy->curlft.use_time = (unsigned long)xtime.tv_sec; policy->curlft.use_time = (unsigned long)xtime.tv_sec;
switch (policy->action) { switch (policy->action) {
...@@ -936,7 +991,6 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) ...@@ -936,7 +991,6 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb)
if (!pol) if (!pol)
return 1; return 1;
if (!pol->curlft.use_time)
pol->curlft.use_time = (unsigned long)xtime.tv_sec; pol->curlft.use_time = (unsigned long)xtime.tv_sec;
if (pol->action == XFRM_POLICY_ALLOW) { if (pol->action == XFRM_POLICY_ALLOW) {
......
...@@ -28,7 +28,7 @@ DECLARE_WAIT_QUEUE_HEAD(km_waitq); ...@@ -28,7 +28,7 @@ DECLARE_WAIT_QUEUE_HEAD(km_waitq);
static void __xfrm_state_delete(struct xfrm_state *x); static void __xfrm_state_delete(struct xfrm_state *x);
unsigned long make_jiffies(long secs) static inline unsigned long make_jiffies(long secs)
{ {
if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ) if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
return MAX_SCHEDULE_TIMEOUT-1; return MAX_SCHEDULE_TIMEOUT-1;
...@@ -92,6 +92,13 @@ static void xfrm_timer_handler(unsigned long data) ...@@ -92,6 +92,13 @@ static void xfrm_timer_handler(unsigned long data)
goto out; goto out;
expired: expired:
if (x->km.state == XFRM_STATE_ACQ && x->id.spi == 0) {
x->km.state = XFRM_STATE_EXPIRED;
wake_up(&km_waitq);
next = 2;
goto resched;
}
if (x->id.spi != 0)
km_expired(x); km_expired(x);
__xfrm_state_delete(x); __xfrm_state_delete(x);
...@@ -298,11 +305,13 @@ xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl, ...@@ -298,11 +305,13 @@ xfrm_state_find(u32 daddr, u32 saddr, struct flowi *fl, struct xfrm_tmpl *tmpl,
x->km.state = XFRM_STATE_DEAD; x->km.state = XFRM_STATE_DEAD;
xfrm_state_put(x); xfrm_state_put(x);
x = NULL; x = NULL;
error = 1;
} }
} }
spin_unlock_bh(&xfrm_state_lock); spin_unlock_bh(&xfrm_state_lock);
if (!x) if (!x)
*err = acquire_in_progress ? -EAGAIN : -ENOMEM; *err = acquire_in_progress ? -EAGAIN :
(error ? -ESRCH : -ENOMEM);
return x; return x;
} }
...@@ -612,6 +621,7 @@ void km_expired(struct xfrm_state *x) ...@@ -612,6 +621,7 @@ void km_expired(struct xfrm_state *x)
list_for_each_entry(km, &xfrm_km_list, list) list_for_each_entry(km, &xfrm_km_list, list)
km->notify(x, 1); km->notify(x, 1);
read_unlock(&xfrm_km_lock); read_unlock(&xfrm_km_lock);
wake_up(&km_waitq);
} }
int km_query(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *pol) int km_query(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *pol)
......
...@@ -196,9 +196,11 @@ static int pfkey_release(struct socket *sock) ...@@ -196,9 +196,11 @@ static int pfkey_release(struct socket *sock)
return 0; return 0;
} }
static void pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2, static int pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2,
int allocation, struct sock *sk) int allocation, struct sock *sk)
{ {
int err = -ENOBUFS;
sock_hold(sk); sock_hold(sk);
if (*skb2 == NULL) { if (*skb2 == NULL) {
if (atomic_read(&skb->users) != 1) { if (atomic_read(&skb->users) != 1) {
...@@ -215,9 +217,11 @@ static void pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2, ...@@ -215,9 +217,11 @@ static void pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2,
skb_queue_tail(&sk->receive_queue, *skb2); skb_queue_tail(&sk->receive_queue, *skb2);
sk->data_ready(sk, (*skb2)->len); sk->data_ready(sk, (*skb2)->len);
*skb2 = NULL; *skb2 = NULL;
err = 0;
} }
} }
sock_put(sk); sock_put(sk);
return err;
} }
/* Send SKB to all pfkey sockets matching selected criteria. */ /* Send SKB to all pfkey sockets matching selected criteria. */
...@@ -225,21 +229,23 @@ static void pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2, ...@@ -225,21 +229,23 @@ static void pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2,
#define BROADCAST_ONE 1 #define BROADCAST_ONE 1
#define BROADCAST_REGISTERED 2 #define BROADCAST_REGISTERED 2
#define BROADCAST_PROMISC_ONLY 4 #define BROADCAST_PROMISC_ONLY 4
static void pfkey_broadcast(struct sk_buff *skb, int allocation, static int pfkey_broadcast(struct sk_buff *skb, int allocation,
int broadcast_flags, struct sock *one_sk) int broadcast_flags, struct sock *one_sk)
{ {
struct sock *sk; struct sock *sk;
struct sk_buff *skb2 = NULL; struct sk_buff *skb2 = NULL;
int err = -ESRCH;
/* XXX Do we need something like netlink_overrun? I think /* XXX Do we need something like netlink_overrun? I think
* XXX PF_KEY socket apps will not mind current behavior. * XXX PF_KEY socket apps will not mind current behavior.
*/ */
if (!skb) if (!skb)
return; return -ENOMEM;
pfkey_lock_table(); pfkey_lock_table();
for (sk = pfkey_table; sk; sk = sk->next) { for (sk = pfkey_table; sk; sk = sk->next) {
struct pfkey_opt *pfk = pfkey_sk(sk); struct pfkey_opt *pfk = pfkey_sk(sk);
int err2;
/* Yes, it means that if you are meant to receive this /* Yes, it means that if you are meant to receive this
* pfkey message you receive it twice as promiscuous * pfkey message you receive it twice as promiscuous
...@@ -261,16 +267,22 @@ static void pfkey_broadcast(struct sk_buff *skb, int allocation, ...@@ -261,16 +267,22 @@ static void pfkey_broadcast(struct sk_buff *skb, int allocation,
continue; continue;
} }
pfkey_broadcast_one(skb, &skb2, allocation, sk); err2 = pfkey_broadcast_one(skb, &skb2, allocation, sk);
/* Error is cleare after succecful sending to at least one
* registered KM */
if ((broadcast_flags & BROADCAST_REGISTERED) && err)
err = err2;
} }
pfkey_unlock_table(); pfkey_unlock_table();
if (one_sk != NULL) if (one_sk != NULL)
pfkey_broadcast_one(skb, &skb2, allocation, one_sk); err = pfkey_broadcast_one(skb, &skb2, allocation, one_sk);
if (skb2) if (skb2)
kfree_skb(skb2); kfree_skb(skb2);
kfree_skb(skb); kfree_skb(skb);
return err;
} }
static inline void pfkey_hdr_dup(struct sadb_msg *new, struct sadb_msg *orig) static inline void pfkey_hdr_dup(struct sadb_msg *new, struct sadb_msg *orig)
...@@ -1101,8 +1113,12 @@ static int pfkey_acquire(struct sock *sk, struct sk_buff *skb, struct sadb_msg * ...@@ -1101,8 +1113,12 @@ static int pfkey_acquire(struct sock *sk, struct sk_buff *skb, struct sadb_msg *
if (x == NULL) if (x == NULL)
return 0; return 0;
if (x->km.state == XFRM_STATE_ACQ) spin_lock_bh(&x->lock);
xfrm_state_delete(x); if (x->km.state == XFRM_STATE_ACQ) {
x->km.state = XFRM_STATE_ERROR;
wake_up(&km_waitq);
}
spin_unlock_bh(&x->lock);
xfrm_state_put(x); xfrm_state_put(x);
return 0; return 0;
} }
...@@ -1783,14 +1799,10 @@ static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h ...@@ -1783,14 +1799,10 @@ static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h
struct sk_buff *out_skb; struct sk_buff *out_skb;
struct sadb_msg *out_hdr; struct sadb_msg *out_hdr;
if (!ext_hdrs[SADB_X_EXT_POLICY-1]) if ((pol = ext_hdrs[SADB_X_EXT_POLICY-1]) == NULL)
return -EINVAL; return -EINVAL;
pol = ext_hdrs[SADB_X_EXT_POLICY-1]; xp = xfrm_policy_byid(0, pol->sadb_x_policy_id,
if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
return -EINVAL;
xp = xfrm_policy_byid(pol->sadb_x_policy_dir-1, pol->sadb_x_policy_id,
hdr->sadb_msg_type == SADB_X_SPDDELETE2); hdr->sadb_msg_type == SADB_X_SPDDELETE2);
if (xp == NULL) if (xp == NULL)
return -ENOENT; return -ENOENT;
...@@ -2142,9 +2154,7 @@ static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct ...@@ -2142,9 +2154,7 @@ static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct
else if (x->id.proto == IPPROTO_ESP) else if (x->id.proto == IPPROTO_ESP)
dump_esp_combs(skb, t); dump_esp_combs(skb, t);
pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL); return pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL);
return 0;
} }
static struct xfrm_policy *pfkey_compile_policy(int opt, u8 *data, int len, int *dir) static struct xfrm_policy *pfkey_compile_policy(int opt, u8 *data, int len, int *dir)
......
...@@ -283,6 +283,7 @@ extern int (*dlci_ioctl_hook)(unsigned int, void *); ...@@ -283,6 +283,7 @@ extern int (*dlci_ioctl_hook)(unsigned int, void *);
EXPORT_SYMBOL(dlci_ioctl_hook); EXPORT_SYMBOL(dlci_ioctl_hook);
#endif #endif
EXPORT_SYMBOL(km_waitq);
EXPORT_SYMBOL(xfrm_cfg_sem); EXPORT_SYMBOL(xfrm_cfg_sem);
EXPORT_SYMBOL(xfrm_policy_alloc); EXPORT_SYMBOL(xfrm_policy_alloc);
EXPORT_SYMBOL(__xfrm_policy_destroy); EXPORT_SYMBOL(__xfrm_policy_destroy);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment