一、IPsec
       IPsec不是一个单独的协议,IPsec = AH + ESP + IPcomp + IKE,IPsec包括了一组相对独立的协议:
认证协议头(AH):提供数据包的真实性验证,AH协议覆盖了从IP头到IP尾的整个数据包;
封装安全载荷(ESP):保证了数据包的机密性;
IP有效载荷压缩协议(IPComp):在对数据包封装ESP头之前进行压缩数据包;
因特网密钥交换协议(IKE):AH和ESP协议需要通信两端之间共享密钥,IKE提供了共享密钥的方法。
      在内核代码中实现了AH、ESP和IPcomp协议,而IKE是在用户层实现的后台进程,内核和用户层通过密钥管理表实现二者之间的协作。
二、IPsec框架
            userland programs                          IKE daemon
                  ^ | AF_INET{,6} socket                       ^ | PF_KEY socket
========= | | ======================== | | ======== Kernel/user boundary
                    | v                                                       | v
             transport layer, TCP/UDP        key management table
                    ^ |                                                      ^ | key information
                    | |                                                         | |
                   | v                                                       | v
               IP input/output logic <-------> AH/ESP/IPcomp logic
                   ^ |
                   | v
          Network drivers (ethernet)    
三、IPsec的工作模式:传输模式和隧道模式
1. 传输模式:传输模式只是加密了除IP首部外的报文,用以保护上层协议
`transport mode`
my host ======== peer's host
        transport
        mode
packets: [IP: me->peer] ESP payload
                        <---------> encrypted
报文格式:
---------------------------------------
| IPhdr | ESP/AH | TCP/UDP|
---------------------------------------
2. 隧道模式:隧道模式旨在加密整个报文
`tunnel mode`
        (a)                  (b)                        (c)
my host ---- my ××× gateway ======== peer's ××× gateway ---- peer's host
                            tunnel mode
packets on (a): [IP: me->peer] payload
packets on (b): [IP: mygw->peergw] ESP [IP: me->peer] payload
                                   <------------------------> encrypted
packets on (c): [IP: me->peer] payload
报文格式:
----------------------------------------------------
| IPhdr1 | ESP/AH | IPhdr2 |TCP/UDP|
----------------------------------------------------
四、IPsec体系的两个重要组建:安全策略数据库(SPDB)和SA数据库(SADB)
       每个进入或者离开IP堆栈的数据包,都必须检索SPDB,寻找可能的安全应用。每个SPDB表项定义了一对通信实体之间采取的安全策略:丢弃(discard)、绕过(none)或者应用。对于定义了“应用”行为的SPDB表项,它们均会指向SADB中的一个或一组SA,表示要将其应用于数据包。SA所组成的数据库成为SADB,用于集中存放SA.通过构成SA的三元组(安全索引参数SPI、目的IP地址、安全协议AH或ESP)可以唯一地检索到一个SA。
       对于出站的数据包当它由传送层流进IP层时IPsec首先检索SPDB判断应为数据包提供哪些安全服务如果策略要求安全服务且SA已经建立时直接应用SA。如果SA没有建立则需要首先建立SA再进行处理在SA正式建立之前,包是不会传送出去的。入站的数据包如果应用了IPsec,则由IPSEC层对这个包进行处理,IPsec层会从IP 报文中提取SPI 、目的IP地址和安全协议值,对SADB进行检索,如果SA不存在则该IP报文丢弃否则交由AH层或ESP层进行协议载荷处理。
       IPSec中的安全协议在处理数据前,必须先要和通信对方建立SA。IPsec采用了一种标准的自动的SA管理协议--IKE协议。
        IKE协议建立SA的过程分为两个阶段:第一个阶段交换建立ISAKMP SA,有了这个SA双方可以进行加密传输了,然后利用ISAKMP SA来保护第二个阶段交换以建立IPsec SA。这个阶段的交换分为主模式和积极模式两种模式,每种模式都有四种建立ISAKMP SA的方法,四种方法之间的区别在于对方身份的验证方法不同:以预共享密钥作为验证的交换以数字签名作为验证的交换以公共密钥加密作为验证的交换以修改过的公共密钥加密作为验证的交换。第二个阶段只有快速模式。
