Commit e3e5d274 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller

[IPSEC] Add policy expiration

This patch finally adds policy expiration.

Note that it resends soft policy expire messages every 30 seconds.  This
is needed as when "soft use expire" is used for dead peer detection,
a lost message could lead to a dead peer that isn't discovered until the
SAs expire.

I've only implemented notification for XFRM as I didn't want to just add
another PFKEY extension in case it collides with something else.  Of
course it could be easily done for PFKEY with an extension too.
parent 62859d82
......@@ -119,7 +119,9 @@ enum
#define XFRM_MSG_UPDPOLICY (XFRM_MSG_BASE + 9)
#define XFRM_MSG_UPDSA (XFRM_MSG_BASE + 10)
#define XFRM_MSG_MAX (XFRM_MSG_UPDSA+1)
#define XFRM_MSG_POLEXPIRE (XFRM_MSG_BASE + 11)
#define XFRM_MSG_MAX (XFRM_MSG_POLEXPIRE+1)
struct xfrm_user_tmpl {
struct xfrm_id id;
......@@ -217,6 +219,11 @@ struct xfrm_user_expire {
__u8 hard;
};
struct xfrm_user_polexpire {
struct xfrm_userpolicy_info pol;
__u8 hard;
};
#define XFRMGRP_ACQUIRE 1
#define XFRMGRP_EXPIRE 2
......
......@@ -287,6 +287,8 @@ struct xfrm_policy
struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH];
};
#define XFRM_KM_TIMEOUT 30
struct xfrm_mgr
{
struct list_head list;
......@@ -295,6 +297,7 @@ struct xfrm_mgr
int (*acquire)(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *xp, int dir);
struct xfrm_policy *(*compile_policy)(u16 family, int opt, u8 *data, int len, int *dir);
int (*new_mapping)(struct xfrm_state *x, xfrm_address_t *ipaddr, u16 sport);
int (*notify_policy)(struct xfrm_policy *x, int dir, int event);
};
extern int xfrm_register_km(struct xfrm_mgr *km);
......@@ -635,16 +638,16 @@ static inline int xfrm_sk_clone_policy(struct sock *sk)
return 0;
}
extern void __xfrm_sk_free_policy(struct xfrm_policy *, int dir);
extern void xfrm_policy_delete(struct xfrm_policy *pol, int dir);
static inline void xfrm_sk_free_policy(struct sock *sk)
{
if (unlikely(sk->sk_policy[0] != NULL)) {
__xfrm_sk_free_policy(sk->sk_policy[0], 0);
xfrm_policy_delete(sk->sk_policy[0], XFRM_POLICY_MAX);
sk->sk_policy[0] = NULL;
}
if (unlikely(sk->sk_policy[1] != NULL)) {
__xfrm_sk_free_policy(sk->sk_policy[1], 1);
xfrm_policy_delete(sk->sk_policy[1], XFRM_POLICY_MAX+1);
sk->sk_policy[1] = NULL;
}
}
......@@ -809,10 +812,10 @@ extern int xfrm_flush_bundles(struct xfrm_state *x);
extern int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl, unsigned short family);
extern wait_queue_head_t km_waitq;
extern void km_warn_expired(struct xfrm_state *x);
extern void km_expired(struct xfrm_state *x);
extern void km_state_expired(struct xfrm_state *x, int hard);
extern int km_query(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *pol);
extern int km_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, u16 sport);
extern void km_policy_expired(struct xfrm_policy *pol, int dir, int hard);
extern void xfrm4_input_init(void);
extern void xfrm6_input_init(void);
......
......@@ -1989,9 +1989,7 @@ static int pfkey_spddelete(struct sock *sk, struct sk_buff *skb, struct sadb_msg
err = 0;
out:
if (xp) {
xfrm_policy_kill(xp);
}
xfrm_pol_put(xp);
return err;
}
......@@ -2031,12 +2029,7 @@ static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h
err = 0;
out:
if (xp) {
if (hdr->sadb_msg_type == SADB_X_SPDDELETE2)
xfrm_policy_kill(xp);
else
xfrm_pol_put(xp);
}
return err;
}
......
......@@ -141,11 +141,14 @@ 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;
u32 index;
int warn = 0;
int dir;
if (xp->dead)
goto out;
dir = xp->index & 7;
if (xp->lft.hard_add_expires_seconds) {
long tmo = xp->lft.hard_add_expires_seconds +
xp->curlft.add_time - now;
......@@ -154,6 +157,37 @@ static void xfrm_policy_timer(unsigned long data)
if (tmo < next)
next = tmo;
}
if (xp->lft.hard_use_expires_seconds) {
long tmo = xp->lft.hard_use_expires_seconds +
(xp->curlft.use_time ? : xp->curlft.add_time) - now;
if (tmo <= 0)
goto expired;
if (tmo < next)
next = tmo;
}
if (xp->lft.soft_add_expires_seconds) {
long tmo = xp->lft.soft_add_expires_seconds +
xp->curlft.add_time - now;
if (tmo <= 0) {
warn = 1;
tmo = XFRM_KM_TIMEOUT;
}
if (tmo < next)
next = tmo;
}
if (xp->lft.soft_use_expires_seconds) {
long tmo = xp->lft.soft_use_expires_seconds +
(xp->curlft.use_time ? : xp->curlft.add_time) - now;
if (tmo <= 0) {
warn = 1;
tmo = XFRM_KM_TIMEOUT;
}
if (tmo < next)
next = tmo;
}
if (warn)
km_policy_expired(xp, dir, 0);
if (next != LONG_MAX &&
!mod_timer(&xp->timer, jiffies + make_jiffies(next)))
xfrm_pol_hold(xp);
......@@ -163,14 +197,9 @@ static void xfrm_policy_timer(unsigned long data)
return;
expired:
index = xp->index;
km_policy_expired(xp, dir, 1);
xfrm_policy_delete(xp, dir);
xfrm_pol_put(xp);
/* Not 100% correct. id can be recycled in theory */
xp = xfrm_policy_byid(0, index, 1);
if (xp) {
xfrm_policy_kill(xp);
}
}
......@@ -321,8 +350,7 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
policy->index = delpol ? delpol->index : xfrm_gen_index(dir);
policy->curlft.add_time = (unsigned long)xtime.tv_sec;
policy->curlft.use_time = 0;
if (policy->lft.hard_add_expires_seconds &&
!mod_timer(&policy->timer, jiffies + HZ))
if (!mod_timer(&policy->timer, jiffies + HZ))
xfrm_pol_hold(policy);
write_unlock_bh(&xfrm_policy_lock);
......@@ -340,18 +368,18 @@ struct xfrm_policy *xfrm_policy_bysel(int dir, struct xfrm_selector *sel,
write_lock_bh(&xfrm_policy_lock);
for (p = &xfrm_policy_list[dir]; (pol=*p)!=NULL; p = &pol->next) {
if (memcmp(sel, &pol->selector, sizeof(*sel)) == 0) {
xfrm_pol_hold(pol);
if (delete)
*p = pol->next;
break;
}
}
if (pol) {
if (delete)
write_unlock_bh(&xfrm_policy_lock);
if (pol && delete) {
atomic_inc(&flow_cache_genid);
else
xfrm_pol_hold(pol);
xfrm_policy_kill(pol);
}
write_unlock_bh(&xfrm_policy_lock);
return pol;
}
......@@ -362,18 +390,18 @@ struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete)
write_lock_bh(&xfrm_policy_lock);
for (p = &xfrm_policy_list[id & 7]; (pol=*p)!=NULL; p = &pol->next) {
if (pol->index == id) {
xfrm_pol_hold(pol);
if (delete)
*p = pol->next;
break;
}
}
if (pol) {
if (delete)
write_unlock_bh(&xfrm_policy_lock);
if (pol && delete) {
atomic_inc(&flow_cache_genid);
else
xfrm_pol_hold(pol);
xfrm_policy_kill(pol);
}
write_unlock_bh(&xfrm_policy_lock);
return pol;
}
......@@ -473,25 +501,36 @@ struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi
return pol;
}
void xfrm_sk_policy_link(struct xfrm_policy *pol, int dir)
static void __xfrm_policy_link(struct xfrm_policy *pol, int dir)
{
pol->next = xfrm_policy_list[XFRM_POLICY_MAX+dir];
xfrm_policy_list[XFRM_POLICY_MAX+dir] = pol;
pol->next = xfrm_policy_list[dir];
xfrm_policy_list[dir] = pol;
xfrm_pol_hold(pol);
}
void xfrm_sk_policy_unlink(struct xfrm_policy *pol, int dir)
static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol,
int dir)
{
struct xfrm_policy **polp;
for (polp = &xfrm_policy_list[XFRM_POLICY_MAX+dir];
for (polp = &xfrm_policy_list[dir];
*polp != NULL; polp = &(*polp)->next) {
if (*polp == pol) {
*polp = pol->next;
atomic_dec(&pol->refcnt);
return;
return pol;
}
}
return NULL;
}
void xfrm_policy_delete(struct xfrm_policy *pol, int dir)
{
write_lock_bh(&xfrm_policy_lock);
pol = __xfrm_policy_unlink(pol, dir);
write_unlock_bh(&xfrm_policy_lock);
if (pol)
xfrm_policy_kill(pol);
}
int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol)
......@@ -504,10 +543,10 @@ int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol)
if (pol) {
pol->curlft.add_time = (unsigned long)xtime.tv_sec;
pol->index = xfrm_gen_index(XFRM_POLICY_MAX+dir);
xfrm_sk_policy_link(pol, dir);
__xfrm_policy_link(pol, XFRM_POLICY_MAX+dir);
}
if (old_pol)
xfrm_sk_policy_unlink(old_pol, dir);
__xfrm_policy_unlink(old_pol, XFRM_POLICY_MAX+dir);
write_unlock_bh(&xfrm_policy_lock);
if (old_pol) {
......@@ -531,7 +570,7 @@ static struct xfrm_policy *clone_policy(struct xfrm_policy *old, int dir)
memcpy(newp->xfrm_vec, old->xfrm_vec,
newp->xfrm_nr*sizeof(struct xfrm_tmpl));
write_lock_bh(&xfrm_policy_lock);
xfrm_sk_policy_link(newp, dir);
__xfrm_policy_link(newp, XFRM_POLICY_MAX+dir);
write_unlock_bh(&xfrm_policy_lock);
}
return newp;
......@@ -550,15 +589,6 @@ int __xfrm_sk_clone_policy(struct sock *sk)
return 0;
}
void __xfrm_sk_free_policy(struct xfrm_policy *pol, int dir)
{
write_lock_bh(&xfrm_policy_lock);
xfrm_sk_policy_unlink(pol, dir);
write_unlock_bh(&xfrm_policy_lock);
xfrm_policy_kill(pol);
}
/* Resolve list of templates for the flow, given policy. */
static int
......
......@@ -140,7 +140,7 @@ static void xfrm_timer_handler(unsigned long data)
}
if (warn)
km_warn_expired(x);
km_state_expired(x, 0);
resched:
if (next != LONG_MAX &&
!mod_timer(&x->timer, jiffies + make_jiffies(next)))
......@@ -155,7 +155,7 @@ static void xfrm_timer_handler(unsigned long data)
goto resched;
}
if (x->id.spi != 0)
km_expired(x);
km_state_expired(x, 1);
__xfrm_state_delete(x);
out:
......@@ -512,7 +512,7 @@ int xfrm_state_check_expire(struct xfrm_state *x)
if (x->curlft.bytes >= x->lft.hard_byte_limit ||
x->curlft.packets >= x->lft.hard_packet_limit) {
km_expired(x);
km_state_expired(x, 1);
if (!mod_timer(&x->timer, jiffies + XFRM_ACQ_EXPIRES*HZ))
xfrm_state_hold(x);
return -EINVAL;
......@@ -521,7 +521,7 @@ int xfrm_state_check_expire(struct xfrm_state *x)
if (!x->km.dying &&
(x->curlft.bytes >= x->lft.soft_byte_limit ||
x->curlft.packets >= x->lft.soft_packet_limit))
km_warn_expired(x);
km_state_expired(x, 0);
return 0;
}
......@@ -737,27 +737,21 @@ int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl)
static struct list_head xfrm_km_list = LIST_HEAD_INIT(xfrm_km_list);
static rwlock_t xfrm_km_lock = RW_LOCK_UNLOCKED;
void km_warn_expired(struct xfrm_state *x)
{
struct xfrm_mgr *km;
x->km.dying = 1;
read_lock(&xfrm_km_lock);
list_for_each_entry(km, &xfrm_km_list, list)
km->notify(x, 0);
read_unlock(&xfrm_km_lock);
}
void km_expired(struct xfrm_state *x)
void km_state_expired(struct xfrm_state *x, int hard)
{
struct xfrm_mgr *km;
if (hard)
x->km.state = XFRM_STATE_EXPIRED;
else
x->km.dying = 1;
read_lock(&xfrm_km_lock);
list_for_each_entry(km, &xfrm_km_list, list)
km->notify(x, 1);
km->notify(x, hard);
read_unlock(&xfrm_km_lock);
if (hard)
wake_up(&km_waitq);
}
......@@ -792,6 +786,20 @@ int km_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, u16 sport)
return err;
}
void km_policy_expired(struct xfrm_policy *pol, int dir, int hard)
{
struct xfrm_mgr *km;
read_lock(&xfrm_km_lock);
list_for_each_entry(km, &xfrm_km_list, list)
if (km->notify_policy)
km->notify_policy(pol, dir, hard);
read_unlock(&xfrm_km_lock);
if (hard)
wake_up(&km_waitq);
}
int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen)
{
int err;
......
......@@ -783,9 +783,7 @@ static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfr
if (xp == NULL)
return -ENOENT;
if (delete)
xfrm_policy_kill(xp);
else {
if (!delete) {
struct sk_buff *resp_skb;
resp_skb = xfrm_policy_netlink(skb, xp, p->dir, nlh->nlmsg_seq);
......@@ -796,9 +794,10 @@ static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh, void **xfr
NETLINK_CB(skb).pid,
MSG_DONTWAIT);
}
xfrm_pol_put(xp);
}
xfrm_pol_put(xp);
return err;
}
......@@ -994,7 +993,7 @@ static int build_expire(struct sk_buff *skb, struct xfrm_state *x, int hard)
return -1;
}
static int xfrm_send_notify(struct xfrm_state *x, int hard)
static int xfrm_send_state_notify(struct xfrm_state *x, int hard)
{
struct sk_buff *skb;
......@@ -1120,11 +1119,56 @@ struct xfrm_policy *xfrm_compile_policy(u16 family, int opt,
return xp;
}
static int build_polexpire(struct sk_buff *skb, struct xfrm_policy *xp,
int dir, int hard)
{
struct xfrm_user_polexpire *upe;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail;
nlh = NLMSG_PUT(skb, 0, 0, XFRM_MSG_POLEXPIRE, sizeof(*upe));
upe = NLMSG_DATA(nlh);
nlh->nlmsg_flags = 0;
copy_to_user_policy(xp, &upe->pol, dir);
if (copy_to_user_tmpl(xp, skb) < 0)
goto nlmsg_failure;
upe->hard = !!hard;
nlh->nlmsg_len = skb->tail - b;
return skb->len;
nlmsg_failure:
skb_trim(skb, b - skb->data);
return -1;
}
static int xfrm_send_policy_notify(struct xfrm_policy *xp, int dir, int hard)
{
struct sk_buff *skb;
size_t len;
len = sizeof(struct xfrm_user_tmpl) * xp->xfrm_nr;
len = RTA_ALIGN(RTA_LENGTH(len));
len += NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct xfrm_userpolicy_info)));
skb = alloc_skb(len, GFP_ATOMIC);
if (skb == NULL)
return -ENOMEM;
if (build_polexpire(skb, xp, dir, hard) < 0)
BUG();
NETLINK_CB(skb).dst_groups = XFRMGRP_EXPIRE;
return netlink_broadcast(xfrm_nl, skb, 0, XFRMGRP_EXPIRE, GFP_ATOMIC);
}
static struct xfrm_mgr netlink_mgr = {
.id = "netlink",
.notify = xfrm_send_notify,
.notify = xfrm_send_state_notify,
.acquire = xfrm_send_acquire,
.compile_policy = xfrm_compile_policy,
.notify_policy = xfrm_send_policy_notify,
};
static int __init xfrm_user_init(void)
......
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