目录
1
实现总体思想
...
2
1.1
Netfilter
的
HOOK
机制的介绍
...
2
1.2
处理模块之间的关系
...
3
1.3
功能模块之间的关系
...
5
2
各个模块的实现
...
6
2.1
处理模块的实现
...
6
2.1.1
Netfilter钩子函数
...
6
2.1.1.1
实现思想
...
6
2.1.1.2
实现细节
...
6
2.1.2
进入IP包预处理
...
7
2.1.2.1
实现思想
...
7
2.1.2.2
实现细节
...
7
2.1.3
IPSec进入策略处理
...
7
2.1.3.1
实现思想
...
7
2.1.3.2
实现细节
...
7
2.1.4
路由
...
8
2.1.4.1
实现思想
...
8
2.1.4.2
实现细节
...
8
2.1.5
本机对IP包处理
...
8
2.1.5.1
实现思想
...
8
2.1.5.2
实现细节
...
8
2.1.6
IPSec进入处理
...
9
2.1.6.1
实现思想
...
9
2.1.6.2
实现细节
...
9
2.1.7
转发处理
...
10
2.1.7.1
实现思想
...
10
2.1.7.2
实现细节
...
10
2.1.8
本地IP包处理
...
11
2.1.8.1
实现思想
...
11
2.1.8.2
实现细节
...
11
2.1.9
IPSec外出处理
...
11
2.1.9.1
实现思想
...
11
2.1.9.2
实现细节
...
11
2.1.10
发送处理
...
12
2.1.10.1
实现思想
...
12
2.1.10.2
实现细节
...
12
2.2
功能模块的实现
...
13
2.2.1
策略库(SPD)的实现:
...
13
2.2.1.1
实现思想:
...
13
2.2.1.2
实现细节
...
14
2.2.1.3
基本操作
...
14
2.2.2
安全关联库(SAD)的实现
...
14
2.2.2.1
实现思想
...
14
2.2.2.2
实现细节
...
15
2.2.2.3
基本操作
...
15
2.2.3
AH协议处理模块实现
...
15
2.2.3.1
AH格式:
...
16
2.2.3.2
实现功能:
...
16
2.2.3.3
实现模式:
...
16
2.2.4
ESP协议处理模块实现
...
17
2.2.4.1
ESP格式
...
17
2.2.4.2
实现功能:
...
17
2.2.4.3
实现模式
...
17
2.2.5
算法实现
...
18
2.2.5.1
实现思想
...
18
2.2.5.2
实现细节
...
18
2.2.6
日志、统计、配置与审计实现
...
18
2.2.6.1
日志与审计
...
18
2.2.6.2
统计
...
18
2.2.6.3
配置
...
18
2.2.7
应用程序与内核的通讯接口实现
...
19
2.2.7.1
实现思想
...
19
2.2.7.2
实现细节
...
20
1
实现总体思想
通过改造
Linux
的
IP
协议栈源代码,使得
IP
实现与
IPSec
实现完全整合。该实现按功能分,可以分为以下几个功能模块模块:安全策略库,安全关联库,
AH
协议处理,
ESP
协议处理,算法
(
加密卡的实现
)
,日志、统计、配置与审计,面向应用的接口。按处理过程分,还可以分为以下几个处理模块:进入
IP
层预处理、
IPSec
进入策略处理、路由、本机对
IP
包处理、
IPSec
进入处理、转发处理、本地
IP
包处理、
IPSec
外出处理、发送处理。
Ipsec
协议实现与
IP
实现的整合思想:利用
Netfilter
的
HOOK
机制,在接受
IP
包和发送
IP
包的过程中,在适当的处理位置调用相关的
ipsec
处理模块。
图
1
Netfilter
是
linux2.4
内核实现数据包过滤
/
数据包处理
/NAT
等功能的抽象、通用化的框架。
Netfilter
框架包含以下三部分:
1、
为每种网络协议
(IPv4
、
IPv6
等
)
定义一套钩子函数(
IPv4
定义了
5
个钩子函数)
,
这些钩子函数在数据报流过协议栈的几个关键点被调用。在这几个点中,协议栈将把数据报及钩子函数标号作为参数调用
netfilter
框架。
2、
内核的任何模块可以对每种协议的一个或多个钩子进行注册,实现挂接,这样当某个数据包被传递给
netfilter
框架时,内核能检测是否有任何模块对该协议和钩子函数进行了注册。若注册了,则调用该模块的注册时使用的回调函数,这样这些模块就有机会检查
(
可能还会修改
)
该数据包、丢弃该数据包及指示
netfilter
将该数据包传入用户空间的队列。
3、
那些排队的数据包是被传递给用户空间的异步地进行处理。一个用户进程能检查数据包,修改数据包,甚至可以重新将该数据包通过离开内核的同一个钩子函数中注入到内核中。
IP
层的五个
HOOK
点的位置如图
1
所示:
(ipv4)
1.
NF_IP_PRE_ROUTING
:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验
和等检测),
源地址转换在此点进行;
2.
NF_IP_LOCAL_IN
:经路由查找后,送往本机的通过此检查点
,INPUT
包过滤在此点进行;
3.
NF_IP_FORWARD
:要转发的包通过此检测点
,FORWORD
包过滤在此点进行;
4.
NF_IP_POST_ROUTING
:所有马上便要通过网络设备出去的包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行;
5.
NF_IP_LOCAL_OUT
:本机进程发出的包通过此检测点,
OUTPUT
包过滤在此点进行。
IPSec
安全网关对
IP
报文的接收、转发和发送处理的整个过程如图
2
。当安全网关的网卡接收的数据报时,通过中断触发内核的中断处理程序,将网卡接收的数据报传送到内核空间,然后再通过
IP
层预处理程序将数据报转换为
IP
包。此时,我们将利用内核的
Netfilter
机制提供的
HOOK
点
PRE_ROUTING
,将
IP
包传送到
IPSec
进入策略处理模块。该模块将决定哪些包可以进入安全网关,哪些包需要丢弃。对于允许进入网关的
IP
包,将送回到路由处理模块。路由处理根据
IP
头决定
IP
包是发送到本机还是继续转发。
对于发送到本机的
IP
包,首先经过内核对
IP
包的处理,如:分片重组、选项处理等等。然后利用
HOOK
点
LOCAL_IN
,将重组的
IP
包传送到
IPSec
进入处理模块。
IPSec
进入处理模块将对
IPSec
包和非
IPSec
包进行区分,对于发往本机的非
IPSec
包将直接传送到传输层处理模块;对于
IPSec
包将进行认证或解密等
IPSec
处理,并剥去
IPSec
头。处理完后,将重新组装成
IP
包发回到
IP
层预处理模块。这样该
IP
包将重新通过路由来决定发往何处。
对于转发的
IP
包,首先进行转发处理,如:决定下一跳、减少
TTL
、对某些特殊情况发送
ICMP
包。然后,利用
HOOK
点
IP_FORWARD
,将
IP
包传送到外出
IPSec
外出处理模块。
IPSec
处理将根据策略区分
IPSec
包、非
IPSec
包以及包发往何处。对于
IPSec
包将丢弃。对于发往内部网的非
IPSec
包,直接将包传送给发送处理模块,发往内部网。对于发往外部网的
IP
包,将根据策略进行认证或加密等
IPSec
处理,最后将处理过的
IP
包传送到发送处理模块。
对于从安全网关传输层发送的报文,首先进行本地的
IP
包处理,构建
IP
包。然后对
IP
包进行路由,决定
IP
包的出口。路由之后将利用
HOOK
点
LOCAL_OUTPUT
,将
IP
包传送到
IPSec
外出处理模块。
IPSec
外出处理将根据策略决定那些包需要进行
IPSec
处理。对于不需要处理的
IP
包,直接传送到发送处理模块。对于需要进行
IPSec
处理的
IP
包,将根据策略进行认证或加密等
IPSec
处理,然后将
IPSec
包重新发回到路由处理模块,决定
IPSec
包将发往何处。当再次经过
HOOK
点时,
IPSec
外出处理将通过策略将处理过的
IP
包直接传送到发送处理模块。发送处理模块将进行分片等处理,最后将包发送到网卡。
图
2
1.3
功能模块之间的关系
各功能模块之间的关系如图
3
所示。安全策略库存放了由用户或系统管理员所制定的策略,策略将决定通讯的双方是否采用
IPSec
处理,以及决定采用何种
IPSec
协议、模式、算法和嵌套需求。安全关联库由一系列安全关联项组成。安全关联是两个通讯实体经过协商建立起来的一种协定。它们决定了用来保护数据包安全所需的各种参数:序号计数器;抗重播窗口;
AH
验证算法及其密钥;
ESP
加密、认证算法、密钥;安全关联的生存期;
IPsec
协议模式。算法库存放了多种可选的认证和加密算法,在处理时将通过安全关联中的算法项来指明所需要使用的算法。面向应用的接口提供了管理安全策略库,以及配置网关,处理日志、统计、审计信息的接口。用户或系统管理员可通过配置服务器关联全策略库,还可以进行手工注入安全关联或者启动
IKE
动态协商安全关联,以及对日志、统计、配置、审计信息的提取和处理。
图
3
利用
Linux
提供的
Netfilter
框架,并在
Netfilter
框架提供的
HOOK
点上注册并实现
IPSec
相关处理函数,使得
IPSec
处理能加入到
IP
包接收或发送处理过程的适当位置。
Linux
内核的
Netfilter
框架在
ipv4
中提供了
5
个
HOOK
点(图
1
),在每个
HOOK
点上都可以通过一个注册函数
nf_register_hook(struct nf_hook_ops *reg)
将自己实现的处理函数挂接到
HOOK
点上。这样在每一次
HOOK
点被激活时,都将查询并执行该
HOOK
点所注册的处理函数。然后在处理结束时返回相关的信息来决定被处理的包是丢弃、拒绝,还是继续进行
HOOK
点后面的处理。
因此我们的工作便是生成一个
struct nf_hook_ops
结构的实例(结构如下):
struct nf_hook_ops
{
struct list_head list;
nf_hookfn *hook;
int pf;
int hooknum;
int priority;
};
并将该结构中的一个相关处理函数
nf_hookfn
(定义如下)实现为我们所需的相关
IPSec
处理函数:
unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
并用
nf_register_hook
将该实例注册到由
hooknum
所指定的
HOOK
点上。这样,当
hooknum
指定的
HOOK
点被激活时(如在
ip_rcv
中的:
NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish)
),将根据
priority
所指定的优先级,依次执行在该
HOOK
点注册的函数,因此我们所实现的相关
IPSec
处理函数将得到执行。
Netfilter
还提供了一系列返回信息(如下):
l
NF_ACCEPT
继续正常传输数据报
l
NF_DROP
丢弃该数据报,不再传输
l
NF_STOLEN
模块接管该数据报,不要继续传输该数据报
l
NF_QUEUE
对该数据报进行排队
(
通常用于将数据报给用户空间的进程进行处理
)
l
NF_REPEAT
再次调用该钩子函数
在处理结束后将通过返回信息来决定被处理的数据报在
HOOK
点之后应该怎样处理。
2.1.2
初始化
IPSec
的实现
2.1.2.1
注册
HOOK
点
从网卡传来的数据报在进入
IP
层处理之前先进行一些检查,并在此时激活第一个
HOOK
点:
NF_IP_PRE_ROUTING
,对进入本机的包进行进入前预处理。我们实现的
IPSec
进入策略处理模块也在此时通过
HOOK
点被调用执行。
该模块首先对传入的
IP
包进行必要的检查:
1、
通过包类型标志
pkt_type
检查
IP
包是否是其他机器(
PACKET_OTHERHOST
)的,如果是就丢弃。
2、
检查
IP
包的长度是否合法。
3、
检查
IP
版本。
4、
检查校样和。
最后激活
HOOK
点:
NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish)
。然后将依次调用在该
HOOK
点上注册处理函数,其中包括我们实现的
IPSec
进入策略处理模块。。
该模块将根据
IP
报文的相关信息构造选择符。然后通过选择符查询进入安全策略库,找到第一个匹配项(策略)。再根据策略对
IP
包
进行策略检查和处理:
1、
对于外部网的进入包
drop
:(增强型包过滤)不允许某些外部网的包进入;不允许某些外部网的不加密包进入;不允许某些网应该加密却未加密的包进入;不允许某些外部网的加密包进入。
reject
:类似于
drop
,不过要对
ICMP
包进行特殊处理。
accept
:对某些外部网的不加密包或加密包允许通过,对已经经过
IPSec
处理的包(用于
IPSec
嵌套的情况)允许通过。
2、
对于内部网的进入包
drop
:对某些内部网机器的
IP
包不允许通过
accept
:允许内部网的
IP
包通过
最后对于通过的包,传回到
ip_rcv_finish
继续处理
1、
从
IP
包中提取源/目的
IP
地址、源/目的端口、下一层协议等信息。
2、
根据这些提取的信息查询进入安全策略库,找到匹配的策略项。
3、
根据策略项所指定的策略进行处理:
丢弃:返回
NF_DROP
,
Netfilter
机制将丢弃该数据包,不再传输。并将该事件记录到日志中。
拒绝:返回
NF_STOLEN
,
Netfilter
机制将接管该数据包,不再继续传输。并将该事件记录到日志中。
接收:返回
NF_ACCEPT
,
Netfilter
机制将正常传输该数据包:将该包传送给
ip_rcv_finish
。其中对于
IPSec
包需要作上标记(利用标记变量
nmask
),表明该包需要在以后进行
IPSec
处理。
根据
IP
包的源地址、目的地址和服务类型(
tos
)等信息查找路由表,找到该
IP
包对应的路由项,并记录该项。在路由项中指出了
IP
包接下来该如何处理:对于本地
IP
包,将交给本机的
IP
处理模块
ip_local_deliver
;对于非本机接收包,适合转发的将交给转发处理模块
ip_forward
,其他情况将作特殊处理。
1、
将从
IP
包中提取的源地址、目的地址和服务类型值,通过适当的散列算法产生散列值。
2、
根据散列值在路由缓存散列表中查找散列位置
3、
进一步根据源地址、目的地址、输入接口、服务类型在散列链中找到正确的路由项。
4、
根据路由项中指定的输入处理函数,调用适当的函数:对于本地
IP
包调用
ip_local_deliver
;对于其他
IP
包调用
ip_forward
。
对经过路由传送来的
IP
包,首先检查是否存在分片。如果存在分片,必须缓存
IP
分片包。等所有的分片到齐时,进行分片重组处理,将分片合成完整的
IP
报文。然后激活
HOOK
点:
NF_IP_LOCAL_IN
,对进入
IP
包进行相关处理。我们实现的
IPSec
进入处理模块也在此时调用。
1、
检查
IP
包的
3
位标志字段,当
IP
包片偏量
(frag_off)
第
14
位
(IP_MF)
为
1
时
,
表示该
IP
包有后继分片。这时将进行分片重组处理
ip_defrag
。
2、
分片重组处理:
l
根据分片包的
16
位标识、源地址、目的地址、协议字段计算分片包的散列值。
l
根据散列值,定位该包在分片链中的位置。
l
如果有分片链,说明已有其他分片到达。将该分片插入到对应的分片队列中。
l
检查该分片是否是最后一个分片,分片是否都到齐了。如果都满足,则将分片重组为一个完整的
IP
包。
l
返回新的
IP
包。
3、
激活
HOOK
点:
NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish)
。然后将依次调用在该
HOOK
点上注册处理函数,其中包括我们实现的
IPSec
进入处理模块。
首先检查
nmask
标记,判断该
IP
包是否是一个
IPSec
包。如果不是,则将利用返回值
NF_ACCEPT
,
继续正常传输该数据报到
ip_local_deliver_finish
。如果是,则将进行
IPSec
进入处理,并在处理完后调用
ip_rcv
将
IP
包传送回进入
IP
包预处理模块,此时应该返回
NF_DROP
,使该包不在向上传输。
IPSec
进入处理首先将从
IP
包中提取目的地址、下一协议、安全参数索引(
spi
)信息,并根据这些信息查找安全关联库(
SAD
),找到对应的唯一的一个
SA
。然后根据找到的
SA
进行:
SA
状态处理、
SA
生存期处理、重播窗口处理、模式处理、相关
IPSec
协议处理(
AH
或
ESP
)。当处理完一个
SA
后,必须要标记处理过的
SA
(用于后期的进入策略匹配),并检查是否存在
SA
串(即多个
SA
)。如果有,则循环处理
SA
串,直到遇到传输层协议头或者非本机的
IP
头(表明
SA
串上的所有
SA
都处理完毕)。
最后根据处理过的
IP
包的相关信息查找安全策略库(
SPD
),找到该
IP
包所对应的进入策略(该策略应该与发送端的外出策略是相同的)。然后检查该策略所指定的
SA
串是否与处理过并标记的
SA
串相同,从而判断是否进行了所要求的
IPSec
处理。如果不相同,则丢弃该
IP
包,并进行日志记录。
1、
检查
nmask
标记(该标记在进入策略处理时被设置上)。如果不是
IPSec
包,返回
NF_ACCEPT
;如果是,继续进行
IPSec
进入处理。
2、
提取
IP
头中的目的地址、下一协议,通过下一协议字段信息,提取
IPSec
头中的安全参数索引(
spi
)信息。
3、
根据
<dst
,
proto
,
spi>
三元组查找安全关联库(
SAD
)。如果没找到对应的
SA
(或
SA
串),则将该
IP
包丢弃,并记录日志信息;如果找到了对应的
SA
(或
SA
串),则将根据
SA
(或
SA
串)对
IPSec
包进行下面的处理。
4、
检查
SA
的状态
:对于幼稚(
SADB_SASTATE_LARVAL
)和死亡(
SADB_SASTATE_DEAD
)状态的包将丢弃,并记录日志信息
5、
检查
SA
的生存期。生存期分为以下几种:字节数、包数、使用时间、增加时间,并且对于每一种生存期还有软硬之分。如果是硬生存期过期了,则将激活
IKE
模块动态协商一个新的
SA
,将过期的
SA
(或
SA
束)删除,并将
IP
包丢弃,并记录日志信息;如果是软生存期过期了,同样要激活
IKE
模块动态协商一个新的
SA
,但此时并不删除
SA
(或
SA
束),而是将
SA
的状态标记为正在死亡(
SADB_SASTATE_DYING
)。而且不丢弃该数据包,继续进行处理。
6、
检查重播窗口:检查时将判断到达的
IPSec
包的序列号是否小于该网关所记录的处理过的最大
IPSec
包的序列号。如果小,则再检查序号的差值是否超过了窗口的大小。如果没有超过,则检查窗口,判断该
IPSec
包是否已经到达过,如果已经到达过,则将该包丢弃;如果没到达,则接收。如果到达的
IPSec
包的序列号大于处理过的最大序列号,则接收,并继续处理。
7、
根据
SA
指定的
IPSec
协议(
AH
或
ESP
)、算法和密钥进行认证或(和)解密处理。
8、
更新重播窗口:此时检查如果被处理的
IPSec
包的序列号大于网关处理过的最大序列号,并且序号的差值小于窗口大小,则更新窗口,标记该到达并处理过的包。最后将网关处理的最大序列号更新为该被处理
IPSec
包的序列号。
9、
更新
SA
的生存期:字节数、包数、使用时间。
10、
记录下该
SA
信息,以便与以后进行进入策略检查。
11、
提取
IPSec
头指出的下一协议:如果还是
IPSec
协议,说明有多个
SA
(
SA
串)对该数据包进行了处理,则需要利用原
IP
头和处理过的数据区组成新的
IP
包,进行循环处理;如果是
IP
协议,说明
SA
(或
SA
串)已经处理完毕,则需要根据内部的
IP
头信息组建新的
IP
包,并跳出循环进入后继的处理。
12、
进行进入策略匹配:根据新的
IP
包信息(源地址/目的地址、源端口/目的端口、协议)查找安全策略库(
SPD
),找到该包所对应的第一个进入处理策略(可能匹配多个)。检查该策略所指定的
SA
(或
SA
串)即其应用的顺序是否与
10
中记录过的
SA
(或
SA
串)以及顺序相同。如果相同,则说明满足进入策略允许该数据报进入安全网关;如果不相同,则将再次查找策略库(遍历策略库),直到匹配成功。如果最后还没有找到正确的进入策略,则将丢弃该数据报,并记录日志信息。
13、
最后调用
IP
包与处理模块(
ip_rcv
),重新对新生成的
IP
包进行处理。
14、
返回
NF_DROP
,说明
IP
包不从该
HOOK
点返回。
对于非本地的
IP
包,需要将它进行转发。转发处理首先要检查
IP
头的
TTL
字段不能小于
1
,否则将向该
IP
包的发送者发送
ICMP
超时差错包。减小
TTL
值。接下来提取该
IP
包的路由信息(在转发前已经记录),根据路由信息获得该
IP
包的外出设备。然后根据外出设备的
MTU
和
IP
头的分片标志,判断允许分片,如果需要分片且不允许分片(
DF
位设置为
1
),则发送
ICMP_DEST_UNREACH
目的不可达
ICMP
报文。最后激活
HOOK
点:
IP_FORWARD
,对转发
IP
包进行相关处理。我们实现的
IPSec
外出处理模块也在此时调用。
1、
检查
IP
头的
TTL
字段:如果小于
1
,则向该
IP
包的发送者发送
ICMP
生存期超时差错包,丢弃该包
2、
减少该
IP
包的
TTL
值。
3、
提取转发前记录的路由信息,找到外出设备。
4、
根据外出设备的
MTU
和
IP
头的分片标志,判断允许分片,如果需要分片且不允许分片(
DF
位设置为
1
),则发送
ICMP_DEST_UNREACH
目的不可达
ICMP
报文。
5、
激活
HOOK
点:
NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, ip_forward_finish);
调用我们实现的
IPSec
外出处理模块。
对于本地传输层(
TCP
/
UDP
)产生的包,首先将进行路由,然后组装成
IP
包。最后包激活
HOOK
点
5
:
NF_IP_LOCAL_OUT
。在这个
HOOK
点上将调用我们实现的
IPSec
外出处理模块。
1.
查找路由
2.
填充
IP
头,组装
IP
包
3.
激活
HOOK
点
5
:
NF_IP_LOCAL_OUT
,
对于
TCP
:
NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, ip_queue_xmit2)
对于
UDP
:
NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,ip_send)
IPSec
的外出处理函数挂在
HOOK
点:
NF_IP_FORWARD
、
NF_IP_LOCAL_OUT
上。在这里首先将根据
IP
包的相关信息查找安全策略库(
SPD
),找到该
IP
包对应的外出策略。策略将决定对该
IP
包的几种处理:
ACCEPT; IPSEC SAList; DROP; TRAP; HOLD
。
ACCEPT
表示对
IP
包不做任何
IPSec
处理,允许
IP
包直接通过。
IPSEC SAList
表示将根据
SAList
指示的安全关联串(
SA
串)对
IP
包进行
IPSec
处理。
DROP
表示将丢弃策略不允许外出的
IP
包,或者先前试图等待
SA
协商,但
SA
却不能协商成功的包。
TRAP
表示要求更新一个安全关联(
SA
),此时将设置
SA
为
HOLD
,并通过发送
PF_KEY
消息来触发
IKE
守护进程进行安全关联(
SA
)的协商。
HOLD
表示
SA
还未协商成功,要求丢弃
IP
包。
对于可以进行
IPSec
外出处理的包,根据策略提供的信息,查找安全关联库(
SAD
),找到该
IP
包应该应用的安全关联(
SA
或
SA
串)。如果没有找到
SA
,则将通过发送
PF_KEY
消息来触发
IKE
守护进程进行安全关联(
SA
或
SA
串)的协商,此时
IP
包被暂时保持,直到
sa
创建或超时。如果找到了
SA
(或
SA
串),则检查
SA
的状态、生存期。然后进行
IPSec
协议处理,根据
SA
(或
SA
串)所指定的协议(
AH
或
ESP
),循环对
IP
包进行认证、加密等处理。
SA
串处理完毕后,将重新构建
IP
包,并对新的
IP
包重新进行路由。最后返回
NF_ACCEPT
,表示该
IP
包将进入激活
HOOK
点时指定的处理函数:
ip_queue_xmit2
、
ip_send
,进入发送处理模块。
1.
查找
SPD
(外出策略)库,找到匹配项的所定义的目标动作。
1)
ACCEPT
:返回
NF_ACCEPT
,即包不用经过任何加工处理,直接通过。
2)
DROP
:返回
NF_DROP
,丢掉此包不再传输
3)
TRAP
:发送
PF_key
消息
ACCQUIRE
给
IKE
守护进程,在
SPD
中把它替换成
HOLD
,返回
NF_STOLEN
,不继续传输该数据报。
4)
HOLD
:截取符合此策略的包,更新以前截获的包,返回
NF_STOLEN
,不继续传输该数据报。
5)
IPSEC Salist
:进行
IPSec
处理。以下便是详细的步骤说明。
2.
根据策略提供的信息,查找安全关联库(
SAD
),寻找该
IP
包应该应用的安全关联(
SA
或
SA
串)。如果没有找到
SA
,则将通过发送
PF_KEY
消息来触发
IKE
守护进程进行安全关联(
SA
或
SA
串)的协商,此时
IP
包被暂时保持,直到
sa
创建或超时。
3.
如果找到了
SA
(
SA
串),检查当前
SA
的状态,对于已经过时的
SA,
要释放
SA
和这个
SA
包。
4.
设置重播窗口。
5.
检查
SA
的生命期,包括使用时间,已处理的字节数,处理的
IP
包数目。对于已经过生命期的
SA,
要删除,发
PF_key
消息给
IKE
。(*
IKE
应该自己维护
SA ,
更新删除应该能自动进行不需内核干涉或提醒,但是要实现对于字节的限制应该只能由内核告诉
IKE
进行更新)
6.
根据
SA
中的协议设置
AH
或
ESP
头,然后转到
SA
链的下一个
SA
,重复从
4
开始的处理。
7.
设置新的
MTU
(如果实现了
PMTU
机制,要做相应的处理)
8.
根据
SA
指定的协议(
AH
或
ESP
)设置头空间和尾空间,并使用
SA
规定的协议中应该使用的加密或验证算法处理
IP
包数据区内容。
9.
处理结束后调整
IP
头重新设置校验和。
10.
更新
SA
的生命期
11.
对于
SA
串将循环处理
SA
串上的每一个
SA
12.
对新的
IP
包重新进行路由。
13.
返回
NF_ACCEPT
,将
IP
包传送到进入激活
HOOK
点时指定的处理函数:
ip_queue_xmit2
、
ip_send
。进入发送处理模块。
首先要判断是否需要进行分片处理。转发的
IP
包,或者是本地产生的经过
IPSec
处理过的
IP
包的长度有可能大于发送接口的
MTU
,此时必须进行分片处理。分片结束后将
IP
包交给链路层处理模块,然后由网卡发送该数据包。
1、
判断是否需要分片,如果需要,则调用分片函数
ip_fragment
进行分片。
2、
将
IP
包交给链路层处理模块:
dev_queue_xmit
。
我们将安全策略库的配置设置在安全网关上,各个安全网关独立配置策略库。但要保证策略的一致性。
安全策略库(
SPD
)说明了对
IP
数据报提供何种保护,并以何种方式实施保护。
SPD
中策略项的建立和维护应通过协商;而且对于进入和外出处理都应该有自己的策略库。对于进入或外出的每一份数据报,都可能有三种处理:丢弃、绕过或应用
IPSec
。
SPD
提供了便于用户或系统管理员进行维护的管理接口。可允许主机中的应用程序选择
IPSec
安全处理。
SPD
中的策略项记录对
SA(SA
束
)
进行了规定,其字段包含了
IPsec
协议、模式、算法和嵌套等要求。
SPD
还控制密钥管理
(
如
ISAKMP)
的数据包,即对
ISAKMP
数据包的处理明确说明。
SPD
是利用
radix
树型结构来构造。每一个结点就是一个策略项。策略项中包含一个
SAID
数据结构,它是
SPD
与
SAD
之间的接口。可以由它来查找
SAD
,从而指定相关的
SA
(或
SA
串)。这样使得策略项可以对应相关的一个
SA
或者多个
SA
(
SA
串)。
SPD
中策略项的查找是通过选择符来进行的。
SA
或
SA
束的粒度决定于选择符。通过选择符,可以找到外出或进入
IP
包应该实行的策略项。两个策略项的选择符可以相同。我们选用第一匹配项,并保证
SPD
始终以同样的顺序进行查找,这样就保证了第一匹配策略项的一致性选择。
1、
策略库的构建方式:采用
radix
树型结构。存放在内核中。
2、
选择符的构成。选择符包含下列参数:目的地
IP
地址、源
IP
地址、名字(用户
ID
、系统名字)、数据保密等级、源端口和目的端口
3、
策略项的构成。策略项包含下列参数:
radix
树相关数据结构、
SAID
结构、地址、掩码。
4、
为用户层提供的接口(
PF_key
),允许用户程序可通过接口对库进行操作:添加、删除、搜索(匹配)。
5、
对策略库的操作时机:用户通过配置界面对策略库进行相关操作;
IKE
协商
SA
完毕后,在更新
SAD
的同时,也要建立新的
SAD
与
SPD
之间的关联。
用户配置程序通过
PF_key
接口调用内核相关程序程序,实现对安全策略库的操作。
1、
添加策略项:
ipsec_create_policy
根据用户传入的信息构造策略项结构,然后将该结构加入到
radix
树中。
2、
删除策略项:
ipsec_delete_policy
根据用户传入信息,在
radix
树中找到该策略项,然后删除该节点。
3、
查找策略项:
ipsec_find_policy
根据用户传入信息,在
radix
树中查找到该策略项。
安全关联(
SA
)是构成
IPSec
的基础。
SA
是两个通信实体经过协商建立起来的一种协定。他们决定了用来保护数据保安全的
IPSec
协议、模式、算法及密钥、生存期、抗重播窗口、计数器等等。
SA
是单向的,因此外出和进入处理需要不同的
SA
。
SA
还与协议相关,每一种协议都有一个
SA
。
安全关联库(
SAD
)维护了
IPSec
协议用来保障数据保安全的
SA
记录。每个
SA
都在
SAD
中有一条记录相对应。对于外出处理,应
SPD
中查找指向
SAD
中
SA
的指针,如
SA
未建立,则应激活
IKE
建立
SA
,并同
SPD
和
SAD
的记录关联起来。对于进入处理,
SAD
的记录用目的
IP
地址、
IPSec
协议类型和
SPI
标识。
SAD
是利用
HASH
表来构造的。
SAD
的查找是通过一个三元组(
SAID
):协议、目的地址、
SPI
来进行的,三元组标识了唯一的
SA
。通过对
SAID
的散列找到
SA
头,然后再进行详细匹配找到相应的
SA
。
SA
的管理可以通过手工进行,也可以通过
IKE
来进行动态协商。为了进行
SA
的管理,我们利用
PF_key
实现了一个用户应用与内核通讯的接口。
1、
安全关联库(
SAD
)的构建方式:通过
hash
表(如图)
2、
SAD
库的查找通过一个
SAID
:
<
目的地址、协议、
SPI>
3、
SA
记录的构成。每一个
SA
的基本结构包括:
l
序号计数器:
32
比特。
l
序号计数器溢出标志:标识序号计数器是否溢出。如果溢出,则产生一个审计事件,并禁止用
SA
继续发送数据包。
l
抗重播窗口:
32
比特计数器及位图,用于决定进入的
AH
或
ESP
数据包是否为重发的。
l
AH
验证算法及其密钥等。
l
ESP
加密算法、密钥、
IV
模式、
IV
等。
l
ESP
验证算法、密钥等。如未选择验证服务,该字段为空。
l
安全关联的生存期:一个时间间隔。
l
IPsec
协议模式:隧道、传输或通配:主机实施应支持所有模式;网关实施应支持隧道模式
l
PMTU
:所考察的路径的
MTU
及其寿命变量。
4、
SAD
和
SPD
之间是通过
SAID
进行关联的。通过查看
SPD
中的
SAID
值,可对
SAD
进行查找,找到该策略项所应该实施的
SA
。
5、
安全关联库与用户程序(
IKE
)的接口,通过
PF_key
来实现。无论是手工创建一个
SA
,还是通过密钥管理协议
IKE
动态创建
SA
,都通过该接口对
SAD
和
SPD
进行操作。
1、
创建一个
SA
:
ipsec_create_sa
根据用户提供的
SA
相关参数构建
SA
结构,然后提取
SAID
值,并对
SAID
进行散列。将
SA
结构放入散列链头。
2、
删除一个
SA
:
ipsec_delete_sa
根据用户参数,提取
SAID
。根据
SAID
查找
SAD
,找到后将
SA
结构从链中删除。
3、
查找一个
SA
:
ipsec_find_sa
根据用户参数,提取
SAID
。对
SAID
散列后,在
SAD
散列表中中找到
SA
链头,再进行详细
SAID
匹配找到为一的
SA
。
各字段含义如下:
1)
下一头(
8
比特):标识紧跟验证头的下一个头的类型。
2)
载荷长度(
8
比特):以
32-
位字为单位的验证头的长度,再减去
2
。例如,缺省的验证数据字段的长度是
96
比特(
3
个
32-
位字),加上
3
个字长的固定头,头部共
6
个字长,因此该字段的值为
4
。
3)
保留(
16
比特):保留为将来使用。
4)
安全参数索引(
32
比特):用于标识一个安全关联。
5)
序号(
8
比特):单增的计数器值。
6)
验证数据(可变):该字段的长度可变(但应为
32-
位字的整数倍),包含的数据有数据包的
ICV
(完整性校验值)或
MAC
。
AH
用于为
IP
提供数据完整性、数据原始身份验证和一些可选的、有限的抗重播服务。
在安全网关上只实现隧道模式:
AH
:
外部
IP
头
|
下一个头
|
载荷长度
|
保留
|
安全参数索引
(SPI)
|
序列号
|
验证数据
|
内部
IP
头
|
TCP
头
|
数据
|
各字段含义如下:
1)
安全参数索引(
32
比特):标识一个安全关联。
2)
序号(
32
比特):单增的计数器值。
3)
载荷数据(可变):传输层数据段(传输模式)或
IP
包(隧道模式),通过加密得到保护。
4)
填充(
0-255
字节):额外的字节。有的加密算法要求明文长度是
8
位组的某个整倍数。
5)
填充长度(
8
比特):表示填充的字节数。
6)
下一头(
8
比特):通过标识载荷中的第一个头(如
IPv6
中的扩展头,或诸如
TCP
之类的上层协议头),确定载荷数据字段中数据的类型。
验证数据(可变):长度可变的字段(应为
32-
位字的整数倍),用于填入
ICV
。
ICV
的计算范围为
ESP
包中除去验证数据字段的部分。
ESP
用于为
IP
提供机密性、数据源验证、抗重播以及数据完整性等安全服务。
在安全网关上只实现隧道模式:
ESP
:
IP
头
|
安全参数索引
(SPI)
|
序列号
|
内部
IP
头
|
TCP
头
|
数据
|
|
填充项
|
填充项长度
|
下一个头
|
验证数据
|
支持多种认证算法和加密算法,并且可以动态添加。其中现有认证算法支持:
hmac-md5-96
和
hmac-sha1-96
;加密算法支持:
3des-md5-96
和
3des-sha1-96
。密钥生成分为两种:一种是预共享密钥
PSK
,一种是公私钥
RSA
。如果利用
PSK
生成密钥,则通讯双方的
PSK
密钥必须相同。
VPN
网关将记录对网关进行的各种操作,包括错误信息、安全规则文件的修改等等。并按照安全等级、事件名称和发生时间、事件内容及操作者以图表的方式显示在
VPN
配置服务器上。
通过安全事件的日志记录,可以提高
VPN
网关管理的安全性,并通过审查日志发现违反安全规则的操作,并及时采取相应的措施。
对各种流量信息进行统计:通过的总
IP
包数,丢弃的总
IP
包数、经过
IPSec
处理过的
IP
包数、绕过处理的
IP
包数,各种流过的协议,时间统计等等。
为用户提供友好的配置界面,可以对
VPN
网关的的各种信息进行配置。(如图)
用户的各种配置请求,以及密钥管理守护进程(
IKE
)都将涉及到对内核相关数据结构(安全策略库
SPD
、安全关联库
SAD
)的修改。因此我们利用
PF_KEY
协议来实现用户应用程序与内核的通讯。(如图)
如图,用户配置信息和
IKE
守护进程可以通过
PF_KEY
协议接口对内核空间的安全策略库(
SAD
)进行操作。同样,内核程序也可以通过
PF_KEY
协议接口向应用程序发出协商
SA
的请求。
PF_KEY
协议簇的
socket
的操作同其他类型的
socket
操作无差别。
int s = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
用户进程通过使用这个
socket
接口来发送和接收信息与内核通信。对这个
socket
的操作同其它
socket
一样,
bind(), connect(), socketpair(), accept(), getpeername(), getsockname(), ioctl(), and listen().
实现的主要消息种类:
◆
SADB_ADD
向内核的
SADB
增加一个
SA
◆
SADB_DELETE
从内核的
SADB
删除一个
SA
◆
SADB_GET
从内核的
SADB
获取一个
SA
◆
SADB_REGISTER IKE
守护进程注册策自己能提供服务的协议类型,
AH
,
ESP
或其它
◆
SADB_EXPIRE
内核发送给
IKE
守护进程某个
SA
过期,包括软、硬过期
1
、
PF_KEY
消息头格式
上面是
PF_KEY
消息的基本头,每个基本头后面都可以跟随一个或多个扩展域:生存期域、地址域、密钥域等等。
2
、内核实现
内核启动时调用
ipsec_init()(/ipsec_init.c),
为了使用
pf_key
接口它调用
pfkey_init()(/pfkey_v2.c)
。此函数注册它要服务的
SA
类型:
AH,ESP,IPIP,
如果配置了
IP_COMP
那么也注册。首先建立几个全局变量结构结构,然后把定义的
SA
类型添加到对应的
pfkey_supported_list
队列中,然后注册
pfkey
的
socket
操作集。
l
struct supported_list *pfkey_supported_list[SADB_SATYPE_MAX+1]
;
每种
SA
类型一个链表数组,说明了当前内核支持的
Sa
类型。
l
struct socket_list *pfkey_registered_sockets[SADB_SATYPE_MAX+1];
每种
SA
类型一个链表数组,说明了在当前内核中注册使用的
pfkey socket
。
l
struct socket_list *pfkey_open_sockets = NULL
应用
pfkey
接口的进程打开的
socket
l
struct sock *pfkey_sock_list = NULL;
应用
pfkey
接口的进程打开的
socket
对应的
sock
应用程序和内核之间通讯时,首先要建立一个
PF_KEY
消息结构,然后将消息类型和消息相关内容填入
PF_KEY
消息中,最后发送消息。
在接收消息时,将会根据不同的消息类型进行不同的处理。