kk Blog —— 通用基础


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

ipsec 的SPD和SAP详解

http://www.360doc.com/content/12/0501/10/9523427_207838323.shtml

8.6.1 IPSec机制的SPD

SPD 的内容用来存放IPSec 的规则,而这些规则用来定义哪些流量需要走IPSec,这个数据库的内容相当多,笔者仅介绍我们需要知道的部分,这些信息有目的端IP、来源端IP、只执 行AH 或ESP、同时执行AH 及ESP、目的端Port、来源端Port、走Transport 或Tunnel 模式。图8-35 即为SPD 的结构,从结构内容我们可以看到第一笔及第二笔数据刚好构成一条双向的VPN 连接,此外,因为一台主机可能同时与多部主机进行IPSec 连接,因此数据库的内容可能同时存在多笔数据。

图 8-35 SPD 数据库结构

有了以上的信息,当VPN 主机有数据封包要送出时,这个封包会由SPD进行匹配,如果封包的来源端及目的端IP 不等于SPD 所记载的内容,那么这个封包就不会交由AH 或ESP 协议来处理;反之,封包就会被送交给AH 或ESP 协议来处埋,至于会送给谁来处理,就由SPD 的“执行协议”这个字段的内容来决定。

8.6.2 IPSec机制的SAD

存放在SAD 数据库中的参数有SPI 值、目的端IP、AH 或ESP、AH 验证算法、AH 验证的加密密钥、ESP 验证算法、ESP 验证的加密密钥、ESP 的加密算法、ESP 的加密密钥、走Transport 或Tunnel 模式,其中SPI(Security ParameterIndex,索引值)是两部VPN 主机之间以随机数或手动指定的唯一值,其目的是要作为数据库的索引值,这对整个IPSec 的运行没有其他用途。此外,需要注意AH 与ESP 协议的参数是分开的,因此,笔者刻意把SAD 划分成两个部分,并以执行协议来区分。

封包在SPD 中被决定必须执行AH、ESP 或AH 及ESP 协议之后,就会从SAD 数据库中找到处理这个流量的参数。例如,我们是192.168.0.1 主机,当SPD 决定192.168.0.1 送到192.168.0.10 的TCP Port 110 的封包时,需要执行AH 及ESP 协议,在封包交给ESP 机制之后,ESP 机制会以目的端IP 来找到处理这个封包的参数,如图8-36 中的第一条数据。此外,ESP 机制在处理的过程中会将SPI 值加入到ESP 包头内,就如图8-37 中ESP 包头内的SPI 值,至于这个SPI 值的用途,本节稍后会有完整的解说。

图 8-36 SAD 数据库结构(1)

图 8-37 SAD 数据库结构(2)

ESP 协议处理完成之后,接着把封包交由AH 机制来处理,AH 机制从图8.36 中找到目的端IP 为192.68.0.10 的那一条数据,并从数据中找到处理这个封包的参数。此外,在AH 处理的过程中,也会将SPI 值一并包在AH 包头之中,也就是如图8-37 中AH 包头内所看到的SPI 值,至于SPI 值的用途稍后也会有完整的介绍。

我们同样可以借助setkey 工具来管理SAD 的内容,例如,可以使用setkey-D 来检查SAD 的内容,或是使用setkey -D -F 来清除。

接下来以图8-38 为例来说明IPSec 的运行流程。假 设主机A 要送数据给主机B,当数据送达网络层的较下端时①,IPSec 的Filter 会将封包的特征与SPD 数据库的内容匹配②,如果封包特征与SPD 数据库的内容都没有符合,这个封包就会直接通过网络传送给主机B ③,在这个封包进入到主机B 之后,主机B 的IPSec Filter 会将这个封包的特征与其SPD 数据库的内容匹配④,如果匹配的结果不符合,那么封包就直接往上层传送⑤。

图 8-38 IPSec 的运行流程

但 如果主机A 送给主机B 的封包特征有符合主机A 上SPD 数据库内容,封包就会被送入到IPSec 的AH 及ESP 机制中⑥,接着,AH、ESP 就会到SAD 数据库中找到处理这个封包的参数⑦,完成处理后的封包随即被送往主机B ⑧,在主机B 找到这个封包之后,就把这个封包的特征与其SPD 数据库的内容进行匹配④,如果匹配的结果符合,这个封包就会送入AH 及ESP 机制处理⑨,接着,AH、ESP 就会从SAD 数据库中找到处理这个封包的参数,最后,将处理完成的数据往上层传递。看完以上的流程之后,相信你可以更加了解IPSec 的运行流程。

最后,我们来看AH 及ESP 包头内SPI 值的用途是什么。试想,如果你是192.168.0.10 这台VPN 主机,当你从网络上收到一个IPSec 处理后的封包,请问要如何去验证这个封包的完整性及解开这个封包的加密部分呢?首要任务就是找到AH 及ESP 的参数,因为有这些参数我们才能知道封包是以何种验证算法来计算,且AH 的加密密钥、ESP 的加密算法及ESP 的加密密钥又各是什么。

