kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Linux2.6下ESP包解析流程

http://www.360doc.com/content/11/0516/05/706976_117227003.shtml

以下Linux内核代码版本为2.6.19.2。

2. 流程分析

2.1 esp协议结构

esp协议结构定义,对于每个IPv4上层的协议,如TCP、UDP、ICMP、IGMP、ESP、AH等都需要定义这个 结构挂接到IPv4的协议链表中,当接收到IP数据包时,会根据包中定义的IP协议号找到该结构,然后 调用其成员handler函数进行处理。

1
2
3
4
5
6
/* net/ipv4/esp4.c */
static struct net_protocol esp4_protocol = {
	.handler = xfrm4_rcv,
	.err_handler = esp4_err,
	.no_policy = 1,
};

esp协议的handler函数是xfrm4_rcv()

2.2 xfrm4_rcv

1
2
3
4
5
/* net/ipv4/xfrm4_input.c */
int xfrm4_rcv(struct sk_buff *skb)
{
	return xfrm4_rcv_encap(skb, 0);
}

实际就是xfrm4_rcv_encap,封装类型参数设置为0,即没封装数据

2.3 xfrm4_rcv_encap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/* net/ipv4/xfrm4_input.c */
int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type)
{
	int err;
	__be32 spi, seq;
	struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH];
	struct xfrm_state *x;
	int xfrm_nr = 0;
	int decaps = 0;
	// 获取skb中的spi和序列号信息
	if ((err = xfrm4_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) != 0)
		goto drop;
	// 进入循环进行解包操作
	do {
		struct iphdr *iph = skb->nh.iph;
		// 循环解包次数太深的话放弃
		if (xfrm_nr == XFRM_MAX_DEPTH)
			goto drop;
		// 根据地址, SPI和协议查找SA
		x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, iph->protocol, 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等, 对输入数据解密
		if (x->type->input(x, skb))
			goto drop_unlock;
		/* 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);
		xfrm_vec[xfrm_nr++] = x;
		// mode可为通道,传输等模式, 对输入数据解封装
		if (x->mode->input(x, skb))
			goto drop;
		// 如果是IPSEC通道模式,将decaps参数置1,否则表示是传输模式
		if (x->props.mode == XFRM_MODE_TUNNEL) {
			decaps = 1;
			break;
		}
		// 看内层协议是否还要继续解包, 不需要解时返回1, 需要解时返回0, 错误返回负数
		// 协议类型可以多层封装的,比如用AH封装ESP, 就得先解完AH再解ESP
		if ((err = xfrm_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) < 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);
	if (decaps) {
		// 通道模式
		if (!(skb->dev->flags&IFF_LOOPBACK)) {
			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->nh.raw);
		skb->nh.iph->tot_len = htons(skb->len);
		ip_send_check(skb->nh.iph);
		NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL, xfrm4_rcv_encap_finish);
		return 0;
#else
		// 内核不支持NETFILTER, 该包肯定就是到自身的了
		// 返回IP协议的负值, 表示重新进行IP层协议的处理
		// 用解码后的内层协议来处理数据
		return -skb->nh.iph->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;
}

最后说一下返回负协议值的处理, IP上层协议的handler是在ip_local_deliver_finish()函数中调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* net/ipv4/ip_input.c */
static inline int ip_local_deliver_finish(struct sk_buff *skb)
{
	int ihl = skb->nh.iph->ihl*4;
	__skb_pull(skb, ihl);
	/* Point into the IP datagram, just past the header. */
	skb->h.raw = skb->data;
	rcu_read_lock();
	{
		/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
		int protocol = skb->nh.iph->protocol;
		int hash;
		struct sock *raw_sk;
		struct net_protocol *ipprot;
	resubmit:
		// 协议hash值, IPv4最大支持255种协议
		hash = protocol & (MAX_INET_PROTOS - 1);
		raw_sk = sk_head(&raw_v4_htable[hash]);
		/* If there maybe a raw socket we must check - if not we
			* don't care less
			*/
		if (raw_sk && !raw_v4_input(skb, skb->nh.iph, hash))
			raw_sk = NULL;
		// 直接在协议数组中获取协议指针
		if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
			int ret;
			if (!ipprot->no_policy) {
				if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					kfree_skb(skb);
					goto out;
				}
				nf_reset(skb);
			}
			// 调用协议handler
			ret = ipprot->handler(skb);
			if (ret < 0) {
				// 如果返回值为负, 取反后重新跳回resubmit点进行选协议处理
				protocol = -ret;
				goto resubmit;
			}
			IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
		} else {
			if (!raw_sk) {
				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
					icmp_send(skb, ICMP_DEST_UNREACH,
								ICMP_PROT_UNREACH, 0);
				}
			} else
				IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
			kfree_skb(skb);
		}
	}
	out:
	rcu_read_unlock();
	return 0;
}

