ipsec VPN防重放的实现过程-图解(参考strongswan)
防重放机制有三种:时间戳、挑战和滑动窗口。在strongswan中,使用了第三种机制->滑动窗口,根据strongswan源代码,它的主要原理画了一个简图,我自己设置了一个游标,方便理解,表示指向当前接受报文的最大序列号,因为滑动窗口是跟这个值相关的。
如图:滑动窗口长度为10,这边假设从96-105序号的报文全部都接受了,当前序号为105的报文,观察当接受序列号106时候会发生怎样的变化。
滑动窗口它所维护序号就是96-105,滑动窗口的特点是,滑动窗口是维护[当前序列号-滑动窗口大小+1]至[当前序列号]的报文,相应序列号的报文有收到,相应窗口置1,否则置0,当然当前96到105是全部都接受了,当报文来时,它会做如下判断:
1.如果接下来获得报文序号是m,m>105 && m - 105 < 10,那么窗口会往前移动,如下一个报文是106,窗口会往前移动,变成维护97-106的序列号,游标移动到106。
2.如果接下来获得报文序号是m,m<105,比如m=98,那么窗口会检查对应窗口是否有置1,已经置1了就会丢弃报文,实现放重发功能,如果该窗口为0,证明还没接受过报文,此时会接受报文,但是游标不移动。
(这里的第1点要注意要判断m-105<10,比如当前是接受序号为117报文而不是106,它维护的是[117-10+1]-[117]=108-117,108已经大于105了,所以滑动窗口要全部置零,这点在后面的图解中有体现)
所以总结就一点:
序列号>游标值,移动游标实现移动滑动窗口,序列号<游标值,不移动游标进行防重发验证
strongswan防重放代码原理:
在代码中它是申请了一个数组,数组的长度就是滑动窗口的长度,它的构造很巧妙,如图,还是跟上述一样,比如当前接受到最大序列号为105,当它接受到106的报文时的变化。
可以看出,序列号为105,根据上面的它维护的是[105-10+1]-[105]=96-105,它在数组形式如图,数组下标为0,置1,表示接受101报文已接受,一直到数组下标为9,置1,表示已接受100的报文。当106报文到来时候,下标5对应的数组值先置0再置1,表示接受了106的报文,同时也把已接受96的报文信息删除了,实现滑动窗口的移动。
以这种规律,再假设现在接受序列号报文214,那么它在数组的形式是:
先确定214在哪个下标中,然后依次填写其他值,就可以知道它在数组的形式。
strognswan代码以及简易图解防重放过程:
图中,报文来的序号依次为1,2,3,5,6,7,8,910,11,13,13,25
序号1到10的报文我画成灰色(中间缺失了序号4的报文),11到13的报文我画成橘色(中间缺失了序号12的报文), 25为绿色,滑动窗口为白色表示没有相应置1,其他颜色都是表示已经置1。
/**
* Set or unset a bit in the window.
*/
static inline void set_window_bit(private_esp_context_t *this,
u_int index, bool set)
{
u_int i = index / CHAR_BIT;
if (set)
{
this->window.ptr[i] |= 1 << (index % CHAR_BIT);
}
else
{
this->window.ptr[i] &= ~(1 << (index % CHAR_BIT));
}
}
/**
* Get a bit from the window.
*/
static inline bool get_window_bit(private_esp_context_t *this, u_int index)
{
u_int i = index / CHAR_BIT;
return this->window.ptr[i] & (1 << index % CHAR_BIT);
}
/**
* Returns TRUE if the supplied seqno is not already marked in the window
*/
static bool check_window(private_esp_context_t *this, uint32_t seqno)
{
u_int offset;
offset = this->last_seqno - seqno;
offset = (this->seqno_index - offset) % this->window_size;
return !get_window_bit(this, offset);
}
METHOD(esp_context_t, verify_seqno, bool,
private_esp_context_t *this, uint32_t seqno)
{
if (!this->inbound)
{
return FALSE;
}
if (seqno > this->last_seqno)
{ /* |----------------------------------------|
* <---------^ ^ or <---------^ ^
* WIN H S WIN H S
*/
return TRUE;
}
else if (seqno > 0 && this->window_size > this->last_seqno - seqno)
{ /* |----------------------------------------|
* <---------^ or <---------^
* WIN ^ H WIN ^ H
* S S
*/
return check_window(this, seqno);
}
else
{ /* |----------------------------------------|
* ^ <---------^
* S WIN H
*/
return FALSE;
}
}
METHOD(esp_context_t, set_authenticated_seqno, void,
private_esp_context_t *this, uint32_t seqno)
{
u_int i, shift;
if (!this->inbound)
{
return;
}
if (seqno > this->last_seqno)
{ /* shift the window to the new highest authenticated seqno */
shift = seqno - this->last_seqno;
shift = shift < this->window_size ? shift : this->window_size;
for (i = 0; i < shift; ++i)
{
this->seqno_index = (this->seqno_index + 1) % this->window_size;
set_window_bit(this, this->seqno_index, FALSE);
}
set_window_bit(this, this->seqno_index, TRUE);
this->last_seqno = seqno;
}
else
{ /* seqno is inside the window, set the corresponding window bit */
i = this->last_seqno - seqno;
set_window_bit(this, (this->seqno_index - i) % this->window_size, TRUE);
}
}
相关的代码的函数如图,代码的执行顺序是在报文解密前,调用verify_seqno函数,验证是否相应滑动窗口有置1,在解密成功后执行set_authenticated_seqno,移动游标即更新滑动窗口。
- 上一篇:HashSet数据结构介绍
- 下一篇:大数据学习路线(2018年最新整理)