IPSEC实现


IPSEC介绍与实现

一、介绍

IPSec 协议不是一个单独的协议,它给出了应用于IP层上网络数据安全的一整套体系结构,包括网络认证协议 Authentication Header(AH)、封装安全载荷协议Encapsulating Security Payload(ESP)、密钥管理协议Internet Key Exchange (IKE)和用于网络认证及加密的一些算法等。IPSec 规定了如何在对等层之间选择安全协议、确定安全算法和密钥交换,向上提供了访问控制、数据源认证、数据加密等网络安全服务。

1、安全特性

IPSec的安全特性主要有:

·不可否认性 "不可否认性"可以证实消息发送方是唯一可能的发送者,发送者不能否认发送过消息。"不可否认性"是采用公钥技术的一个特征,当使用公钥技术时,发送方用 私钥产生一个数字签名随消息一起发送,接收方用发送者的公钥来验证数字签名。由于在理论上只有发送者才唯一拥有私钥,也只有发送者才可能产生该数字签名, 所以只要数字签名通过验证,发送者就不能否认曾发送过该消息。但"不可否认性"不是基于认证的共享密钥技术的特征,因为在基于认证的共享密钥技术中,发送 方和接收方掌握相同的密钥。

·反重播性 "反重播"确保每个IP包的唯一性,保证信息万一被截取复制后,不能再被重新利用、重新传输回目的地址。该特性可以防止攻击者截取破译信息后,再用相同的信息包冒取非法访问权(即使这种冒取行为发生在数月之后)。

·数据完整性 防止传输过程中数据被篡改,确保发出数据和接收数据的一致性。IPSec利用Hash函数为每个数据包产生一个加密检查和,接收方在打开包前先计算检查和,若包遭篡改导致检查和不相符,数据包即被丢弃。

·数据可靠性(加密) 在传输前,对数据进行加密,可以保证在传输过程中,即使数据包遭截取,信息也无法被读。该特性在IPSec中为可选项,与IPSec策略的具体设置相关。

·认证 数据源发送信任状,由接收方验证信任状的合法性,只有通过认证的系统才可以建立通信连接。

2、基于电子证书的公钥认证

一个架构良好的公钥体系,在信任状的传递中不造成任何信息外泄,能解决很多安全问题。 IPSec与特定的公钥体系相结合,可以提供基于电子证书的认证。公钥证书认证在Windows 2000中,适用于对非Windows 2000主机、独立主机,非信任域成员的客户机、或者不运行Kerberos v5认证协议的主机进行身份认证。

3、预置共享密钥认证

IPSec也可以使用预置共享密钥进行认证。预共享意味着通信双方必须在IPSec策略设置中 就共享的密钥达成一致。之后在安全协商过程中,信息在传输前使用共享密钥加密,接收端使用同样的密钥解密,如果接收方能够解密,即被认为可以通过认证。但 在Windows 2000 IPSec策略中,这种认证方式被认为不够安全而一般不推荐使用。

4、公钥加密

IPSec的公钥加密用于身份认证和密钥交换。公钥加密,也被称为"不对称加密法",即加解密过程需要两把不同的密钥,一把用来产生数字签名和加密数据,另一把用来验证数字签名和对数据进行解密。

使用公钥加密法,每个用户拥有一个密钥对,其中私钥仅为其个人所知,公钥则可分发给任意需要与 之进行加密通信的人。例如:A想要发送加密信息给B,则A需要用B的公钥加密信息,之后只有B才能用他的私钥对该加密信息进行解密。虽然密钥对中两把钥匙 彼此相关,但要想从其中一把来推导出另一把,以目前计算机的运算能力来看,这种做法几乎完全不现实。因此,在这种加密法中,公钥可以广为分发,而私钥则需 要仔细地妥善保管。

5、Hash函数和数据完整性

Hash信息验证码HMAC(Hash message authentication codes)验证接收消息和发送消息的完全一致性(完整性)。这在数据交换中非常关键,尤其当传输媒介如公共网络中不提供安全保证时更显其重要性。

HMAC结合hash算法和共享密钥提供完整性。Hash散列通常也被当成是数字签名,但这种说法不够准确,两者的区别在于:Hash散列使用共享密钥,而数字签名基于公钥技术。hash算法也称为消息摘要或单向转换。称它为单向转换是因为:

1)双方必须在通信的两个端头处各自执行Hash函数计算;

2)使用Hash函数很容易从消息计算出消息摘要,但其逆向反演过程以目前计算机的运算能力几乎不可实现。

Hash散列本身就是所谓加密检查和或消息完整性编码MIC(Message Integrity Code),通信双方必须各自执行函数计算来验证消息。举例来说,发送方首先使用HMAC算法和共享密钥计算消息检查和,然后将计算结果A封装进数据包中 一起发送;接收方再对所接收的消息执行HMAC计算得出结果B,并将B与A进行比较。如果消息在传输中遭篡改致使B与A不一致,接收方丢弃该数据包。

有两种最常用的hash函数:

·HMAC-MD5 MD5(消息摘要5)基于RFC1321。MD5对MD4做了改进,计算速度比MD4稍慢,但安全性能得到了进一步改善。MD5在计算中使用了64个32位常数,最终生成一个128位的完整性检查和。

·HMAC-SHA 安全Hash算法定义在NIST FIPS 180-1,其算法以MD5为原型。 SHA在计算中使用了79个32位常数,最终产生一个160位完整性检查和。SHA检查和长度比MD5更长,因此安全性也更高。

6、加密和数据可靠性

IPSec使用的数据加密算法是DES--Data Encryption Standard(数据加密标准)。DES密钥长度为56位,在形式上是一个64位数。DES以64位(8字节)为分组对数据加密,每64位明文,经过 16轮置换生成64位密文,其中每字节有1位用于奇偶校验,所以实际有效密钥长度是56位。 IPSec还支持3DES算法,3DES可提供更高的安全性,但相应地,计算速度更慢。

7、密钥管理

·动态密钥更新

IPSec策略使用"动态密钥更新"法来决定在一次通信中,新密钥产生的频率。动态密钥指在通 信过程中,数据流被划分成一个个"数据块",每一个"数据块"都使用不同的密钥加密,这可以保证万一攻击者中途截取了部分通信数据流和相应的密钥后,也不 会危及到所有其余的通信信息的安全。动态密钥更新服务由Internet密钥交换IKE(Internet Key Exchange)提供,详见IKE介绍部分。

IPSec策略允许专家级用户自定义密钥生命周期。如果该值没有设置,则按缺省时间间隔自动生成新密钥。

·密钥长度

密钥长度每增加一位,可能的密钥数就会增加一倍,相应地,破解密钥的难度也会随之成指数级加大。IPSec策略提供多种加密算法,可生成多种长度不等的密钥,用户可根据不同的安全需求加以选择。

·Diffie-Hellman算法

要启动安全通讯,通信两端必须首先得到相同的共享密钥(主密钥),但共享密钥不能通过网络相互发送,因为这种做法极易泄密。

Diffie-Hellman算法是用于密钥交换的最早最安全的算法之一。DH算法的基本工作 原理是:通信双方公开或半公开交换一些准备用来生成密钥的"材料数据",在彼此交换过密钥生成"材料"后,两端可以各自生成出完全一样的共享密钥。在任何 时候,双方都绝不交换真正的密钥。

通信双方交换的密钥生成"材料",长度不等,"材料"长度越长,所生成的密钥强度也就越高,密钥破译就越困难。 除进行密钥交换外,IPSec还使用DH算法生成所有其他加密密钥。

二、IPSEC使用

1.编译kernel 2.6

必须选择下面的选择

CONFIG_INET_AH

CONFIG_INET_ESP

CONFIG_XFRM_USER

可能还要安装module-init-tool

如何生成kernel看另外的文档

2.ipsec-tools

 /configure --prefix=/

 make

 make install

3.两台机器的通讯

 linux(192.168.0.254) host-A--------------linux box(192.168.0.141)host-B

在一台linux中

#加入pf_sock

modprobe af_key

#加密

modprobe md5

modprobe des

#AH

modprobe ah4

#esp

modprobe esp4

cat >setkey.sh <<EOF

#!/sbin/setkey -f

flush;

spdflush;

# AH

add 192.168.0.141 192.168.0.254 ah 15700 -A hmac-md5 "1234567890123456";

add 192.168.0.254 192.168.0.141 ah 24500 -A hmac-md5 "1234567890123456";

# ESP

add 192.168.0.141 192.168.0.254 esp 15701 -E 3des-cbc "123456789012123456789012";

add 192.168.0.254 192.168.0.141 esp 24501 -E 3des-cbc "123456789012123456789012";

spdadd 192.168.0.141 192.168.0.254 any -P out ipsec

           esp/transport//require

           ah/transport//require;

spdadd 192.168.0.254 192.168.0.141 any -P in ipsec

           esp/transport//require

           ah/transport//require;

EOF 

执行setkey后,就可以通讯了

速度测试:

   没有ipsec          有ipsec

A->B   10.21M/s     2.43M/s

B->A   10.94M/s     2.27M/s

上面的用的是手工密钥,可以还可以用Preshared Keys,X.509 Certificates。

其中/usr/share/ssl/misc/CA可以用来生成X.509 Certificates

生成证书:

mkdir certs

cd certs

/usr/share/ssl/misc/CA -newca

# 254 passwd :ca254

# 141 passwd :ca141

/usr/share/ssl/misc/CA -newreq

# 254 passwd :cert254

# 141 passwd :cert141

#sign it using the certificate authority??

/usr/share/ssl/misc/CA -sign

mv newcert.pem vpngateway_cert.pem

mv newreq.pem vpngateway_key.pem

mkdir /etc/certs

