xfrm: Fix replay notification for esn.
authorSteffen Klassert <steffen.klassert@secunet.com>
Mon, 11 Mar 2013 09:32:16 +0000 (10:32 +0100)
committerSteffen Klassert <steffen.klassert@secunet.com>
Wed, 20 Mar 2013 10:57:52 +0000 (11:57 +0100)
We may miscalculate the sequence number difference from the
last time we send a notification if a sequence number wrap
occured in the meantime. We fix this by adding a separate
replay notify function for esn. Here we take the high bits
of the sequence number into account to calculate the
difference.

Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
net/xfrm/xfrm_replay.c

index 35754cc8a9e5b9c51cdaf52693128ee2098718f7..a3906737f49ef3636d4fba9001848302ae1da250 100644 (file)
@@ -334,6 +334,72 @@ static void xfrm_replay_notify_bmp(struct xfrm_state *x, int event)
                x->xflags &= ~XFRM_TIME_DEFER;
 }
 
+static void xfrm_replay_notify_esn(struct xfrm_state *x, int event)
+{
+       u32 seq_diff, oseq_diff;
+       struct km_event c;
+       struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
+       struct xfrm_replay_state_esn *preplay_esn = x->preplay_esn;
+
+       /* we send notify messages in case
+        *  1. we updated on of the sequence numbers, and the seqno difference
+        *     is at least x->replay_maxdiff, in this case we also update the
+        *     timeout of our timer function
+        *  2. if x->replay_maxage has elapsed since last update,
+        *     and there were changes
+        *
+        *  The state structure must be locked!
+        */
+
+       switch (event) {
+       case XFRM_REPLAY_UPDATE:
+               if (!x->replay_maxdiff)
+                       break;
+
+               if (replay_esn->seq_hi == preplay_esn->seq_hi)
+                       seq_diff = replay_esn->seq - preplay_esn->seq;
+               else
+                       seq_diff = UINT_MAX - preplay_esn->seq
+                                  + replay_esn->seq;
+
+               if (replay_esn->oseq_hi == preplay_esn->oseq_hi)
+                       oseq_diff = replay_esn->oseq - preplay_esn->oseq;
+               else
+                       oseq_diff = UINT_MAX - preplay_esn->oseq
+                                   + replay_esn->oseq;
+
+               if (seq_diff < x->replay_maxdiff &&
+                   oseq_diff < x->replay_maxdiff) {
+
+                       if (x->xflags & XFRM_TIME_DEFER)
+                               event = XFRM_REPLAY_TIMEOUT;
+                       else
+                               return;
+               }
+
+               break;
+
+       case XFRM_REPLAY_TIMEOUT:
+               if (memcmp(x->replay_esn, x->preplay_esn,
+                          xfrm_replay_state_esn_len(replay_esn)) == 0) {
+                       x->xflags |= XFRM_TIME_DEFER;
+                       return;
+               }
+
+               break;
+       }
+
+       memcpy(x->preplay_esn, x->replay_esn,
+              xfrm_replay_state_esn_len(replay_esn));
+       c.event = XFRM_MSG_NEWAE;
+       c.data.aevent = event;
+       km_state_notify(x, &c);
+
+       if (x->replay_maxage &&
+           !mod_timer(&x->rtimer, jiffies + x->replay_maxage))
+               x->xflags &= ~XFRM_TIME_DEFER;
+}
+
 static int xfrm_replay_overflow_esn(struct xfrm_state *x, struct sk_buff *skb)
 {
        int err = 0;
@@ -510,7 +576,7 @@ static struct xfrm_replay xfrm_replay_esn = {
        .advance        = xfrm_replay_advance_esn,
        .check          = xfrm_replay_check_esn,
        .recheck        = xfrm_replay_recheck_esn,
-       .notify         = xfrm_replay_notify_bmp,
+       .notify         = xfrm_replay_notify_esn,
        .overflow       = xfrm_replay_overflow_esn,
 };