五、IPsec Tools
       IPsec tools包括setkey(配置规则策略)和racoon(密钥协商)。
六、使用setkey配置IPsec内核
例子:
[root:~ #] setkey -c << eof
> add 20.0.0.1 30.0.0.1 esp 2500 -m tunnel -E 3des-cbc "123456789012345678901234";
> eof
[root:~ #] setkey -c << eof
> spdadd 20.0.0.1:20.0.0.11 [111:1111] 30.0.0.1:30.0.0.111 [333:3333] tcp -P out ipsec esp/tunnel/20.0.0.1-30.0.0.1/use;
> eof
     第一条命令,添加从20.0.0.1与30.0.0.1之间的隧道,此隧道使用3des-cbc加密模式,其中加密密钥为"123456789012345678901234"。
      第二条命令,添加隧道的IPsec安全策略,20.0.0.1:20.0.0.11 [111:1111]发往30.0.0.1:30.0.0.111 [333:3333]的TCP包进行了加密处理(前提是密钥已经被置入内核),这条配置并没有阻止30.0.0.1:30.0.0.111 [333:3333]发往20.0.0.1:20.0.0.11 [111:1111]的明文TCP包。如果想拒绝此明文包可以使用如下命令:
spdadd 20.0.0.1:20.0.0.11 [111:1111] 30.0.0.1:30.0.0.111 [333:3333] tcp -P out ipsec esp/tunnel/20.0.0.1-30.0.0.1/require;
    如果上述例子是在保护20.0.0.1/24的安全网关上配置的(安全策略为require),那么在保护30.0.0.1/24的网关上进行如下配置:
[root:~ #] setkey -c << eof
> add 30.0.0.1 20.0.0.1 esp 2500 -m tunnel -E 3des-cbc "123456789012345678901234";
> spdadd 30.0.0.1:30.0.0.111 [333:3333] 20.0.0.1:20.0.0.11 [111:1111] tcp -P out ipsec esp/tunnel/30.0.0.1-20.0.0.1/use;
> eof  
      在通信双方的隧道都配置好后,如果通信双方可通,racoon会自动进行密钥协商,使用setkey -D可以看到发起端隧道状态由INIT变为REQU协商好后为OPEN状态,被动端的状态由INIT变为RESP最后协商好达到OPEN状态。
七、setkey配置策略的流程
       以setkey -f SA_FILE为例,展示下setkey添加隧道/规则的流程,SA_FILE中的格式为:add sip_s dip_s esp 0 -m tunnel -E scb2-cbc "1234567890123456";
1.   在词法解析token.l中,程序检验每个字段的格式合法性并转换合法字段如将add转换为ADD、将esp转换为PR_ESP;
2.   在语法解析parse.y中,程序依次解析add命令中的每个字段并赋值;
3.   在parse.y解析个字段合法后,会将SADB_ADD、隧道协议、源和目的IP地址传给setkeymsg_add()函数,在函数setkeymsg_add()中通过调用setkeymsg0()初始化msg首部。在src/include-glibc/linux/pfkeyv2.h中定义了struct sadb_msg的格式如下:
struct sadb_msg {
        uint8_t         sadb_msg_version;
        uint8_t         sadb_msg_type;
        uint8_t         sadb_msg_errno;
        uint8_t         sadb_msg_satype;
        uint16_t        sadb_msg_len;
        uint16_t        sadb_msg_reserved;
        uint32_t        sadb_msg_seq;
        uint32_t        sadb_msg_pid;
} __attribute__((packed));
     在初始化msg首部的时候,设置msg的中操作的类型为SADB_ADD。
     在setkeymsg_add()中不仅封装了消息头,还封装了功能扩展部分,其封装的结构如下所示:
             ----------------------------------------------------------------------
               |消息头|扩展头1|扩展内容1|扩展头2|扩展内容2|…………|
              ----------------------------------------------------------------------
     在src/include-glibc/linux/pfkeyv2.h中定义了struct sadb_ext 的格式如下:
struct sadb_ext {
        uint16_t        sadb_ext_len;
        uint16_t        sadb_ext_type;
} __attribute__((packed));
     每个sadb_ext_type对应了一个扩展内容结构。如:SADB_EXT_KEY_ENCRYPT对应了struct sadb_key、SADB_EXT_ADDRESS_SRC对应了struct sadb_address。
    
      add操作信息中首先要封装一个key的扩展结构,然后分别封装sa、sa2、源ip地址、目的ip地址的扩展结构。sa封装了spi值等sa信息,sa2则是封装明通、密通选项和密钥生存时间。
4.   封装好添加隧道的命令后,通过sendkeymsg()将该命令置往内核并接收内核返回的消息(使用的是PF_KEY socket)。
      接收内核返回的消息后,首先比较获得消息的长度和msg字段中的sadb_msg_len是否一致,不一致就说明在传输过程中出现错误。
5.   将得到的内核消息提交给postproc()函数进行分析,如果该消息有误(msg中的sadb_msg_errno字段值为ENOENT)时,则根据sadb_msg_type返回不同的错误代码,该错误代码在src/wst/***_error.h(自己的文件)中定义。
     在postproc()中如果检测到msg中sadb_msg_type的值为SADB_GET、SADB_DUMP、SADB_X_SPDGET、SADB_X_SPDDUMP则会显示收到的信息的内容。

PS:
1.   在阅读setkey代码的时候,不要看parse.c和token.c,这两个c文件是由parse.y和token.l这两个语法和词法解析工具生成的。我刚开始就看错了,走了些弯路。
2.   如果想给setkey增加一些特殊的参数,首先是要修改token.l然后是parse.y。
3.   源码结构:
|
|------src\-------------------------------------------------------源码
|            |----racoon\------------------------------------------ 密钥协商
|           |----libipsec\------------------------------------------setkey所依赖的函数
|            |             |-------------pfkey_dump.c-------------- -v参数主要是调用这个c文件中的函数
|          |             |------ ------ipsec_dump_policy.c-------对SP具体内容的显示
|           |             |-------- ----key_debug.c-----------------显示发送给内核和从内核接收的消息的内容
|           |----include-glibc\
|           |            |-------------linux\
|            |            |                    |-----------net\
|            |            |                    |               |------------------pfkeyv2.h---与内核交互的接口
|           |----setkey\---------------------------------------------配置管理工具
|           |             |-------------parse.y------------------------语法解析
|            |            |-------------token.l-------------------词法分析
|
八、setkey的命令参数介绍
1.   使用setkey -c开启命令行配置隧道和规则的功能,通过使用setkey -f开启文件配置方式。
     使用setkey -D显示内核中的所有隧道信息,使用setkey -DP显示内核中的所有规则信息。
     使用setkey -F清除内核中的所有隧道信息,使用setkey -FP清除内核中的所有规则信息。
2.   对于setkey -D、setkey -DP,将操作类型SADB_DUMP、SADB_X_SPDDUMP以参数形式传递给sendkeyshort()函数,在sendkeyshort()中,封装消息头后通过sendkeymsg()函数发送给内核。剩下的流程和第七节流程4、5一样。
      对于setkey -F和setkey -FP命令,将操作类型SADB_FLUSH、SADB_X_SPDFLUSH以参数形式传递给sendkeyshort()函数,在sendkeyshort()中,封装消息头后通过sendkeymsg()函数发送给内核。剩下的流程和第七节流程4、5一样。
      对于setkey -c命令,程序会在读取标准输入的内容后解析该字符串。解析过程和界面程序配置隧道规则的流程中第2步的解析过程一致,余下的步骤第七节流程3、4、5一致,如果是setkey -cv命令,那么在sendkeymsg()中会将发送的消息内容和接收到内核的回应消息内容显示出来。
九、关于racoon
       对于racoon的认识不够,这点很惭愧,主要是第一阶段的两个模式比较复杂,对理论的认识不够,导致看代码比较头痛。手头上有某仁兄的论文一篇《IPSEC中IKE协议在LINUX上的实现和研究》讲ike的实现的,这个是基于Frees/WAN的,实在没耐心看下去,目前的工作是在设计自己的规则看完setkey就够了。以后我会加上这节的。