cp ~/certs/*.pem /etc/certs/

#因为racoon不认这个key的格式,转一下

cd /etc/

openssl rsa -in 254_key.pem -out 254_key.pem

#input cert254

4.网关之间的通讯

 C(192.168.0.119)---(192.168.0.114)linux(10.0.0.12)---(10.0.0.13)linux(192.168.0.115)----C(192.168.0.253)

 和上面差不多


                       IPSEC实现

Linux2.6内核中自带了IPSEC的实现 该实现包括以下几个部分: PF_KEY类型套接口, 用来提供和用户层空间进行PF_KEY通信,代码在net
/key目录下. 安全联盟SA和安全策略SP管理,是使用xfrm库来实现的,代码在net/xfrm/目录下定义. ESP,AH等协议实现,在net/ipv4(6)下定义. 加密认证算法库,在crypto目录下定义,这些算法都是标准代码了. 本文主要描述XFRM库的实现以及在IPV4下相关协议的处理部分, IPV6的忽略. xfrm是内核中变化比较大的部分,每个版本中都有不小的差异, 同时也说明了该模块的不成熟性。 在net/xfrm目录下的各文件大致功能说明如下: xfrm_state.c: xfrm状态管理 xfrm_policy.c: xfrm策略管理 xfrm_algo.c: 算法管理 xfrm_hash.c: HASH计算函数 xfrm_input.c: 安全路径(sec_path)处理,用于进入的ipsec包 xfrm_user.c: netlink接口的SA和SP管理 在net/ipv4目录下的和ipsec相关各文件大致功能说明如下: ah4.c: IPV4的AH协议处理 esp4.c: IPV4的ESP协议处理 ipcomp.c: IP压缩协议处理 xfrm4_input: 接收的IPV4的IPSEC包处理 xfrm4_output: 发出的IPV4的IPSEC包处理 xfrm4_state: IPV4的SA处理 xfrm4_policy: IPV4的策略处理 xfrm4_tunnel: IPV4的通道处理 xfrm4_mode_transport: 传输模式 xfrm4_mode_tunnel: 通道模式 xfrm4_mode_beet: BEET模式 本文Linux内核代码版本为2.6.24 [数据结构] 内核SA的定义用xfrm_state结构定义,SP用xfrm_policy结构定义,在include/net/xfrm.h中定义. struct xfrm_state { struct hlist_node bydst; // 按目的地址HASH struct hlist_node bysrc; // 按源地址HASH struct hlist_node byspi; // 按SPI值HASH atomic_t refcnt;// 所有使用计数 spinlock_t lock; // 状态锁 struct xfrm_id id; // ID结构, 即目的地址,SPI,协议三元组 struct xfrm_selector sel; // 状态选择器 u32 genid; // 状态的标志值, 防止发生碰撞 struct { // KEY回调管理处理结构参数 u8 state; u8 dying; u32 seq; } km; /* Parameters of this state. */ struct { // SA相关参数结构 u32 reqid; // 请求ID u8 mode; // 模式: 传输/通道 u8 replay_window; // 回放窗口 u8 aalgo, ealgo, calgo; // 认证,加密,压缩算法ID值 u8 flags; // 一些标志 u16 family; // 协议族 xfrm_address_t saddr; // 源地址 int header_len; // 添加的协议头长度 int trailer_len; //踪迹长度 } props; struct xfrm_lifetime_cfg lft; // 生存时间配置 /* Data for transformer */ struct xfrm_algo *aalg; // 认证算法 struct xfrm_algo *ealg; // 加密算法 struct xfrm_algo *calg; // 压缩算法 /* Data for encapsulator */ struct xfrm_encap_tmpl *encap; // NAT-T封装信息 /* Data for care-of address */ xfrm_address_t *coaddr; /* IPComp needs an IPIP tunnel for handling uncompressed packets */ struct xfrm_state *tunnel; // 通道, 实际是另一个SA /* If a tunnel, number of users + 1 */ atomic_t tunnel_users; // 通道的使用数 /* State for replay detection */ struct xfrm_replay_state replay; // 回放检测结构,包含各种序列号掩码等信息 /* Replay detection state at the time we sent the last notification */ struct xfrm_replay_state preplay; // 上次的回放记录值 /* internal flag that only holds state for delayed aevent at the moment */ u32 xflags; // 标志 /* Replay detection notification settings */ u32 replay_maxage; // 回放最大时间间隔 u32 replay_maxdiff; // 回放最大差值 /* Replay detection notification timer */ struct timer_list rtimer; // 回放检测定时器 /* Statistics */ struct xfrm_stats stats; // 统计值 struct xfrm_lifetime_cur curlft; // 当前时间计数器 struct timer_list timer; // SA定时器 /* Last used time */ u64 lastused; // 上次使用时间 /* Reference to data common to all the instances of this transformer. */ struct xfrm_type *type; // 协议, ESP/AH/IPCOMP struct xfrm_mode *inner_mode; // 进入或输出的模式, 通道或传输 struct xfrm_mode *outer_mode; /* Security context */ struct xfrm_sec_ctx *security; // 安全上下文, 加密时使用 /* Private data of this transformer, format is opaque, * interpreted by xfrm_type methods. */ void *data; // 内部数据 }; struct xfrm_policy { struct xfrm_policy *next; // 下一个策略 struct hlist_node bydst; // 按目的地址HASH的链表 struct hlist_node byidx; // 按索引号HASH的链表 /* This lock only affects elements except for entry. */ rwlock_t lock; // 策略结构锁 atomic_t refcnt; // 引用次数 struct timer_list timer; // 策略定时器 u32 priority; // 策略优先级 u32 index; // 策略索引号 struct xfrm_selector selector;// 选择器 struct xfrm_lifetime_cfg lft; // 策略生命期 struct xfrm_lifetime_cur curlft; // 当前的生命期数据 struct dst_entry *bundles;// 路由链表 u16 family; // 协议族 u8 type; // 类型 u8 action; // 策略动作, 接受/加密/阻塞等 u8 flags; // 标志 u8 dead; // 策略死亡标志 u8 xfrm_nr;// 使用的xfrm_vec的数量 /* XXX 1 byte hole, try to pack */ struct xfrm_sec_ctx *security;// 安全上下文 struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH];// 状态模板 }; xfrm模板结构, 用于状态和策略的查询 struct xfrm_tmpl { /* id in template is interpreted as: * daddr - destination of tunnel, may be zero for transport mode. * spi - zero to acquire spi. Not zero if spi is static, then * daddr must be fixed too. * proto - AH/ESP/IPCOMP */ struct xfrm_id id; // SA三元组, 目的地址, 协议, SPI xfrm_address_t saddr;// 源地址 unsigned short encap_family; __u32 reqid;// 请求ID /* Mode: transport, tunnel etc. */ __u8 mode; /* Sharing mode: unique, this session only, this user only etc. */ __u8 share; /* May skip this transfomration if no SA is found */ __u8 optional; /* Bit mask of algos allowed for acquisition */ __u32 aalgos; __u32 ealgos; __u32 calgos; }; 对ESP, AH, IPCOMP等协议的描述是通过xfrm_type结构来描述的, 多个协议的封装就是靠多个协议结构形成的链表来实现 struct xfrm_type { char *description; // 描述字符串 struct module *owner; // 协议模块 __u8 proto; // 协议值 __u8 flags; // 标志 #define XFRM_TYPE_NON_FRAGMENT 1 #define XFRM_TYPE_REPLAY_PROT 2 int (*init_state)(struct xfrm_state *x); // 初始化状态 void (*destructor)(struct xfrm_state *); // 析构函数 int (*input)(struct xfrm_state *, struct sk_buff *skb); // 数据输入函数 int (*output)(struct xfrm_state *, struct sk_buff *pskb); // 数据输出函数 int (*reject)(struct xfrm_state *, struct sk_buff *, struct flowi *); // 拒绝函数 int (*hdr_offset)(struct xfrm_state *, struct sk_buff *, u8 **); // 头部偏移 xfrm_address_t *(*local_addr)(struct xfrm_state *, xfrm_address_t *); // 本地地址 xfrm_address_t *(*remote_addr)(struct xfrm_state *, xfrm_address_t *);// 远程地址 /* Estimate maximal size of result of transformation of a dgram */ u32 (*get_mtu)(struct xfrm_state *, int size); // 最大数据报长度 }; 模式结构用于描述IPSEC连接描述, 可为通道模式或传输模式两种. struct xfrm_mode { int (*input)(struct xfrm_state *x, struct sk_buff *skb); // 数据输入函数 int (*output)(struct xfrm_state *x,struct sk_buff *skb); // 数据输出函数 struct xfrm_state_afinfo *afinfo; //策略的相关协议处理结构 struct module *owner; unsigned int encap; // 封装 int flags; //标志 }; 以下结构用于描述具体协议族下的的策略处理. struct xfrm_policy_afinfo { unsigned short family; // 协议族 struct dst_ops *dst_ops;// 目的操作结构 void (*garbage_collect)(void);// 垃圾搜集 int (*dst_lookup)(struct xfrm_dst **dst, struct flowi *fl);// 路由选择 int (*get_saddr)(xfrm_address_t *saddr, xfrm_address_t *daddr);// 获取源地址 struct dst_entry *(*find_bundle)(struct flowi *fl, struct xfrm_policy *policy);// 查找路由项 int (*bundle_create)(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx, struct flowi *fl, struct dst_entry **dst_p);// 创建新路由项 void (*decode_session)(struct sk_buff *skb, struct flowi *fl);// 解码会话 }; 状态的相关协议处理结构 struct xfrm_state_afinfo { unsigned int family;// 协议族 struct module *owner; struct xfrm_type *type_map[IPPROTO_MAX]; struct xfrm_mode *mode_map[XFRM_MODE_MAX]; int (*init_flags)(struct xfrm_state *x);// 初始化标志 void (*init_tempsel)(struct xfrm_state *x, struct flowi *fl, struct xfrm_tmpl *tmpl, xfrm_address_t *daddr, xfrm_address_t *saddr);// 初始化模板选择 int (*tmpl_sort)(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n);// 模板排序 int (*state_sort)(struct xfrm_state **dst, struct xfrm_state **src, int n);// 状态排序 int (*output)(struct sk_buff *skb); }; IPV4的状态相关协议处理结构 static struct xfrm_state_afinfo xfrm4_state_afinfo = { //net/ipv4/xfrm4_state.c .family = AF_INET, .owner = THIS_MODULE, .init_flags = xfrm4_init_flags, .init_tempsel = __xfrm4_init_tempsel, .output = xfrm4_output, }; 回调通知信息结构 struct xfrm_mgr { struct list_head list; char *id; int (*notify)(struct xfrm_state *x, struct km_event *c);// 状态通知 int (*acquire)(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *xp, int dir);// 获取, 如获取SA struct xfrm_policy *(*compile_policy)(struct sock *sk, int opt, u8 *data, int len, int *dir);// 编译策略 int (*new_mapping)(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 sport);// 映射 int (*notify_policy)(struct xfrm_policy *x, int dir, struct km_event *c);// 策略通知 int (*report)(u8 proto, struct xfrm_selector *sel, xfrm_address_t *addr);// 报告 int (*migrate)(struct xfrm_selector *sel, u8 dir, u8 type, struct xfrm_migrate *m, int num_bundles);//迁移 }; static struct xfrm_mgr pfkeyv2_mgr = // net/key/pf_key.c { .id = "pfkeyv2", .notify = pfkey_send_notify, .acquire = pfkey_send_acquire, .compile_policy = pfkey_compile_policy, .new_mapping = pfkey_send_new_mapping, .notify_policy = pfkey_send_policy_notify, .migrate = pfkey_send_migrate, }; [/数据结构] [初始化] int __init ip_rt_init(void) //ip路由初始化函数 { ...... #ifdef CONFIG_XFRM xfrm_init(); xfrm4_init(); #endif ...... } net/xfrm/xfrm_policy.c xfrm初始化函数包括状态, 策略和输入处理的初始化函数 xfrm是不支持模块方式的 void __init xfrm_init(void) { xfrm_state_init(); xfrm_policy_init(); xfrm_input_init(); } 状态初始化 void __init xfrm_state_init(void) { unsigned int sz; //初始HASH表不大, 每个HASH中初始化为8个链表, 但随着状态数量的增加会动态增加HASH表数量 sz = sizeof(struct hlist_head) * 8; //建立3组HASH, 分别按SA的源地址, 目的地址和SPI值 xfrm_state_bydst = xfrm_hash_alloc(sz); xfrm_state_bysrc = xfrm_hash_alloc(sz); xfrm_state_byspi = xfrm_hash_alloc(sz); if (!xfrm_state_bydst || !xfrm_state_bysrc || !xfrm_state_byspi) panic("XFRM: Cannot allocate bydst/bysrc/byspi hashes."); //xfrm_state_hmask初始值为=7 xfrm_state_hmask = ((sz / sizeof(struct hlist_head)) - 1); //初始化工作队列work_queue, 完成对状态垃圾的搜集和释放 INIT_WORK(&xfrm_state_gc_work, xfrm_state_gc_task); } 策略初始化 static void __init xfrm_policy_init(void) { unsigned int hmask, sz; int dir; //建立一个内核cache, 用于分配xfrm_dst结构 xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache", sizeof(struct xfrm_dst), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); //分配状态HASH表, 初始是8个HASH链表,以后随着策略数量的增加会动态增加HASH表的数量 hmask = 8 - 1; sz = (hmask+1) * sizeof(struct hlist_head); //该HASH表是按策略的index参数进行索引的 xfrm_policy_byidx = xfrm_hash_alloc(sz); xfrm_idx_hmask = hmask; if (!xfrm_policy_byidx) panic("XFRM: failed to allocate byidx hash\n"); //输入, 输出, 转发三个处理点, 双向 for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) { struct xfrm_policy_hash *htab; //初始化inexact链表头, inexact处理选择子相关长度不是标准值的一些特别策略 INIT_HLIST_HEAD(&xfrm_policy_inexact[dir]); //分配按地址HASH的HASH表 htab = &xfrm_policy_bydst[dir]; htab->table = xfrm_hash_alloc(sz); htab->hmask = hmask; if (!htab->table) panic("XFRM: failed to allocate bydst hash\n"); } //初始化策略垃圾搜集的工作队列, 完成对策略垃圾的搜集和释放 INIT_WORK(&xfrm_policy_gc_work, xfrm_policy_gc_task); //登记网卡通知,参看下面网卡通知回调实现 register_netdevice_notifier(&xfrm_dev_notifier); } 输入初始化 struct sec_path结构是对输入的加密包进行层层解包的处理, 在sk_buff中有该结构的指针sp, 如果sp非空表示这是个IPSEC解密后的包. void __init xfrm_input_init(void) { //建立一个内核cache, 用于分配sec_path结构(安全路径) secpath_cachep = kmem_cache_create("secpath_cache", sizeof(struct sec_path), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); } //协议相关的状态策略初始化,注册两个具体的相关协议的处理结构 void __init xfrm4_init(void) { xfrm4_state_init();//xfrm_state_register_afinfo(&xfrm4_state_afinfo); xfrm4_policy_init();//xfrm_policy_register_afinfo(&xfrm4_policy_afinfo); } 协议初始化 在net/ipv4/xfrm4_tunnel.c中定义ipip协议的接收处理函数 static struct xfrm_tunnel xfrm_tunnel_handler = { .handler = xfrm_tunnel_rcv, .err_handler = xfrm_tunnel_err, .priority = 2, }; static int __init ipip_init(void) { if (xfrm_register_type(&ipip_type, AF_INET) < 0) { //参考上面数据结构部分 printk(KERN_INFO "ipip init: can't add xfrm type\n"); return -EAGAIN; } if (xfrm4_tunnel_register(&xfrm_tunnel_handler, AF_INET)) { //参考ip隧道基础研究文章 printk(KERN_INFO "ipip init: can't add xfrm handler for AF_INET\n"); xfrm_unregister_type(&ipip_type, AF_INET); return -EAGAIN; } ...... return 0; } 在net/ipv4/esp4.c中定义了esp协议的接收处理函数. static struct net_protocol esp4_protocol = { .handler = xfrm4_rcv, .err_handler = esp4_err, .no_policy = 1, }; static int __init esp4_init(void) { if (xfrm_register_type(&esp_type, AF_INET) < 0) { //参考上面数据结构部分 printk(KERN_INFO "ip esp init: can't add xfrm type\n"); return -EAGAIN; } if (inet_add_protocol(&esp4_protocol, IPPROTO_ESP) < 0) { //参考ip隧道基础研究文章 printk(KERN_INFO "ip esp init: can't add protocol\n"); xfrm_unregister_type(&esp_type, AF_INET); return -EAGAIN; } return 0; } 在net/ipv4/ah4.c中定义了ah协议的接收处理函数,与esp基本一样 static struct net_protocol ah4_protocol = { .handler = xfrm4_rcv, .err_handler = ah4_err, .no_policy = 1, }; static int __init ah4_init(void) { if (xfrm_register_type(&ah_type, AF_INET) < 0) { printk(KERN_INFO "ip ah init: can't add xfrm type\n"); return -EAGAIN; } if (inet_add_protocol(&ah4_protocol, IPPROTO_AH) < 0) { printk(KERN_INFO "ip ah init: can't add protocol\n"); xfrm_unregister_type(&ah_type, AF_INET); return -EAGAIN; } return 0; } [/初始化] [数据接收] 下面我们先看ipip协议的接收处理函数. static int xfrm_tunnel_rcv(struct sk_buff *skb) { return xfrm4_rcv_spi(skb, IPPROTO_IPIP, ip_hdr(skb)->saddr); } esp和ah协议的接收处理函数 int xfrm4_rcv(struct sk_buff *skb) { return xfrm4_rcv_spi(skb, ip_hdr(skb)->protocol, 0); } 都调用到同样的函数 static inline int xfrm4_rcv_spi(struct sk_buff *skb, int nexthdr, __be32 spi) { return xfrm4_rcv_encap(skb, nexthdr, spi, 0); } 实际就是 int xfrm4_rcv_encap(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) { int err; __be32 seq; struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH]; struct xfrm_state *x; int xfrm_nr = 0; int decaps = 0; unsigned int nhoff = offsetof(struct iphdr, protocol); //协议字段在ip头中的偏移位置 seq = 0; //获取skb中的spi和序列号信息,ipip时spi不为0 if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) goto drop; //进入循环进行解包操作 do { const struct iphdr *iph = ip_hdr(skb); if (xfrm_nr == XFRM_MAX_DEPTH)//循环解包次数太深的话放弃,目前定义是 6 goto drop; //根据地址, SPI和协议查找SA //主要调用__xfrm_state_lookup(daddr, spi, proto, family);上下有锁保护 x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, nexthdr, AF_INET); if (x == NULL) goto drop; // 以下根据SA定义的操作对数据解码 spin_lock(&x->lock); if (unlikely(x->km.state != XFRM_STATE_VALID)) goto drop_unlock; //检查由SA指定的封装类型是否和函数指定的封装类型相同 if ((x->encap ? x->encap->encap_type : 0) != encap_type) goto drop_unlock; //SA重放窗口检查 if (x->props.replay_window && xfrm_replay_check(x, seq)) goto drop_unlock; //SA生存期检查 if (xfrm_state_check_expire(x)) goto drop_unlock; //type可为esp,ah,ipcomp, ipip等, 调用具体协议的输入函数对输入数据解密,参看下面具体协议实现 nexthdr = x->type->input(x, skb); if (nexthdr <= 0) goto drop_unlock; skb_network_header(skb)[nhoff] = nexthdr; //修改为解密后的协议 /* only the first xfrm gets the encap type */ encap_type = 0; //更新重放窗口 if (x->props.replay_window) xfrm_replay_advance(x, seq); //包数,字节数统计 x->curlft.bytes += skb->len; x->curlft.packets++; spin_unlock(&x->lock); //保存数据解封用的SA, 增加SA数量计数 xfrm_vec[xfrm_nr++] = x; //mode可为通道,传输等模式, 调用具体模式输入函数对数据解封装,参看下面模式函数实现 if (x->outer_mode->input(x, skb)) goto drop; //如果是IPSEC通道模式,将decaps参数置1,否则表示是传输模式 if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) { decaps = 1; break; } //看内层协议是否还要继续解包, 不需要解时返回1, 需要解时返回0, 错误返回负数 //协议类型可以多层封装的,比如用AH封装ESP, 就得先解完AH再解ESP err = xfrm_parse_spi(skb, nexthdr, &spi, &seq); if (err < 0) goto drop; } while (!err); /* Allocate new secpath or COW existing one. */ // 为skb包建立新的安全路径(struct sec_path) if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) { struct sec_path *sp; sp = secpath_dup(skb->sp); if (!sp) goto drop; if (skb->sp) secpath_put(skb->sp); skb->sp = sp; } if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH) goto drop; //将刚才循环解包用到的SA拷贝到安全路径 //因此检查一个数据包是否是普通明文包还是解密后的明文包就看skb->sp参数是否为空 memcpy(skb->sp->xvec + skb->sp->len, xfrm_vec, xfrm_nr * sizeof(xfrm_vec[0])); skb->sp->len += xfrm_nr; nf_reset(skb); //去掉ip_conntrack和bridge if (decaps) {//通道模式,IPIP协议 dst_release(skb->dst); //去掉路由缓存 skb->dst = NULL; netif_rx(skb); //重新进入网卡接收函数 return 0; } else { //传输模式 #ifdef CONFIG_NETFILTER //如果定义NETFILTER, 进入PRE_ROUTING链处理,然后进入路由选择处理,其实现在已经处于INPUT点, //但解码后需要将该包作为一个新包看待。可能需要进行目的NAT操作, 这时候可能目的地址就会改变不是到自身的了, //因此需要将其相当于是放回PRE_PROUTING点去操作, 重新找路由. //这也说明可以制定针对解码后明文包的NAT规则,在还是加密包的时候不匹配但解码后能匹配上 __skb_push(skb, skb->data - skb_network_header(skb)); ip_hdr(skb)->tot_len = htons(skb->len); ip_send_check(ip_hdr(skb)); NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL, xfrm4_rcv_encap_finish); return 0; #else //内核不支持NETFILTER, 该包肯定就是到自身的了。返回IP协议的负值, 表示重新进行IP层协议的处理 //用解码后的内层协议来处理数据,具体看ip_local_deliver_finish函数实现 return -ip_hdr(skb)->protocol; #endif } drop_unlock: spin_unlock(&x->lock); xfrm_state_put(x); drop: while (--xfrm_nr >= 0) xfrm_state_put(xfrm_vec[xfrm_nr]); kfree_skb(skb); return 0; } 解析AH,ESP数据包中的SPI和序号,返回值是网络序的 int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq) { int offset, offset_seq; int hlen; //通过nexthdr参数来判断协议类型, nexthdr是IPV6里的说法, 在IPV4中就是IP头里的协议字段 //根据不同协议确定数据中SPI和序列号相对数据起始点的偏移 switch (nexthdr) { case IPPROTO_AH: hlen = sizeof(struct ip_auth_hdr); offset = offsetof(struct ip_auth_hdr, spi); offset_seq = offsetof(struct ip_auth_hdr, seq_no); break; case IPPROTO_ESP: hlen = sizeof(struct ip_esp_hdr); offset = offsetof(struct ip_esp_hdr, spi); offset_seq = offsetof(struct ip_esp_hdr, seq_no); break; case IPPROTO_COMP: if (!pskb_may_pull(skb, sizeof(struct ip_comp_hdr))) return -EINVAL; //SPI值取第3,4字节的数据, 序号为0 *spi = htonl(ntohs(*(__be16*)(skb_transport_header(skb) + 2))); *seq = 0; return 0; default: return 1; } if (!pskb_may_pull(skb, hlen)) return -EINVAL; //根据偏移获取SPI和序号, 注意是网络序的值 *spi = *(__be32*)(skb_transport_header(skb) + offset); *seq = *(__be32*)(skb_transport_header(skb) + offset_seq); return 0; } 根据SPI进行HASH后查找 static struct xfrm_state *__xfrm_state_lookup(xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family) { unsigned int h = xfrm_spi_hash(daddr, spi, proto, family);//根据SPI进行HASH struct xfrm_state *x; struct hlist_node *entry; //循环相应的SPI链表 hlist_for_each_entry(x, entry, xfrm_state_byspi+h, byspi) { //比较协议族, SPI, 和协议是否相同 if (x->props.family != family || x->id.spi != spi || x->id.proto != proto) continue; switch (family) { //比较目的地址是否相同 case AF_INET: if (x->id.daddr.a4 != daddr->a4) continue; break; case AF_INET6: if (!ipv6_addr_equal((struct in6_addr *)daddr, (struct in6_addr *)x->id.daddr.a6)) continue; break; } xfrm_state_hold(x); return x; } } static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb) { //如果没有路由, 重新查找路由 if (skb->dst == NULL) { const struct iphdr *iph = ip_hdr(skb); if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev)) goto drop; } //调用相关的路由输入函数 return dst_input(skb); drop: kfree_skb(skb); return NET_RX_DROP; } [/数据接收] [数据发送] IPV4的IPSEC数据发送处理在net/ipv4/xfrm4_output.c中定义,作为安全路由的输出函数. dst_output -> xfrm_dst->route->output == xfrm4_output int xfrm4_output(struct sk_buff *skb) { //就是一个条件HOOK, 当skb包不带IPSKB_REROUTED标志时进入POSTROUTING点的NAT操作 //这是数据在xfrm策略中多个bundle时会多次调用, 也就是数据在封装完成前可以进行源NAT操作 //HOOK出口函数为xfrm4_output_finish return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev, xfrm4_output_finish, !(IPCB(skb)->flags & IPSKB_REROUTED)); } 发送结束处理 static int xfrm4_output_finish(struct sk_buff *skb) { struct sk_buff *segs; #ifdef CONFIG_NETFILTER //如果内核定义了NETFILTER, 当到达最后一个路由(普通路由)时, 设置IPSKB_REROUTED标志, //进行普通路由发出函数(ip_output), 设置该标志后不进行源NAT操作 if (!skb->dst->xfrm) { IPCB(skb)->flags |= IPSKB_REROUTED; return dst_output(skb); } #endif //如果skb包不是是gso, 转xfrm4_output_finish2 if (!skb_is_gso(skb)) return xfrm4_output_finish2(skb); //处理gso数据包, 最终也是使用xfrm4_output_finish2处理数据包 skb->protocol = htons(ETH_P_IP); segs = skb_gso_segment(skb, 0); kfree_skb(skb); if (unlikely(IS_ERR(segs))) return PTR_ERR(segs); do { //循环发送 struct sk_buff *nskb = segs->next; int err; segs->next = NULL; err = xfrm4_output_finish2(segs); if (unlikely(err)) { while ((segs = nskb)) { nskb = segs->next; segs->next = NULL; kfree_skb(segs); } return err; } segs = nskb; } while (segs); return 0; } 第2级发送结束处理 static int xfrm4_output_finish2(struct sk_buff *skb) { int err; //根据安全路由包装要发送的数据 while (likely((err = xfrm4_output_one(skb)) == 0)) { //处理成功,释放skb中的netfilter信息 nf_reset(skb); //重新将该包作为初始发送包, 进入OUTPUT点处理, 注意这是个函数而不是宏 //如果内核没定义NETFILTER, 该函数只是个空函数 //返回1表示NF_ACCEPT err = nf_hook(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dst->dev, dst_output); if (unlikely(err != 1)) break; //如果已经没有SA, 就只是个普通包了, 路由发送(ip_output)返回, 退出循环 if (!skb->dst->xfrm) return dst_output(skb); //如果还有SA, 目前还只是中间状态, 还可以进行SNAT操作, 进入POSTROUTING点处理 err = nf_hook(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev, xfrm4_output_finish2); if (unlikely(err != 1)) break; } return err; } 按安全路由链表的安全路由处理数据, 该链表反映了多个SA对数据包进行处理 链表是在__xfrm4_bundle_create函数中建立的. static inline int xfrm4_output_one(struct sk_buff *skb) { struct dst_entry *dst = skb->dst; //安全路由 struct xfrm_state *x = dst->xfrm;//相关SA struct iphdr *iph; int err; //如果是通道模式, 检查skb数据长度, 并进行相关处理, //通道模式下封装后的数据包长度可能会超过1500字节的 if (x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) { err = xfrm4_tunnel_check_size(skb); if (err) goto error_nolock; } err = xfrm_output(skb); //具体封装 if (err) goto error_nolock; iph = ip_hdr(skb); iph->tot_len = htons(skb->len); //最新的长度 ip_send_check(iph); //重新计算效验和 //skb中设置IPSKB_XFRM_TRANSFORMED标志 //有该标志的数据包NAT操作后将不进行一些特殊检查 IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED; err = 0; out_exit: return err; error_nolock: kfree_skb(skb); goto out_exit; } int xfrm_output(struct sk_buff *skb) { struct dst_entry *dst = skb->dst; struct xfrm_state *x = dst->xfrm; int err; //skb包校验和计算 if (skb->ip_summed == CHECKSUM_PARTIAL) { err = skb_checksum_help(skb); if (err) goto error_nolock; } do { spin_lock_bh(&x->lock); err = xfrm_state_check(x, skb);//SA合法性检查 if (err) goto error; if (x->type->flags & XFRM_TYPE_REPLAY_PROT) { XFRM_SKB_CB(skb)->seq = ++x->replay.oseq; if (xfrm_aevent_is_on()) xfrm_replay_notify(x, XFRM_REPLAY_UPDATE); } err = x->outer_mode->output(x, skb);//调用模式输出函数, 如通道封装时外部IP头协议为IPIP,参考下面模式函数实现 if (err) goto error; //更新SA中的当前生命期结构中的包和字节计数 x->curlft.bytes += skb->len; x->curlft.packets++; spin_unlock_bh(&x->lock); //调用协议输出, 如对应ESP协议来说是esp4_output, 此时外部IP头协议会改为ESP,参考下面具体协议实现 err = x->type->output(x, skb); if (err) goto error_nolock; //转移到下一个子路由 if (!(skb->dst = dst_pop(dst))) { err = -EHOSTUNREACH; goto error_nolock; } //dst和x参数更新为子路由中的安全路由和SA dst = skb->dst; x = dst->xfrm; } while (x && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL));//循环条件是SA非空, 而且SA模式不是通道模式 err = 0; error_nolock: return err; error: spin_unlock_bh(&x->lock); goto error_nolock; } [/数据发送] [具体协议实现] 与IPSEC相关的安全协议是AH(51)和ESP(50), IPSEC使用这两个协议对普通数据包进行封装, AH只认证不加密, ESP既加密又认证, 当ESP和AH同时使用时, 一般都是先进行ESP封装, 再进行AH封装, 因为AH是对整个IP包进行验证的, 而ESP只验证负载部分. 具体的协议结构定义如下, 通常只定义初始化,析构,输入和输出四个成员函数. static struct xfrm_type ah_type = // net/ipv4/ah4.c { .description = "AH4", .owner = THIS_MODULE, .proto = IPPROTO_AH, .flags = XFRM_TYPE_REPLAY_PROT, .init_state = ah_init_state, //状态初始化 .destructor = ah_destroy,//协议释放 .input = ah_input, //协议输入 .output = ah_output //协议输出 }; 状态初始化 ah_data数据结构 struct ah_data //include/net/ah.h { u8 *work_icv; //工作初始化向量 int icv_full_len; //初始化向量完整长度 int icv_trunc_len; //初始化向量截断长度 struct crypto_hash *tfm; //HASH算法 }; 该函数被xfrm状态(SA)初始化函数xfrm_init_state调用,用来生成SA中所有的AH数据处理结构相关信息 static int ah_init_state(struct xfrm_state *x) { struct ah_data *ahp = NULL; struct xfrm_algo_desc *aalg_desc; struct crypto_hash *tfm; if (!x->aalg) //对AH协议的SA, 认证算法是必须的, 否则就没法进行AH认证了 goto error; if (x->encap)//如果要进行UDP封装(进行NAT穿越), 错误, 因为AH是不支持NAT的 goto error; ahp = kzalloc(sizeof(*ahp), GFP_KERNEL);//分配ah_data数据结构空间 if (ahp == NULL) return -ENOMEM; //分配认证算法HASH结构指针并赋值给AH数据结构 //算法是固定相同的, 但在每个应用使用算法时的上下文是不同的, 该结构就是描述具体应用时的相关处理的上下文数据的 tfm = crypto_alloc_hash(x->aalg->alg_name, 0, CRYPTO_ALG_ASYNC); if (IS_ERR(tfm)) goto error; ahp->tfm = tfm; //设置认证算法密钥 if (crypto_hash_setkey(tfm, x->aalg->alg_key, (x->aalg->alg_key_len + 7) / 8)) goto error; /* * Lookup the algorithm description maintained by xfrm_algo, * verify crypto transform properties, and store information * we need for AH processing. This lookup cannot fail here * after a successful crypto_alloc_hash(). */ aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);//查找算法描述结构 BUG_ON(!aalg_desc); if (aalg_desc->uinfo.auth.icv_fullbits / 8 != crypto_hash_digestsize(tfm)) { printk(KERN_INFO "AH: %s digestsize %u != %hu\n", x->aalg->alg_name, crypto_hash_digestsize(tfm), aalg_desc->uinfo.auth.icv_fullbits/8); goto error; } //AH数据结构的初始化向量的总长和截断长度的赋值 ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8; ahp->icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8; BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN); //分配初始化向量空间, 没对其赋值, 其初始值就是随机值, 这也是初始化向量所需要的 ahp->work_icv = kmalloc(ahp->icv_full_len, GFP_KERNEL); if (!ahp->work_icv) goto error; //AH类型SA中AH头长度: ip_auth_hdr结构和初始化向量长度, 按8字节对齐 //反映在AH封装操作时要将数据包增加的长度 x->props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp->icv_trunc_len); if (x->props.mode == XFRM_MODE_TUNNEL)//如果是通道模式, 增加IP头长度 x->props.header_len += sizeof(struct iphdr); x->data = ahp;//SA数据指向AH数据结构 return 0; error: if (ahp) { kfree(ahp->work_icv); crypto_free_hash(ahp->tfm); kfree(ahp); } return -EINVAL; } 接收数据处理, 在xfrm4_rcv_encap()函数中调用,进行AH认证, 剥离AH头 static int ah_input(struct xfrm_state *x, struct sk_buff *skb) { int ah_hlen; int ihl; int nexthdr; int err = -EINVAL; struct iphdr *iph; struct ip_auth_hdr *ah; struct ah_data *ahp; char work_buf[60]; //IP头备份空间 if (!pskb_may_pull(skb, sizeof(*ah)))//skb数据包要准备留出AH头空间 goto out; ah = (struct ip_auth_hdr *)skb->data; //IP上层数据为AH数据 //SA相关的AH处理数据 ahp = x->data; nexthdr = ah->nexthdr; ah_hlen = (ah->hdrlen + 2) << 2; //AH头部长度合法性检查 if (ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_full_len) && ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len)) goto out; //skb数据包要准备留出实际AH头空间 if (!pskb_may_pull(skb, ah_hlen)) goto out; /* We are going to _remove_ AH header to keep sockets happy, so... Later this can change. */ if (skb_cloned(skb) && pskb_expand_head(skb, 0, 0, GFP_ATOMIC))//对于clone的包要复制成独立包 goto out; skb->ip_summed = CHECKSUM_NONE; ah = (struct ip_auth_hdr *)skb->data;//可能包已经进行了复制, 所以对ah重新赋值 iph = ip_hdr(skb); ihl = skb->data - skb_network_header(skb); //IP头长度 memcpy(work_buf, iph, ihl); //备份外部IP头数据 //将IP头中的一些参数清零, 这些参数不进行认证 iph->ttl = 0; iph->tos = 0; iph->frag_off = 0; iph->check = 0; if (ihl > sizeof(*iph)) {//IP头长度超过20字节时,处理IP选项参数 __be32 dummy; if (ip_clear_mutable_options(iph, &dummy)) goto out; } { u8 auth_data[MAX_AH_AUTH_LEN];//认证数据缓冲区 memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);//拷贝数据包中的认证数据到缓冲区 skb_push(skb, ihl);//包括IP头部分数据 err = ah_mac_digest(ahp, skb, ah->auth_data);//计算认证值是否匹配, 非0表示出错 if (err) goto out; err = -EINVAL; if (memcmp(ahp->work_icv, auth_data, ahp->icv_trunc_len)) {//复制一定长度的认证数据作为初始化向量 x->stats.integrity_failed++; goto out; } } skb->network_header += ah_hlen; //更新网络头字段 memcpy(skb_network_header(skb), work_buf, ihl);//将原来IP头数据拷贝到原来AH头后面作为新IP头 skb->transport_header = skb->network_header; __skb_pull(skb, ah_hlen + ihl);//skb包缩减原来的IP头和AH头, 以新IP头作为数据开始 return nexthdr; //返回AH内部包裹的协议 out: return err; } 发送数据处理, 在xfrm4_output_one()中调用,计算AH认证值, 添加AH头 static int ah_output(struct xfrm_state *x, struct sk_buff *skb) { int err; struct iphdr *iph, *top_iph; struct ip_auth_hdr *ah; struct ah_data *ahp; union {//临时IP头缓冲区, 最大IP头60字节 struct iphdr iph; char buf[60]; } tmp_iph; skb_push(skb, -skb_network_offset(skb)); //data指针移动到网络头位置 top_iph = ip_hdr(skb);//当前的IP头将作为最外部IP头 iph = &tmp_iph.iph; //临时IP头,用于临时保存IP头内部分字段数据 //将当前IP头中不进行认证的字段数据复制到临时IP头 iph->tos = top_iph->tos; iph->ttl = top_iph->ttl; iph->frag_off = top_iph->frag_off; if (top_iph->ihl != 5) {//如果有IP选项, 处理IP选项 iph->daddr = top_iph->daddr; memcpy(iph+1, top_iph+1, top_iph->ihl*4 - sizeof(struct iphdr)); err = ip_clear_mutable_options(top_iph, &top_iph->daddr); if (err) goto error; } //AH头定位在外部IP头后面, skb缓冲中已经预留出AH头的数据部分了, //这是通过mode->output函数预留的, 通常调用type->output前要调用mode->oputput,参考上面函数xfrm_output ah = ip_auth_hdr(skb); ah->nexthdr = *skb_mac_header(skb);//AH中的下一个头用mac中指定的协议 *skb_mac_header(skb) = IPPROTO_AH; //mac中协议字段改为AH //将外部IP头的不进行认证计算的部分字段清零 top_iph->tos = 0; top_iph->tot_len = htons(skb->len); top_iph->frag_off = 0; top_iph->ttl = 0; top_iph->check = 0; ahp = x->data;//AH数据处理结构 ah->hdrlen = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2;//AH头长度对齐 //AH头参数赋值 ah->reserved = 0; ah->spi = x->id.spi;//SPI值 ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq);//序列号 spin_lock_bh(&x->lock); err = ah_mac_digest(ahp, skb, ah->auth_data);//对skb进行AH认证值的计算 memcpy(ah->auth_data, ahp->work_icv, ahp->icv_trunc_len);//赋值初始化向量值到认证数据部分 spin_unlock_bh(&x->lock); if (err) goto error; // 恢复原来IP头的的不认证部分的值 top_iph->tos = iph->tos; top_iph->ttl = iph->ttl; top_iph->frag_off = iph->frag_off; if (top_iph->ihl != 5) { //处理ip选项 top_iph->daddr = iph->daddr; memcpy(top_iph+1, iph+1, top_iph->ihl*4 - sizeof(struct iphdr)); } err = 0; error: return err; } static struct xfrm_type esp_type = //net/ipv4/esp4.c { .description = "ESP4", .owner = THIS_MODULE, .proto = IPPROTO_ESP, .flags = XFRM_TYPE_REPLAY_PROT, .init_state = esp_init_state, .destructor = esp_destroy, .get_mtu = esp4_get_mtu, //获取mtu .input = esp_input, .output = esp_output }; esp_data数据结构 struct esp_data { struct scatterlist sgbuf[ESP_NUM_FAST_SG]; /* Confidentiality */ struct {//加密使用的相关数据 int padlen; //填充长度 /* 0..255 */ /* ivlen is offset from enc_data, where encrypted data start. * It is logically different of crypto_tfm_alg_ivsize(tfm). * We assume that it is either zero (no ivec), or * >= crypto_tfm_alg_ivsize(tfm). */ int ivlen; //初始化向量长度 int ivinitted; //初始化向量是否初始化标志 u8 *ivec; //初始化向量 /* ivec buffer */ struct crypto_blkcipher *tfm; //加密算法 /* crypto handle */ } conf; /* Integrity. It is active when icv_full_len != 0 */ struct {//认证使用的相关数据 u8 *work_icv;//初始化向量 int icv_full_len;//初始化向量全长 int icv_trunc_len;//初始化向量截断长度 struct crypto_hash *tfm;//HASH算法 } auth; }; ESP的esp_data数据结构初始化 static int esp_init_state(struct xfrm_state *x) { struct esp_data *esp = NULL; struct crypto_blkcipher *tfm; u32 align; if (x->ealg == NULL)//ESP加密算法是必须的 goto error; esp = kzalloc(sizeof(*esp), GFP_KERNEL);//分配esp_data数据结构空间 if (esp == NULL) return -ENOMEM; //如果定义了认证算法, 初始化认证算法参数, 和AH类似 if (x->aalg) { struct xfrm_algo_desc *aalg_desc; struct crypto_hash *hash; //分配HASH算法的实现 hash = crypto_alloc_hash(x->aalg->alg_name, 0, CRYPTO_ALG_ASYNC); if (IS_ERR(hash)) goto error; esp->auth.tfm = hash; //设置HASH算法密钥 if (crypto_hash_setkey(hash, x->aalg->alg_key, (x->aalg->alg_key_len + 7) / 8)) goto error; //找到算法描述 aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0); BUG_ON(!aalg_desc); //检查算法初始化向量长度合法性 if (aalg_desc->uinfo.auth.icv_fullbits/8 != crypto_hash_digestsize(hash)) { NETDEBUG(KERN_INFO "ESP: %s digestsize %u != %hu\n", x->aalg->alg_name, crypto_hash_digestsize(hash), aalg_desc->uinfo.auth.icv_fullbits/8); goto error; } //初始化向量的全长和截断长度 esp->auth.icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8; esp->auth.icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8; esp->auth.work_icv = kmalloc(esp->auth.icv_full_len, GFP_KERNEL);//分配全长度的初始化向量空间 if (!esp->auth.work_icv) goto error; } //查找加密算法的具体实现结构 tfm = crypto_alloc_blkcipher(x->ealg->alg_name, 0, CRYPTO_ALG_ASYNC); if (IS_ERR(tfm)) goto error; esp->conf.tfm = tfm; esp->conf.ivlen = crypto_blkcipher_ivsize(tfm);//初始化向量大小 esp->conf.padlen = 0;//填充数据长度初始化为0 if (esp->conf.ivlen) {//初始化向量长度非0, 分配具体的初始化向量空间 esp->conf.ivec = kmalloc(esp->conf.ivlen, GFP_KERNEL); if (unlikely(esp->conf.ivec == NULL)) goto error; esp->conf.ivinitted = 0; } //设置加密算法密钥 if (crypto_blkcipher_setkey(tfm, x->ealg->alg_key, (x->ealg->alg_key_len + 7) / 8)) goto error; //定义SA中ESP头部长度: ESP头加初始化向量长度 //反映在ESP封装操作时要将数据包增加的长度 x->props.header_len = sizeof(struct ip_esp_hdr) + esp->conf.ivlen; if (x->props.mode == XFRM_MODE_TUNNEL)//如果是通道模式, 还需要增加IP头长度 x->props.header_len += sizeof(struct iphdr); else if (x->props.mode == XFRM_MODE_BEET)//如果是BEET模式, 还需要增加IP头长度 x->props.header_len += IPV4_BEET_PHMAXLEN; if (x->encap) {//如果要进行UDP封装 struct xfrm_encap_tmpl *encap = x->encap; switch (encap->encap_type) { default: goto error; case UDP_ENCAP_ESPINUDP://该类型封装增加UDP头长度 x->props.header_len += sizeof(struct udphdr); break; case UDP_ENCAP_ESPINUDP_NON_IKE://该类型封装增加UDP头长度外加加8字节 x->props.header_len += sizeof(struct udphdr) + 2 * sizeof(u32); break; } } x->data = esp;//将esp_data作为SA的data指针 align = ALIGN(crypto_blkcipher_blocksize(esp->conf.tfm), 4); //加密块长度, 按4字节对齐 if (esp->conf.padlen) align = max_t(u32, align, esp->conf.padlen); x->props.trailer_len = align + 1 + esp->auth.icv_trunc_len; //加密块长度 + 1 + 认证长度 return 0; ...... } 获取mtu static u32 esp4_get_mtu(struct xfrm_state *x, int mtu) { struct esp_data *esp = x->data; u32 blksize = ALIGN(crypto_blkcipher_blocksize(esp->conf.tfm), 4);//加密块长度, 按4字节对齐 u32 align = max_t(u32, blksize, esp->conf.padlen); u32 rem; mtu -= x->props.header_len + esp->auth.icv_trunc_len; rem = mtu & (align - 1); mtu &= ~(align - 1); ///mtu对齐 switch (x->props.mode) { case XFRM_MODE_TUNNEL: break; default: case XFRM_MODE_TRANSPORT://传输模式下 /* The worst case */ mtu -= blksize - 4; mtu += min_t(u32, blksize - 4, rem); break; case XFRM_MODE_BEET: /* The worst case. */ mtu += min_t(u32, IPV4_BEET_PHMAXLEN, rem); break; } return mtu - 2; } 接收数据处理, 在xfrm4_rcv_encap()函数中调用 进行ESP认证解密, 剥离ESP头, 解密成普通数据包, 数据包长度减少 static int esp_input(struct xfrm_state *x, struct sk_buff *skb) { struct iphdr *iph; struct ip_esp_hdr *esph; struct esp_data *esp = x->data; struct crypto_blkcipher *tfm = esp->conf.tfm; struct blkcipher_desc desc = { .tfm = tfm }; struct sk_buff *trailer; int blksize = ALIGN(crypto_blkcipher_blocksize(tfm), 4); int alen = esp->auth.icv_trunc_len;//认证初始化向量截断长度 //需要加密的数据长度: 总长减ESP头, 加密初始化向量长度, 认证初始化向量长度 int elen = skb->len - sizeof(*esph) - esp->conf.ivlen - alen; int nfrags; int ihl; u8 nexthdr[2]; struct scatterlist *sg; int padlen; int err; if (!pskb_may_pull(skb, sizeof(*esph)))//在skb头要有足够的ESP头空间 goto out; if (elen <= 0 || (elen & (blksize-1)))//检查需要加密的数据长度, 必须大于0而且按块大小对齐的 goto out; /* If integrity check is required, do this. */ if (esp->auth.icv_full_len) {//认证计算处理 u8 sum[alen]; //计算认证值, 认证值保存在esp_data结构中 err = esp_mac_digest(esp, skb, 0, skb->len - alen); if (err) goto out; //将skb中的认证初始化向量部分数据拷贝到缓冲区sum中 if (skb_copy_bits(skb, skb->len - alen, sum, alen)) BUG(); //比较sum中的向量值和认证算法结构中的向量值是否匹配, 数据包正常情况下应该是相同的 if (unlikely(memcmp(esp->auth.work_icv, sum, alen))) { x->stats.integrity_failed++; goto out; } } //使数据包可写,返回使用了多少个内存页 if ((nfrags = skb_cow_data(skb, 0, &trailer)) < 0) goto out; skb->ip_summed = CHECKSUM_NONE; //定位在数据包中的ESP头位置, 为当前的data位置 esph = (struct ip_esp_hdr *)skb->data; /* Get ivec. This can be wrong, check against another impls. */ if (esp->conf.ivlen)//设置加密算法的初始化向量 crypto_blkcipher_set_iv(tfm, esph->enc_data, esp->conf.ivlen);//拷贝esph头后的数据到加密算法结构中 sg = &esp->sgbuf[0]; if (unlikely(nfrags > ESP_NUM_FAST_SG)) {//内存页超过 4 sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC); if (!sg) goto out; } sg_init_table(sg, nfrags); skb_to_sgvec(skb, sg, sizeof(*esph) + esp->conf.ivlen, elen); //解密操作, 返回非0表示失败 err = crypto_blkcipher_decrypt(&desc, sg, sg, elen); if (unlikely(sg != &esp->sgbuf[0])) kfree(sg); if (unlikely(err))//解密失败返回 return err; if (skb_copy_bits(skb, skb->len-alen-2, nexthdr, 2))//拷贝两字节数据 BUG(); padlen = nexthdr[0]; //填充数据长度 if (padlen+2 >= elen) goto out; /* RFC4303: Drop dummy packets without any error */ if (nexthdr[1] == IPPROTO_NONE) goto out; iph = ip_hdr(skb);//IP头 ihl = iph->ihl * 4; if (x->encap) {//如果是NAT穿越情况, 进行一些处理 struct xfrm_encap_tmpl *encap = x->encap;//xfrm封装模板 struct udphdr *uh = (void *)(skb_network_header(skb) + ihl);//定位UDP数据头位置, 在IP头之后 //如果IP头源地址和SA提议中的源地址不同或源端口不同 if (iph->saddr != x->props.saddr.a4 || uh->source != encap->encap_sport) { xfrm_address_t ipaddr; ipaddr.a4 = iph->saddr;//保存当前IP头源地址 km_new_mapping(x, &ipaddr, uh->source);//进行NAT通知回调处理 } if (x->props.mode == XFRM_MODE_TRANSPORT)//如果是传输模式, 设置不需要计算校验和 skb->ip_summed = CHECKSUM_UNNECESSARY; } //缩减skb数据包长度,调整len和tail字段 pskb_trim(skb, skb->len - alen - padlen - 2); //调整data到esph头和数据后面 __skb_pull(skb, sizeof(*esph) + esp->conf.ivlen); skb_set_transport_header(skb, -ihl); //定位传输层头,data前面20字节处 return nexthdr[1]; //返回记录的IP头中协议 out: return -EINVAL; } 发送数据处理, 在xfrm4_output_one()中调用 添加ESP头, 对数据包进行加密和认证处理, 数据包长度扩大 在NAT穿越情况下会封装为UDP数据 static int esp_output(struct xfrm_state *x, struct sk_buff *skb) { int err; struct ip_esp_hdr *esph; struct crypto_blkcipher *tfm; struct blkcipher_desc desc; struct esp_data *esp; struct sk_buff *trailer; u8 *tail; int blksize; int clen; int alen; int nfrags; /* skb is pure payload to encrypt */ err = -ENOMEM; /* Round to block size */ clen = skb->len;//加密块的初始值 esp = x->data;//获取SA的esp_data数据结构 alen = esp->auth.icv_trunc_len;//认证初始化向量截断长度 tfm = esp->conf.tfm;//加密算法 //给块加密算法描述结构赋值 desc.tfm = tfm; desc.flags = 0; //每个加密块大小 blksize = ALIGN(crypto_blkcipher_blocksize(tfm), 4); clen = ALIGN(clen + 2, blksize);//对齐要加密的数据总长 if (esp->conf.padlen)//如果要考虑填充, 继续对齐 clen = ALIGN(clen, esp->conf.padlen); //使数据包可写 if ((nfrags = skb_cow_data(skb, clen-skb->len+alen, &trailer)) < 0) goto error; /* Fill padding... */ tail = skb_tail_pointer(trailer);//长度对齐后填充多余长度部分内容 do { int i; for (i = 0; i < clen - skb->len - 2; i++) tail[i] = i + 1; } while (0); //最后两字节表示填充数据的长度和ip中原来的协议 tail[clen - skb->len - 2] = (clen - skb->len) - 2; //数据长度 pskb_put(skb, trailer, clen - skb->len);//调整skb->tail位置 //将IP头部分扩展回来,data调整到网络头位置 skb_push(skb, -skb_network_offset(skb)); esph = ip_esp_hdr(skb);//esp头跟在IP头后 *(skb_tail_pointer(trailer) - 1) = *skb_mac_header(skb); //记录ip中原来的协议 *skb_mac_header(skb) = IPPROTO_ESP; //修改ip头中协议 spin_lock_bh(&x->lock); /* this is non-NULL only with UDP Encapsulation */ if (x->encap) {//NAT穿越情况下要将数据封装为UDP包 struct xfrm_encap_tmpl *encap = x->encap; struct udphdr *uh; __be32 *udpdata32; uh = (struct udphdr *)esph;//IP头后改为UDP头 //填充UDP头参数, 源端口, 目的端口, UDP数据长度 uh->source = encap->encap_sport; uh->dest = encap->encap_dport; uh->len = htons(skb->len + alen - skb_transport_offset(skb)); uh->check = 0;//校验和为0, 表示不需要计算校验和, ESP本身就进行认证了 switch (encap->encap_type) { default: case UDP_ENCAP_ESPINUDP://在该模式下ESP头跟在UDP头后面 esph = (struct ip_esp_hdr *)(uh + 1); break; case UDP_ENCAP_ESPINUDP_NON_IKE://在该模式下ESP头跟在UDP头后面2字节处 udpdata32 = (__be32 *)(uh + 1); udpdata32[0] = udpdata32[1] = 0; esph = (struct ip_esp_hdr *)(udpdata32 + 2); break; } *skb_mac_header(skb) = IPPROTO_UDP;//外部IP头协议是UDP } //填充ESP头中的SPI和序列号 esph->spi = x->id.spi; esph->seq_no = htonl(XFRM_SKB_CB(skb)->seq); //如果加密初始化向量长度非零, 设置加密算法中的初始化向量 if (esp->conf.ivlen) { if (unlikely(!esp->conf.ivinitted)) { get_random_bytes(esp->conf.ivec, esp->conf.ivlen); esp->conf.ivinitted = 1; } crypto_blkcipher_set_iv(tfm, esp->conf.ivec, esp->conf.ivlen); } //加密操作 do { struct scatterlist *sg = &esp->sgbuf[0]; if (unlikely(nfrags > ESP_NUM_FAST_SG)) { sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC); if (!sg) goto unlock; } sg_init_table(sg, nfrags); skb_to_sgvec(skb, sg, esph->enc_data + esp->conf.ivlen - skb->data, clen);//对数据加密 err = crypto_blkcipher_encrypt(&desc, sg, sg, clen); if (unlikely(sg != &esp->sgbuf[0])) kfree(sg); } while (0); if (esp->conf.ivlen) {//将加密算法初始化向量拷贝到数据包 memcpy(esph->enc_data, esp->conf.ivec, esp->conf.ivlen); crypto_blkcipher_get_iv(tfm, esp->conf.ivec, esp->conf.ivlen); } if (esp->auth.icv_full_len) {//认证计算, 计算出HASH值并拷贝到数据包中 err = esp_mac_digest(esp, skb, (u8 *)esph - skb->data, sizeof(*esph) + esp->conf.ivlen + clen); memcpy(pskb_put(skb, trailer, alen), esp->auth.work_icv, alen); } unlock: spin_unlock_bh(&x->lock); error: return err; } static struct xfrm_type ipcomp_type = { //net/ipv4/ipcomp.c ip压缩协议 .description = "IPCOMP4", .owner = THIS_MODULE, .proto = IPPROTO_COMP, .init_state = ipcomp_init_state, .destructor = ipcomp_destroy, .input = ipcomp_input, .output = ipcomp_output }; static struct xfrm_type ipip_type = { ///net/ipv4/xfrm4_tunnel.c ipip协议 .description = "IPIP", .owner = THIS_MODULE, .proto = IPPROTO_IPIP, .init_state = ipip_init_state, .destructor = ipip_destroy, .input = ipip_xfrm_rcv, .output = ipip_output }; static int ipip_output(struct xfrm_state *x, struct sk_buff *skb) { skb_push(skb, -skb_network_offset(skb)); return 0; } static int ipip_xfrm_rcv(struct xfrm_state *x, struct sk_buff *skb) { return ip_hdr(skb)->protocol; } [/具体协议实现] [模式函数实现] static struct xfrm_mode xfrm4_tunnel_mode = { //通道模式结构定义 net/ipv4/xfrm4_mode_tunnel.c .input = xfrm4_tunnel_input, .output = xfrm4_tunnel_output, .owner = THIS_MODULE, .encap = XFRM_MODE_TUNNEL, .flags = XFRM_MODE_FLAG_TUNNEL, }; 通道模式下的接收函数, 解封装 static int xfrm4_tunnel_input(struct xfrm_state *x, struct sk_buff *skb) { struct iphdr *iph = ip_hdr(skb); const unsigned char *old_mac; int err = -EINVAL; switch (iph->protocol){//IP协议为IPPROTO_IPIP(4) case IPPROTO_IPIP: break; #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) case IPPROTO_IPV6: break; #endif default: goto out; } //需要在skb头留出IP头的长度(20字节) if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto out; //如果是clone包,重新拷贝一个 if (skb_cloned(skb) && (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC))) goto out; iph = ip_hdr(skb); if (iph->protocol == IPPROTO_IPIP) { if (x->props.flags & XFRM_STATE_DECAP_DSCP)//复制dscp字段 ipv4_copy_dscp(iph, ipip_hdr(skb)); if (!(x->props.flags & XFRM_STATE_NOECN))//非XFRM_STATE_NOECN时进行ECN解封装 ipip_ecn_decapsulate(skb); } #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) else { if (!(x->props.flags & XFRM_STATE_NOECN)) ipip6_ecn_decapsulate(iph, skb); skb->protocol = htons(ETH_P_IPV6); } #endif //将硬件地址挪到数据包缓冲区前 old_mac = skb_mac_header(skb);//取出mac头位置 //现在skb->data指向内部ip头,所以下面在内部ip头前面把原来的mac头复制过去 skb_set_mac_header(skb, -skb->mac_len); memmove(skb_mac_header(skb), old_mac, skb->mac_len); skb_reset_network_header(skb);//重置网络头,网络头为内部ip头,现在外部ip头已经被剥离(被mac头复盖了) err = 0; out: return err; } 通道模式下的数据发出函数, 进行封装 static int xfrm4_tunnel_output(struct xfrm_state *x, struct sk_buff *skb) { struct dst_entry *dst = skb->dst; struct xfrm_dst *xdst = (struct xfrm_dst*)dst; struct iphdr *iph, *top_iph; int flags; iph = ip_hdr(skb); //数据头部增加外部IP头的长度,重置网络头位置 skb_set_network_header(skb, -x->props.header_len); skb->mac_header = skb->network_header + offsetof(struct iphdr, protocol);//mac头指向ip协议字段 skb->transport_header = skb->network_header + sizeof(*iph);//传输头指向ip头后面 top_iph = ip_hdr(skb); //获取外部ip头 //填写外部IP头参数 top_iph->ihl = 5; top_iph->version = 4; flags = x->props.flags; /* DS disclosed */ if (xdst->route->ops->family == AF_INET) { top_iph->protocol = IPPROTO_IPIP;//外部IP头内的协议号为IPIP(4) top_iph->tos = INET_ECN_encapsulate(iph->tos, iph->tos);//重新计算TOS top_iph->frag_off = (flags & XFRM_STATE_NOPMTUDISC) ? 0 : (iph->frag_off & htons(IP_DF));//处理分片包情况 } #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) else { struct ipv6hdr *ipv6h = (struct ipv6hdr*)iph; top_iph->protocol = IPPROTO_IPV6; top_iph->tos = INET_ECN_encapsulate(iph->tos, ipv6_get_dsfield(ipv6h)); top_iph->frag_off = 0; } #endif if (flags & XFRM_STATE_NOECN) IP_ECN_clear(top_iph); if (!top_iph->frag_off) __ip_select_ident(top_iph, dst->child, 0); top_iph->ttl = dst_metric(dst->child, RTAX_HOPLIMIT); top_iph->saddr = x->props.saddr.a4;//外部源地址用proposal中的源地址 top_iph->daddr = x->id.daddr.a4;//外部目的地址是SA中的目的地址 skb->protocol = htons(ETH_P_IP); memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));//IP选项部分设置为0 return 0; } static struct xfrm_mode xfrm4_transport_mode = { //传输模式结构定义 net/ipv4/xfrm4_mode_transport.c .input = xfrm4_transport_input, .output = xfrm4_transport_output, .owner = THIS_MODULE, .encap = XFRM_MODE_TRANSPORT, }; 传输模式下的数据输入函数 static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb) { int ihl = skb->data - skb_transport_header(skb); //获取ip头长度 if (skb->transport_header != skb->network_header) { //移动网络头到传输头位置,现在传输层头指向的是data前ip头长度字节位置 memmove(skb_transport_header(skb), skb_network_header(skb), ihl); skb->network_header = skb->transport_header;//设置网络头 } ip_hdr(skb)->tot_len = htons(skb->len + ihl); //重新对数据包长度赋值 skb_reset_transport_header(skb); //重置传输头位置,为data位置 return 0; } 传输模式下的数据发出函数 static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb) { struct iphdr *iph = ip_hdr(skb); int ihl = iph->ihl * 4; skb_set_network_header(skb, -x->props.header_len); //重新设置网络头位置,向前移出一个props.header_len长的空位 skb->mac_header = skb->network_header + offsetof(struct iphdr, protocol); //mac头值设置为ip头中协议字段的位置 skb->transport_header = skb->network_header + ihl; //传输头在网络头 + ip头长度后面的位置 __skb_pull(skb, ihl);//将skb->data移动到ip头后面 memmove(skb_network_header(skb), iph, ihl); //拷贝原始ip头到新网络头位置 return 0; } static struct xfrm_mode xfrm4_beet_mode = { //beet模式 net/ipv4/xfrm4_mode_beet.c .input = xfrm4_beet_input, .output = xfrm4_beet_output, .owner = THIS_MODULE, .encap = XFRM_MODE_BEET, .flags = XFRM_MODE_FLAG_TUNNEL, }; [/模式函数实现] [IPV4下的xfrm策略] IPV4的策略协议相关处理结构定义如下. static struct xfrm_policy_afinfo xfrm4_policy_afinfo = { //net/ipv4/xfrm4_policy.c .family = AF_INET, .dst_ops = &xfrm4_dst_ops, .dst_lookup = xfrm4_dst_lookup, .get_saddr = xfrm4_get_saddr, .find_bundle = __xfrm4_find_bundle, .bundle_create = __xfrm4_bundle_create, .decode_session = _decode_session4, }; IPV4的路由查找, 就是普通是路由查找方法,返回0成功 static int xfrm4_dst_lookup(struct xfrm_dst **dst, struct flowi *fl) { return __ip_route_output_key((struct rtable**)dst, fl); } 查找地址, 这个函数是在通道模式下, 源地址没明确指定时调用的,查找获取外部头中的源地址 static int xfrm4_get_saddr(xfrm_address_t *saddr, xfrm_address_t *daddr) { struct rtable *rt; //通道的流结构定义,用于查找路由 struct flowi fl_tunnel = { .nl_u = { .ip4_u = { .daddr = daddr->a4, }, }, }; //根据目的地址找路由 if (!xfrm4_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel)) { saddr->a4 = rt->rt_src;//将找到的路由项中的源地址作为通道模式下的外部源地址 dst_release(&rt->u.dst); return 0; } return -EHOSTUNREACH; } 查找策略中的安全路由, 查找条件是流结构的定义的参数 static struct dst_entry * __xfrm4_find_bundle(struct flowi *fl, struct xfrm_policy *policy) { struct dst_entry *dst; read_lock_bh(&policy->lock); //遍历策略的安全路由链表 for (dst = policy->bundles; dst; dst = dst->next) { struct xfrm_dst *xdst = (struct xfrm_dst*)dst; //比较网卡位置, 目的地址, 源地址, TOS值是否匹配 //同时检查该安全路由是否可用 if (xdst->u.rt.fl.oif == fl->oif && /*XXX*/ xdst->u.rt.fl.fl4_dst == fl->fl4_dst && xdst->u.rt.fl.fl4_src == fl->fl4_src && xdst->u.rt.fl.fl4_tos == fl->fl4_tos && xfrm_bundle_ok(policy, xdst, fl, AF_INET, 0)) { dst_clone(dst); break; } } read_unlock_bh(&policy->lock); return dst; } 创建安全路由 static int __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx, struct flowi *fl, struct dst_entry **dst_p) { struct dst_entry *dst, *dst_prev; struct rtable *rt0 = (struct rtable*)(*dst_p); struct rtable *rt = rt0; struct flowi fl_tunnel = { .nl_u = { .ip4_u = { .saddr = fl->fl4_src, .daddr = fl->fl4_dst, .tos = fl->fl4_tos } } }; int i; int err; int header_len = 0; int trailer_len = 0; dst = dst_prev = NULL; dst_hold(&rt->u.dst); //循环次数为策略中SA的数量, 每个SA对应一个安全路由, 一个安全路由对应对数据包的一个操作: 如压缩, ESP封装, AH封装等 for (i = 0; i < nx; i++) { //分配安全路由, 安全路由的操作结构是xfrm4_dst_ops //因为定义了很多不同类型的路由, 每种路由都有各自的操作结构, 这样在上层可用统一的接口进行路由处理 struct dst_entry *dst1 = dst_alloc(&xfrm4_dst_ops); struct xfrm_dst *xdst; if (unlikely(dst1 == NULL)) { err = -ENOBUFS; dst_release(&rt->u.dst); goto error; } if (!dst)//第一次循环 dst = dst1; else {//将新分配的安全路由作为前一个路由的child dst_prev->child = dst1; dst1->flags |= DST_NOHASH; dst_clone(dst1); } xdst = (struct xfrm_dst *)dst1; //安全路由中保留相应的普通路由 xdst->route = &rt->u.dst; xdst->genid = xfrm[i]->genid; //新节点的next是上一个节点 dst1->next = dst_prev; dst_prev = dst1;//现在prev指向新节点 header_len += xfrm[i]->props.header_len; trailer_len += xfrm[i]->props.trailer_len; //如果是通道模式, 需要重新包裹外部IP头, 需要重新寻找外部IP头的路由 if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) { unsigned short encap_family = xfrm[i]->props.family; switch (encap_family) { case AF_INET: fl_tunnel.fl4_dst = xfrm[i]->id.daddr.a4; fl_tunnel.fl4_src = xfrm[i]->props.saddr.a4; break; #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) case AF_INET6: ipv6_addr_copy(&fl_tunnel.fl6_dst, (struct in6_addr*)&xfrm[i]->id.daddr.a6); ipv6_addr_copy(&fl_tunnel.fl6_src, (struct in6_addr*)&xfrm[i]->props.saddr.a6); break; #endif default: BUG_ON(1); } err = xfrm_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel, encap_family); if (err) goto error; } else dst_hold(&rt->u.dst); } //将最新节点的child指向最后的普通路由 dst_prev->child = &rt->u.dst; //最开始分配的安全路由的path指向最后的普通路由 dst->path = &rt->u.dst; //将最开始的全路由点作为要返回的路由节点链表头 *dst_p = dst; dst = dst_prev;//dst现在是最新分配的节点 dst_prev = *dst_p;//prev现在指向最开始分配的全节点 i = 0; 为更好理解上面的操作, 用图来表示. 以上循环形成了下图水平方向的一个链表, 链表中的最左边的路由项节点dst为最老的安全路由项, 新分配的安全路由项通过child链接成链表, child通过next指向老节点, 最后一项是数据包封装完后的最后普通路由项. 垂直方向的链表是在xfrm_lookup()中形成的, 是多个策略同时起作用的情况, 一般情况下就只有一个策略, 本文中可不考虑多策略的情况. rt0.u.dst rt.u.dst rt.u.dst ^ ^ ^ route | route | route | | child | child | bundle +-----+ -----> +-----+ -----> +-----+ child policy ------->| dst | <----- | dst | <----- ... | dst | -----> rt.u.dst (普通路由) +-----+ next +-----+ next +-----+ | |next | V child child +-----+ -----> +-----+ -----> +-----+ child | dst | <----- | dst | <----- ...| dst | -----> rt.u.dst +-----+ next +-----+ next +-----+ | |next | V .... //对新生成的每个安全路由项填充结构参数 for (; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) { struct xfrm_dst *x = (struct xfrm_dst*)dst_prev; x->u.rt.fl = *fl; dst_prev->xfrm = xfrm[i++]; dst_prev->dev = rt->u.dst.dev; if (rt->u.dst.dev) dev_hold(rt->u.dst.dev); dst_prev->obsolete = -1; dst_prev->flags |= DST_HOST; dst_prev->lastuse = jiffies; dst_prev->header_len = header_len; dst_prev->nfheader_len = 0; dst_prev->trailer_len = trailer_len; memcpy(&dst_prev->metrics, &x->route->metrics, sizeof(dst_prev->metrics)); /* Copy neighbout for reachability confirmation */ dst_prev->neighbour = neigh_clone(rt->u.dst.neighbour); dst_prev->input = rt->u.dst.input; //注意安全路由的输出函数是xfrm4_output,参考上面数据发送 dst_prev->output = dst_prev->xfrm->outer_mode->afinfo->output; if (rt0->peer) atomic_inc(&rt0->peer->refcnt); x->u.rt.peer = rt0->peer; /* Sheit... I remember I did this right. Apparently, * it was magically lost, so this code needs audit */ x->u.rt.rt_flags = rt0->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL); x->u.rt.rt_type = rt0->rt_type; x->u.rt.rt_src = rt0->rt_src; x->u.rt.rt_dst = rt0->rt_dst; x->u.rt.rt_gateway = rt0->rt_gateway; x->u.rt.rt_spec_dst = rt0->rt_spec_dst; x->u.rt.idev = rt0->idev; in_dev_hold(rt0->idev); header_len -= x->u.dst.xfrm->props.header_len; trailer_len -= x->u.dst.xfrm->props.trailer_len; } //初始化路由项的MTU值 xfrm_init_pmtu(dst); return 0; error: if (dst) dst_free(dst); return err; } 解码skb数据, 填充流结构 static void _decode_session4(struct sk_buff *skb, struct flowi *fl) { struct iphdr *iph = ip_hdr(skb); //xprth指向IP头后的上层协议头起始 u8 *xprth = skb_network_header(skb) + iph->ihl * 4; memset(fl, 0, sizeof(struct flowi)); if (!(iph->frag_off & htons(IP_MF | IP_OFFSET))) {//数据包必须不是分片包 switch (iph->protocol) { //对UDP(17), TCP(6), SCTP(132)和DCCP(33)协议, 要提取源端口和目的端口 //头4字节是源端口和目的端口 case IPPROTO_UDP: case IPPROTO_UDPLITE: case IPPROTO_TCP: case IPPROTO_SCTP: case IPPROTO_DCCP: //要让skb预留出IP头长度加4字节的长度, 在IP层data应该指向最外面的IP头 if (pskb_may_pull(skb, xprth + 4 - skb->data)) { __be16 *ports = (__be16 *)xprth; fl->fl_ip_sport = ports[0]; fl->fl_ip_dport = ports[1]; } break; case IPPROTO_ICMP://对ICMP(1)协议要提取ICMP包的类型和编码, 2字节 if (pskb_may_pull(skb, xprth + 2 - skb->data)) { u8 *icmp = xprth; fl->fl_icmp_type = icmp[0]; fl->fl_icmp_code = icmp[1]; } break; case IPPROTO_ESP://对于ESP(50)协议要提取其中的SPI值, 4字节 if (pskb_may_pull(skb, xprth + 4 - skb->data)) { __be32 *ehdr = (__be32 *)xprth; fl->fl_ipsec_spi = ehdr[0]; } break; case IPPROTO_AH://对于AH(51)协议要提取其中的SPI值, 4字节 if (pskb_may_pull(skb, xprth + 8 - skb->data)) { __be32 *ah_hdr = (__be32*)xprth; fl->fl_ipsec_spi = ah_hdr[1]; } break; case IPPROTO_COMP://对于COMP(108)协议要提取其中CPI值作为SPI值, 2字节 if (pskb_may_pull(skb, xprth + 4 - skb->data)) { __be16 *ipcomp_hdr = (__be16 *)xprth; fl->fl_ipsec_spi = htonl(ntohs(ipcomp_hdr[1])); } break; default: fl->fl_ipsec_spi = 0; break; } } //填充协议,源地址,目的地址, TOS参数 fl->proto = iph->protocol; fl->fl4_dst = iph->daddr; fl->fl4_src = iph->saddr; fl->fl4_tos = iph->tos; } [/IPV4下的xfrm策略] [网卡通知回调实现] xfrm的网卡通知回调结构 static struct notifier_block xfrm_dev_notifier = { xfrm_dev_event, NULL, 0 }; static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *dev = ptr; if (dev->nd_net != &init_net) return NOTIFY_DONE; switch (event) { case NETDEV_DOWN: //如果网卡down掉的话, 清除相关的所有的xfrm路由项 xfrm_flush_bundles(); 实现为xfrm_prune_bundles(stale_bundle); } return NOTIFY_DONE; } [/网卡通知回调实现]

 

posted on 2013-08-30 10:05  SuperKing  阅读(6564)  评论(0编辑  收藏  举报

导航