Skip to content

Commit

Permalink
xfrm: Add support for IPsec extended sequence numbers
Browse files Browse the repository at this point in the history
This patch adds support for IPsec extended sequence numbers (esn)
as defined in RFC 4303. The bits to manage the anti-replay window
are based on a patch from Alex Badea.

Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
klassert authored and davem330 committed Mar 14, 2011
1 parent 97e15c3 commit 2cd0846
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/net/xfrm.h
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,7 @@ extern int xfrm_state_delete(struct xfrm_state *x);
extern int xfrm_state_flush(struct net *net, u8 proto, struct xfrm_audit *audit_info);
extern void xfrm_sad_getinfo(struct net *net, struct xfrmk_sadinfo *si);
extern void xfrm_spd_getinfo(struct net *net, struct xfrmk_spdinfo *si);
extern u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq);
extern int xfrm_init_replay(struct xfrm_state *x);
extern int xfrm_state_mtu(struct xfrm_state *x, int mtu);
extern int xfrm_init_state(struct xfrm_state *x);
Expand Down
4 changes: 4 additions & 0 deletions net/xfrm/xfrm_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
struct net *net = dev_net(skb->dev);
int err;
__be32 seq;
__be32 seq_hi;
struct xfrm_state *x;
xfrm_address_t *daddr;
struct xfrm_mode *inner_mode;
Expand Down Expand Up @@ -184,7 +185,10 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)

spin_unlock(&x->lock);

seq_hi = htonl(xfrm_replay_seqhi(x, seq));

XFRM_SKB_CB(skb)->seq.input.low = seq;
XFRM_SKB_CB(skb)->seq.input.hi = seq_hi;

nexthdr = x->type->input(x, skb);

Expand Down
190 changes: 189 additions & 1 deletion net/xfrm/xfrm_replay.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@

#include <net/xfrm.h>

u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq)
{
u32 seq, seq_hi, bottom;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;

if (!(x->props.flags & XFRM_STATE_ESN))
return 0;

seq = ntohl(net_seq);
seq_hi = replay_esn->seq_hi;
bottom = replay_esn->seq - replay_esn->replay_window + 1;

if (likely(replay_esn->seq >= replay_esn->replay_window - 1)) {
/* A. same subspace */
if (unlikely(seq < bottom))
seq_hi++;
} else {
/* B. window spans two subspaces */
if (unlikely(seq >= bottom))
seq_hi--;
}

return seq_hi;
}

static void xfrm_replay_notify(struct xfrm_state *x, int event)
{
struct km_event c;
Expand Down Expand Up @@ -313,6 +338,160 @@ static void xfrm_replay_notify_bmp(struct xfrm_state *x, int event)
x->xflags &= ~XFRM_TIME_DEFER;
}

static int xfrm_replay_overflow_esn(struct xfrm_state *x, struct sk_buff *skb)
{
int err = 0;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
struct net *net = xs_net(x);

if (x->type->flags & XFRM_TYPE_REPLAY_PROT) {
XFRM_SKB_CB(skb)->seq.output.low = ++replay_esn->oseq;
XFRM_SKB_CB(skb)->seq.output.hi = replay_esn->oseq_hi;

if (unlikely(replay_esn->oseq == 0)) {
XFRM_SKB_CB(skb)->seq.output.hi = ++replay_esn->oseq_hi;

if (replay_esn->oseq_hi == 0) {
replay_esn->oseq--;
replay_esn->oseq_hi--;
xfrm_audit_state_replay_overflow(x, skb);
err = -EOVERFLOW;

return err;
}
}
if (xfrm_aevent_is_on(net))
x->repl->notify(x, XFRM_REPLAY_UPDATE);
}

return err;
}