IPSEC介绍与实现

http://www.360doc.com/content/14/0210/13/706976_351324389.shtml

一、介绍

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

必须选择下面的选择

1
2
3
CONFIG_INET_AH
CONFIG_INET_ESP
CONFIG_XFRM_USER

可能还要安装 module-init-tool

如何生成kernel看另外的文档

2. ipsec-tools

1
2
3
./configure --prefix=/
 make
make install

3. 两台机器的通讯

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

在一台linux中

1
2
3
4
5
6
7
8
9
10
11
12
#加入pf_sock
modprobe af_key

#加密
modprobe md5
modprobe des

#AH
modprobe ah4

#esp
modprobe esp4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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后,就可以通讯了

速度测试:

1
2
3
       没有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

生成证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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是内核中变化比较大的部分,每个版本中都有不小的差异, 同时也说明了该模块的不成熟性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在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中定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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,
};

[初始化]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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;
}

[数据接收]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
下面我们先看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中定义,作为安全路由的输出函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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只验证负载部分.

具体的协议结构定义如下, 通常只定义初始化,析构,输入和输出四个成员函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
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;
}

[模式函数实现]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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的策略协议相关处理结构定义如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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;
}

[网卡通知回调实现]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

ipt_CLUSTERIP

Linux内核自带了一种多机高可用性的解决方法。在每台机器上配置一个相同ip、mac的多播地址。可以给CLUSTERIP设置多个node,经过这个地址的数据流会按一定算法分配到某个node上。只有持有某个node的机器会收、发属于这个node的数据流,其他机器会drop数据包。

说明:

1) 同一个CLUSTERIP的同一个node可以配置在多台机器上,这样每台都会处理包,但这种情形不是想要的。

2) node 可以只有一个,这种情况下流经CLUSTERIP的数据流都属于这个node

3) 添加CLUSTERIP命令:

1
iptables -I INPUT -d 100.64.1.2 -i eth2 -p icmp -j CLUSTERIP --new --clustermac 01:00:5E:44:55:66 --total-nodes 2 --local-node 1 --hashmode sourceip

4) 切换node的方法:

1
2
echo +1 > /proc/net/ipt_CLUSTERIP/xx.xx.xx.xx
echo -2 > /proc/net/ipt_CLUSTERIP/xx.xx.xx.xx

http://www.linux-ha.org/ClusterIP

http://www.rkeene.org/projects/info/wiki/102

https://www.douban.com/note/389412837/

modprobe ipt_CLUSTERIP

这样就会在/proc/net下创建个ipt_CLUSTERIP目录。

使用iptables创建一条规则,这条规则同时也会创建一个clusterip_config,和规则是一一对应的,同样在

/proc/net/ipt_CLUSTERIP下也会创建一个文件,文件名是虚拟服务器的ip (vip)。在iptables里指定。

在node1上执行

iptables -I INPUT -d 192.168.122.33(这个就个vip) -i eth0 (哪个接口属于集群) -p icmp -j CLUSTERIP –new(必须有的) –hashmode sourceip(用source ip做hash,还有其他的?) –clustermac 01:00:5E:7F:18:0A(虚拟服务器的mac地址,必须为多播地址) –total-nodes 2(这个集群里一共有几台机器) –local-node 1(本地机器编号,只有编号为1的机器才有权力与客户端通信)。这个命令会将为eth0设备添加这个多播地址,见dev_mc_add。

添加完命令后,给这个Node加个ip

ifconfig eth0:0 192.168.122.33, 这样node的内核才回相应arp请求。 所以,客户端在想访问192.168.122.33前,会发送一个arp请求,node1收到后,看到这个ip是自己的,于是便回复你找的就是我,把自己的mac地址发送给客户端。当这个arp回复经过OUTPUT链的arp_mangle钩子时,这个函数会判断 这个arp回复里的消息,如果发现客户端想知道vip的mac地址,就把这个mac地址修改为clustermac,也就是01:00:5E:7F:18:0A。

这样以后客户端发的数据包就会变成广播包了, 所有的node ip都是192.168.122.33,所以所有的node都会收到这个消息,但是只有node1才有权力回复消息。其他node会在clusterip target里把数据包丢掉。

CLUSTERIP TARGET还有一个作用,就是把数据包pky_type修改为PACKET_HOST,让上层乐意收包。