目录
通过改造Linux的IP协议栈源代码,使得IP实现与IPSec实现完全整合。该实现按功能分,可以分为以下几个功能模块模块:安全策略库,安全关联库,AH协议处理,ESP协议处理,算法(加密卡的实现),日志、统计、配置与审计,面向应用的接口。按处理过程分,还可以分为以下几个处理模块:进入IP层预处理、IPSec进入策略处理、路由、本机对IP包处理、IPSec进入处理、转发处理、本地IP包处理、IPSec外出处理、发送处理。
Ipsec协议实现与IP实现的整合思想:利用Netfilter的HOOK机制,在接受IP包和发送IP包的过程中,在适当的处理位置调用相关的ipsec处理模块。
1.1 Netfilter的HOOK机制的介绍
图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包过滤在此点进行。
1.2 处理模块之间的关系
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
各功能模块之间的关系如图3所示。安全策略库存放了由用户或系统管理员所制定的策略,策略将决定通讯的双方是否采用IPSec处理,以及决定采用何种IPSec协议、模式、算法和嵌套需求。安全关联库由一系列安全关联项组成。安全关联是两个通讯实体经过协商建立起来的一种协定。它们决定了用来保护数据包安全所需的各种参数:序号计数器;抗重播窗口;AH验证算法及其密钥;ESP加密、认证算法、密钥;安全关联的生存期;IPsec协议模式。算法库存放了多种可选的认证和加密算法,在处理时将通过安全关联中的算法项来指明所需要使用的算法。面向应用的接口提供了管理安全策略库,以及配置网关,处理日志、统计、审计信息的接口。用户或系统管理员可通过配置服务器关联全策略库,还可以进行手工注入安全关联或者启动IKE动态协商安全关联,以及对日志、统计、配置、审计信息的提取和处理。
图3
2 各个模块的实现
2.1 处理模块的实现
2.1.1 Netfilter钩子函数
2.1.1.1 实现思想
利用Linux提供的Netfilter框架,并在Netfilter框架提供的HOOK点上注册并实现IPSec相关处理函数,使得IPSec处理能加入到IP包接收或发送处理过程的适当位置。
2.1.1.2 实现细节
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点
2.1.3 进入IP包预处理
2.1.3.1 实现思想
从网卡传来的数据报在进入IP层处理之前先进行一些检查,并在此时激活第一个HOOK点:NF_IP_PRE_ROUTING,对进入本机的包进行进入前预处理。我们实现的IPSec进入策略处理模块也在此时通过HOOK点被调用执行。
2.1.3.2 实现细节
该模块首先对传入的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进入策略处理模块。。
2.1.4 IPSec进入策略处理
2.1.4.1 实现思想
该模块将根据IP报文的相关信息构造选择符。然后通过选择符查询进入安全策略库,找到第一个匹配项(策略)。再根据策略对IP包进行策略检查和处理:
1、对于外部网的进入包
drop:(增强型包过滤)不允许某些外部网的包进入;不允许某些外部网的不加密包进入;不允许某些网应该加密却未加密的包进入;不允许某些外部网的加密包进入。
reject:类似于drop,不过要对ICMP包进行特殊处理。
accept:对某些外部网的不加密包或加密包允许通过,对已经经过IPSec处理的包(用于IPSec嵌套的情况)允许通过。
2、对于内部网的进入包
drop:对某些内部网机器的IP包不允许通过
accept:允许内部网的IP包通过
最后对于通过的包,传回到ip_rcv_finish继续处理
2.1.4.2 实现细节
1、从IP包中提取源/目的IP地址、源/目的端口、下一层协议等信息。
2、根据这些提取的信息查询进入安全策略库,找到匹配的策略项。
3、根据策略项所指定的策略进行处理:
丢弃:返回NF_DROP,Netfilter机制将丢弃该数据包,不再传输。并将该事件记录到日志中。
拒绝:返回NF_STOLEN,Netfilter机制将接管该数据包,不再继续传输。并将该事件记录到日志中。
接收:返回NF_ACCEPT,Netfilter机制将正常传输该数据包:将该包传送给ip_rcv_finish。其中对于IPSec包需要作上标记(利用标记变量nmask),表明该包需要在以后进行IPSec处理。
2.1.5 路由
2.1.5.1 实现思想
根据IP包的源地址、目的地址和服务类型(tos)等信息查找路由表,找到该IP包对应的路由项,并记录该项。在路由项中指出了IP包接下来该如何处理:对于本地IP包,将交给本机的IP处理模块ip_local_deliver;对于非本机接收包,适合转发的将交给转发处理模块ip_forward,其他情况将作特殊处理。
2.1.5.2 实现细节
1、将从IP包中提取的源地址、目的地址和服务类型值,通过适当的散列算法产生散列值。
2、根据散列值在路由缓存散列表中查找散列位置
3、进一步根据源地址、目的地址、输入接口、服务类型在散列链中找到正确的路由项。
4、根据路由项中指定的输入处理函数,调用适当的函数:对于本地IP包调用ip_local_deliver;对于其他IP包调用ip_forward。
2.1.6 本机对IP包处理
2.1.6.1 实现思想
对经过路由传送来的IP包,首先检查是否存在分片。如果存在分片,必须缓存IP分片包。等所有的分片到齐时,进行分片重组处理,将分片合成完整的IP报文。然后激活HOOK点:NF_IP_LOCAL_IN,对进入IP包进行相关处理。我们实现的IPSec进入处理模块也在此时调用。
2.1.6.2 实现细节
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进入处理模块。
2.1.7 IPSec进入处理
2.1.7.1 实现思想
首先检查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包,并进行日志记录。
2.1.7.2 实现细节
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点返回。
2.1.8 转发处理
2.1.8.1 实现思想
对于非本地的IP包,需要将它进行转发。转发处理首先要检查IP头的TTL字段不能小于1,否则将向该IP包的发送者发送ICMP超时差错包。减小TTL值。接下来提取该IP包的路由信息(在转发前已经记录),根据路由信息获得该IP包的外出设备。然后根据外出设备的MTU和IP头的分片标志,判断允许分片,如果需要分片且不允许分片(DF位设置为1),则发送ICMP_DEST_UNREACH目的不可达ICMP报文。最后激活HOOK点:IP_FORWARD,对转发IP包进行相关处理。我们实现的IPSec外出处理模块也在此时调用。
2.1.8.2 实现细节
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外出处理模块。
2.1.9 本地IP包处理
2.1.9.1 实现思想
对于本地传输层(TCP/UDP)产生的包,首先将进行路由,然后组装成IP包。最后包激活HOOK点5:NF_IP_LOCAL_OUT。在这个HOOK点上将调用我们实现的IPSec外出处理模块。
2.1.9.2 实现细节
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)
2.1.10 IPSec外出处理
2.1.10.1 实现思想
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,进入发送处理模块。
2.1.10.2 实现细节
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。进入发送处理模块。
2.1.11 发送处理
2.1.11.1 实现思想
首先要判断是否需要进行分片处理。转发的IP包,或者是本地产生的经过IPSec处理过的IP包的长度有可能大于发送接口的MTU,此时必须进行分片处理。分片结束后将IP包交给链路层处理模块,然后由网卡发送该数据包。
2.1.11.2 实现细节
1、判断是否需要分片,如果需要,则调用分片函数ip_fragment进行分片。
2、将IP包交给链路层处理模块:dev_queue_xmit。
2.2 功能模块的实现
2.2.1 策略库(SPD)的实现:
2.2.1.1 实现思想:
我们将安全策略库的配置设置在安全网关上,各个安全网关独立配置策略库。但要保证策略的一致性。
安全策略库(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始终以同样的顺序进行查找,这样就保证了第一匹配策略项的一致性选择。
2.2.1.2 实现细节
1、策略库的构建方式:采用radix树型结构。存放在内核中。
2、选择符的构成。选择符包含下列参数:目的地IP地址、源IP地址、名字(用户ID、系统名字)、数据保密等级、源端口和目的端口
3、策略项的构成。策略项包含下列参数:radix树相关数据结构、SAID结构、地址、掩码。
4、为用户层提供的接口(PF_key),允许用户程序可通过接口对库进行操作:添加、删除、搜索(匹配)。
5、对策略库的操作时机:用户通过配置界面对策略库进行相关操作;IKE协商SA完毕后,在更新SAD的同时,也要建立新的SAD与SPD之间的关联。
2.2.1.3 基本操作
用户配置程序通过PF_key接口调用内核相关程序程序,实现对安全策略库的操作。
1、添加策略项:ipsec_create_policy
根据用户传入的信息构造策略项结构,然后将该结构加入到radix树中。
2、删除策略项:ipsec_delete_policy
根据用户传入信息,在radix树中找到该策略项,然后删除该节点。
3、查找策略项:ipsec_find_policy
根据用户传入信息,在radix树中查找到该策略项。
2.2.2 安全关联库(SAD)的实现
2.2.2.1 实现思想
安全关联(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实现了一个用户应用与内核通讯的接口。
2.2.2.2 实现细节
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进行操作。
2.2.2.3 基本操作
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。
2.2.3 AH协议处理模块实现
2.2.3.1
AH格式:
各字段含义如下:
1) 下一头(8比特):标识紧跟验证头的下一个头的类型。
2) 载荷长度(8比特):以32-位字为单位的验证头的长度,再减去2。例如,缺省的验证数据字段的长度是96比特(3个32-位字),加上3个字长的固定头,头部共6个字长,因此该字段的值为4。
3) 保留(16比特):保留为将来使用。
4) 安全参数索引(32比特):用于标识一个安全关联。
5) 序号(8比特):单增的计数器值。
6) 验证数据(可变):该字段的长度可变(但应为32-位字的整数倍),包含的数据有数据包的ICV(完整性校验值)或MAC。
2.2.3.2 实现功能:
AH用于为IP提供数据完整性、数据原始身份验证和一些可选的、有限的抗重播服务。
2.2.3.3 实现模式:
在安全网关上只实现隧道模式:
AH:
外部IP头 | ||
下一个头 | 载荷长度 | 保留 |
| ||
序列号 | ||
验证数据
| ||
内部IP头 | ||
TCP头 | ||
数据 |
2.2.4 ESP协议处理模块实现
2.2.4.1
ESP格式
各字段含义如下:
1) 安全参数索引(32比特):标识一个安全关联。
2) 序号(32比特):单增的计数器值。
3) 载荷数据(可变):传输层数据段(传输模式)或IP包(隧道模式),通过加密得到保护。
4) 填充(0-255字节):额外的字节。有的加密算法要求明文长度是8位组的某个整倍数。
5) 填充长度(8比特):表示填充的字节数。
6) 下一头(8比特):通过标识载荷中的第一个头(如IPv6中的扩展头,或诸如TCP之类的上层协议头),确定载荷数据字段中数据的类型。
验证数据(可变):长度可变的字段(应为32-位字的整数倍),用于填入ICV。ICV的计算范围为ESP包中除去验证数据字段的部分。
2.2.4.2 实现功能:
ESP用于为IP提供机密性、数据源验证、抗重播以及数据完整性等安全服务。
2.2.4.3 实现模式
在安全网关上只实现隧道模式:
ESP:
IP头 | |||
安全参数索引(SPI) | |||
序列号 | |||
| |||
| |||
数据
| |||
| 填充项 | 填充项长度 | 下一个头 |
验证数据 |
2.2.5 算法实现
2.2.5.1 实现思想
支持多种认证算法和加密算法,并且可以动态添加。其中现有认证算法支持:hmac-md5-96和hmac-sha1-96;加密算法支持:3des-md5-96和3des-sha1-96。密钥生成分为两种:一种是预共享密钥PSK,一种是公私钥RSA。如果利用PSK生成密钥,则通讯双方的PSK密钥必须相同。
2.2.5.2 实现细节
2.2.6 日志、统计、配置与审计实现
2.2.6.1 日志与审计
VPN网关将记录对网关进行的各种操作,包括错误信息、安全规则文件的修改等等。并按照安全等级、事件名称和发生时间、事件内容及操作者以图表的方式显示在VPN配置服务器上。
通过安全事件的日志记录,可以提高VPN网关管理的安全性,并通过审查日志发现违反安全规则的操作,并及时采取相应的措施。
2.2.6.2 统计
对各种流量信息进行统计:通过的总IP包数,丢弃的总IP包数、经过IPSec处理过的IP包数、绕过处理的IP包数,各种流过的协议,时间统计等等。
2.2.6.3 配置
为用户提供友好的配置界面,可以对VPN网关的的各种信息进行配置。(如图)
2.2.7 应用程序与内核的通讯接口实现
2.2.7.1 实现思想
用户的各种配置请求,以及密钥管理守护进程(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过期,包括软、硬过期
2.2.7.2 实现细节
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消息中,最后发送消息。
在接收消息时,将会根据不同的消息类型进行不同的处理。