static int xfrm_replay_check_esn(struct xfrm_state *x,
struct sk_buff *skb, __be32 net_seq)
{
unsigned int bitnr, nr;
u32 diff;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
u32 seq = ntohl(net_seq);
u32 pos = (replay_esn->seq - 1) % replay_esn->replay_window;
u32 wsize = replay_esn->replay_window;
u32 top = replay_esn->seq;
u32 bottom = top - wsize + 1;

if (unlikely(seq == 0 && replay_esn->seq_hi == 0 &&
(replay_esn->seq < replay_esn->replay_window - 1)))
goto err;

diff = top - seq;

if (likely(top >= wsize - 1)) {
/* A. same subspace */
if (likely(seq > top) || seq < bottom)
return 0;
} else {
/* B. window spans two subspaces */
if (likely(seq > top && seq < bottom))
return 0;
if (seq >= bottom)
diff = ~seq + top + 1;
}

if (diff >= replay_esn->replay_window) {
x->stats.replay_window++;
goto err;
}

if (pos >= diff) {
bitnr = (pos - diff) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
if (replay_esn->bmp[nr] & (1U << bitnr))
goto err_replay;
} else {
bitnr = replay_esn->replay_window - (diff - pos);
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
if (replay_esn->bmp[nr] & (1U << bitnr))
goto err_replay;
}
return 0;

err_replay:
x->stats.replay++;
err:
xfrm_audit_state_replay(x, skb, net_seq);
return -EINVAL;
}

static void xfrm_replay_advance_esn(struct xfrm_state *x, __be32 net_seq)
{
unsigned int bitnr, nr, i;
int wrap;
u32 diff, pos, seq, seq_hi;
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;

if (!replay_esn->replay_window)
return;

seq = ntohl(net_seq);
pos = (replay_esn->seq - 1) % replay_esn->replay_window;
seq_hi = xfrm_replay_seqhi(x, net_seq);
wrap = seq_hi - replay_esn->seq_hi;

if ((!wrap && seq > replay_esn->seq) || wrap > 0) {
if (likely(!wrap))
diff = seq - replay_esn->seq;
else
diff = ~replay_esn->seq + seq + 1;

if (diff < replay_esn->replay_window) {
for (i = 1; i < diff; i++) {
bitnr = (pos + i) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] &= ~(1U << bitnr);
}

bitnr = (pos + diff) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] |= (1U << bitnr);
} else {
nr = replay_esn->replay_window >> 5;
for (i = 0; i <= nr; i++)
replay_esn->bmp[i] = 0;

bitnr = (pos + diff) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] |= (1U << bitnr);
}

replay_esn->seq = seq;

if (unlikely(wrap > 0))
replay_esn->seq_hi++;
} else {
diff = replay_esn->seq - seq;

if (pos >= diff) {
bitnr = (pos - diff) % replay_esn->replay_window;
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] |= (1U << bitnr);
} else {
bitnr = replay_esn->replay_window - (diff - pos);
nr = bitnr >> 5;
bitnr = bitnr & 0x1F;
replay_esn->bmp[nr] |= (1U << bitnr);
}
}

if (xfrm_aevent_is_on(xs_net(x)))
xfrm_replay_notify(x, XFRM_REPLAY_UPDATE);
}

static struct xfrm_replay xfrm_replay_legacy = {
.advance = xfrm_replay_advance,
.check = xfrm_replay_check,
Expand All @@ -327,6 +506,13 @@ static struct xfrm_replay xfrm_replay_bmp = {
.overflow = xfrm_replay_overflow_bmp,
};

static struct xfrm_replay xfrm_replay_esn = {
.advance = xfrm_replay_advance_esn,
.check = xfrm_replay_check_esn,
.notify = xfrm_replay_notify_bmp,
.overflow = xfrm_replay_overflow_esn,
};

int xfrm_init_replay(struct xfrm_state *x)
{
struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
Expand All @@ -336,11 +522,13 @@ int xfrm_init_replay(struct xfrm_state *x)
replay_esn->bmp_len * sizeof(__u32))
return -EINVAL;

if ((x->props.flags & XFRM_STATE_ESN) && x->replay_esn)
x->repl = &xfrm_replay_esn;
else
x->repl = &xfrm_replay_bmp;
} else
x->repl = &xfrm_replay_legacy;


return 0;
}
EXPORT_SYMBOL(xfrm_init_replay);

0 comments on commit 2cd0846

Please sign in to comment.