XFRM is an IPSec implementation for the Linux kernel. The name XFRM stands for "transform" referencing the transformation of IP packets as per the IPSec protocol.
The following RFCs are relevant for IPSec:
- RFC4301: Definition of the IPSec protocol.
- RFC4302: Definition of the Authentication Header (AH) sub-protocol for ensuring authenticity of IP packets.
- RFC4303: Definition of the Encapsulating Security Payload (ESP) sub-protocol for ensuring authenticity and secrecy of IP packets.
The IPSec protocol allows for sequence numbers of size 32 bits or 64 bits. The 64 bit sequence numbers are referred to as Extended Sequence Numbers (ESN).
The anti-replay mechanism is defined in the RFCs for both AH and ESP. The mechanism keeps a window of acceptable sequence numbers of incoming packets. The window extends back from the highest sequence number received so far, defining a lower bound for the acceptable sequence numbers. When receiving a sequence number below that bound, it is rejected. When receiving a sequence number higher than the current highest sequence number, the window is shifted forward. When receiving a sequence number within the window, the mechanism will mark this sequence number in a checklist for ensuring that each sequence number in the window is only received once. If the sequence number has already been marked, it is rejected.
This checklist can be implemented as a bitmap, where each sequence number in the window is represented by a single bit, with 0 meaning this sequence number has not been received yet, and 1 meaning it has already been received.
Based on this information, the meaning of the fields in the xfrm_replay_state_esn
struct can be given as follows.
The struct holds the state of the anti-replay mechanism with extended sequence numbers (64 bits):
The highest sequence number received so far is represented by seq
and seq_hi
. Each is a 32 bit integer, so together they can represent a 64 bit number, with seq
holding the lower 32 bit and seq_hi
holding the higher 32 bit. The reason for splitting the 64 bit value into two 32 bit values, instead of representing it as a single 64 bit variable, is that the IPSec protocol mandates an optimization where only the lower 32 bit of the sequence number are included in the package. For this reason, it is more convenient to have the lower 32 bits as a separate variable in the struct, so that it can be accessed directly without resorting to bit-operations.
The sequence number counter for outgoing packages is tracked in oseq
and oseq_hi
. As before, the 64 bit number is represented by two 32 bit variables.
The size of the window is represented by replay_window
. The smallest acceptable sequence number if given by the sequence number expressed by seq
and seq_hi
minus replay_window
plus one.
The bitmap for checking off received sequence numbers within the window is represented by bmp
. It is defined as a zero-sized array, but when the memory for the struct is allocated, additional memory is reserved after the struct, which can then be accessed e.g. with bmp[i]
(which is of course just syntactic sugar for *(bmp+i)
). The size of the bitmap is held in bmp_len
. It is of course related to the window size, i.e. window size divided by 8*sizeof(u32)
, rounded up. I would speculate that it is stored explicitly to avoid having to recalculate this value frequently.