由于我们在启动IPSec VPN 时会将AH 及ESP 的参数分别汇入到两部VPN 主机的SPD 及SAD 两个数据库,因此,处理这个封包的AH 及ESP 参数在本机的SAD 数据库内一定找得到,但问题是要如何找到? 我们可以从图8-39看到这个封包的来源端IP 是192.168.0.1、目的端IP 是192.168.0.10,而且在第封包内AH 及ESP 包头的部分分别可以看到一个SPI 的参数,这个例子为“AH:0x200”、“ESP :0x201”,这样,当我们收到这个封包之后,就可以从目前使用的协议AH、封包的目的端IP 及封包内AH 包头内的SPI 值来匹配出SAD 的其中一条数据,而这笔数据一定会是唯一的, 这样一来,就可以取得处理这个封包的AH 及ESP 参数。这个SPI 值是从何而来的呢? SPI 值产生的方式有两种,其一是由系统自动产生,其二是我们先回顾IPSec 设定文件的内容后,如下第4、6、8、10 这4 行中的0x200 字段就是手动指定SPI 的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. flush;
2. spdflush;
3. #=====================<< SAD >>==============================
4. add 10.0.1.200 10.0.1.210 ah 0x300 -m tunnel
5. -A hmac-sha1 0x3f3f0cd71d0e300d5788127cc78db64ea3f21107;
6. add 10.0.1.210 10.0.1.200 ah 0x200 -m tunnel
7. -A hmac-sha1 0x73f6e61bf4c8307020c230b367296e26a5262fb5;
8. add 10.0.1.200 10.0.1.210 esp 0x201 -m tunnel
9. -E 3des-cbc 0xb99d4139d9976665eede3f74d4e897617c5a7452e5789f9f;
10. add 10.0.1.210 10.0.1.200 esp 0x301 -m tunnel
11. -E 3des-cbc 0xbb025b18e28ea7fb15479ba25346e24fd9acb1e7cd502a25;
12. #=====================<< SPD >>==============================
13. spdadd 192.168.0.0/24 192.168.1.0/24 any -P out ipsec
14. esp/tunnel/10.0.1.210-10.0.1.200/require
15. ah/tunnel/10.0.1.210-10.0.1.200/require;
16. spdadd 192.168.1.0/24 192.168.0.0/24 any -P in ipsec
17. esp/tunnel/10.0.1.200-10.0.1.210/require
18. ah/tunnel/10.0.1.200-10.0.1.210/require;

图 8-39 IPSec 的封包结构

IPSec传输模式下ESP报文装包和拆包过程

https://blog.csdn.net/tl437002770/article/details/51107399

IPSec是一种端到端的确保IP层通信安全的体制,IPSec不是一个单独的协议,而是一组协议,如图1所示,IPSec安全体系主要包含了3种协议ESP、AH、IKE,1个安全联盟SA和一些加密和认证的算法。

图1

AH(Authentication Header):提供数据完整性验证,通过Hash实现;数据源身份认证,在计算验证码时加入共享密钥;防止重放攻击,AH包头的序列号可防止重放攻击。

ESP(Encapsulating Security Payload):ESP的协议号是50,提供AH的三种服务,此外,数据包加密,可加密整个IP报文,也可只加密IP的数据部分;数据流加密,用于支持IPSec路由器。

IKE(Internet Key Exchange):IKE负责密钥管理,用于实体之间的身份认证,加密算法的协商,以及会话密钥的生成,IKE的结果会保留在安全联盟(SA)中,供AH和ESP通信使用。

SA(Security Association):SA是两个IPSec实体之间经过协商建立起来的一种安全协定,包括采用何种IPSec协议、运行模式是隧道模式还是传输模式,以及使用的验证算法和加密算法等一系列内容。

AH和ESP使用SA来保护通信,两者可单独使用,也可嵌套使用,IKE主要功能就是在实体之间协商SA。

IPSec主要有两种工作方式,隧道模式和传输模式,隧道模式保护的是整个IP数据包,而传输模式保护的是IP的载荷,即真正传输的数据。如图2所示,是ESP头的格式,由ESP头部、载荷数据、ESP尾部和验证数据组成,其中,32位的SPI与目的地址和协议组成的三元组可以为IP包确定唯一的SA,序列号可用于抗重放攻击,载荷数据为明文或者加密后的密文,本字段的长度必须是8位的整数倍,填充长度指明填充字段的长度,下一个头指明载荷的数据类型,比如TCP等,验证数据可选,储存验证的结果。

图2

如图3,为传输模式下ESP装包过程:首先,给原IP报文添加ESP尾部;然后,将尾部和原IP报文的载荷一起进行加密;第三,将机密数据加入ESP头部;第四,对加密区域和ESP头部进行验证,得到完整性度量值,附在ESP报文最后;最后,将IP头部附在ESP报文前,构成新的IP报文。

图3

拆包时,首先,检查协议类型,确定为IPSec包;然后,通过ESP头部SPI确认SA内容,以及通过序列号确认不是重放攻击;第三,计算验证区域的摘要,与ESP验证数据做比较,相同则数据完整;第四,根据SA提供的算法和密钥,解密加密区域,得原IP数据包和ESP尾部;第五,根据尾部填充长度学习删除填充字段,即可得原IP数据包;最后,根据IP的目的地址进行转发。

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;
}