http://blog.csdn.net/u011130578/article/details/44942679
8.5.1 选项功能
TCP MD5选项用于强化BGP协议的安全性,其基本原理是在TCP报文段的选项中携带MD5摘要。这个摘要的行为类似于这个报文的签名,其中包含这只有通信双方才能理解的信息。如果BGP协议使用TCP作为其传输层协议,使用MD5选项会有效减少安全隐患。
8.5.2 协议规范
TCP MD5选项的规范由RFC 2385提出。
每一个TCP报文段都应该携带MD5选项(包含一个16字节的MD5 digest)。MD5算法的输入数据如下(严格按照顺序):
(1)TCP伪首部(源IP,目的IP,填充0的协议号,报文长度)
(2)TCP首部,不包含选项,checksum计为0
(3)TCP数据段(如果有)
(4)密钥或口令,这个需要TCP通信双方和连接规范都知晓
接收方收到TCP报文时,必须根据报文的信息以及自己的密钥来计算digest,并与报文中的digest进行比较。如果比较失败则必须丢弃报文,并且不能产生任何响应。这样就大大增加了攻击者通过伪造TCP报文实施对BGP协议的攻击的难度。
8.5.3 开启方法
Linux内核需要开启CONFIG_TCP_MD5SIG编译选项才能支持TCP MD5选项功能。应用进程还需要使用TCP_MD5SIG socket选项导入密钥:
1
2
3
struct tcp_md5sig cmd;
...
setsockopt(sockfd, SOL_TCP, TCP_MD5SIG, &cmd, sizeof(cmd));
其中struct tcp_md5sig的定义为:
1
2
3
4
5
6
7
8
9
191 #define TCP_MD5SIG_MAXKEYLEN 80
192
193 struct tcp_md5sig {
194 struct __kernel_sockaddr_storage tcpm_addr; /* address associated */
195 __u16 __tcpm_pad1; /* zero */
196 __u16 tcpm_keylen; /* key length */
197 __u32 __tcpm_pad2; /* zero */
198 __u8 tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
199 };
其中tcpm_addr是要通信的服务器的地址(IP地址、端口等),如果sockfd要与N个机器进行通信则需要调用N此setsockopt系统调用来导入相应的地址-密钥对。举个例子,如果A要与B通信,则A需要调用setsockopt来导入B的地址和一个密钥Key,而B也需要调用setsockopt来导入A的地址和与A相同的密钥Key,然后双方才能使用MD5选项进行通信。
8.5.4 内核实现
TCP_MD5SIG socket选项对应的内核代码为:
1
2
3
4
5
6
7
8
9
10
11
2371 static int do_tcp_setsockopt(struct sock *sk, int level,
2372 int optname, char __user *optval, unsigned int optlen)
2373 {
...
2605 #ifdef CONFIG_TCP_MD5SIG
2606 case TCP_MD5SIG:
2607 /* Read the IP->Key mappings from userspace */
2608 err = tp->af_specific->md5_parse(sk, optval, optlen); //指向tcp_v4_parse_md5_keys函数
2609 break;
2610 #endif
...
tcp_v4_parse_md5_keys用于导入MD5签名的密钥(key):
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
1083 static int tcp_v4_parse_md5_keys(struct sock *sk, char __user *optval,
1084 int optlen)
1085 {
1086 struct tcp_md5sig cmd;
1087 struct sockaddr_in *sin = (struct sockaddr_in *)&cmd.tcpm_addr;
1088
1089 if (optlen < sizeof(cmd))
1090 return -EINVAL;
1091
1092 if (copy_from_user(&cmd, optval, sizeof(cmd)))
1093 return -EFAULT;
1094
1095 if (sin->sin_family != AF_INET)
1096 return -EINVAL;
1097
1098 if (!cmd.tcpm_key || !cmd.tcpm_keylen)
1099 return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,
1100 AF_INET); //删除key
1101
1102 if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
1103 return -EINVAL;
1104
1105 return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,
1106 AF_INET, cmd.tcpm_key, cmd.tcpm_keylen,
1107 GFP_KERNEL);
1108 }
tcp_md5_do_add和tcp_md5_do_del用于添加和删除key:
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
998 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
999 int family, const u8 *newkey, u8 newkeylen, gfp_t gfp)
1000 {
1001 /* Add Key to the list */
1002 struct tcp_md5sig_key *key;
1003 struct tcp_sock *tp = tcp_sk(sk);
1004 struct tcp_md5sig_info *md5sig;
1005
1006 key = tcp_md5_do_lookup(sk, addr, family);
1007 if (key) { //如果有现成的
1008 /* Pre-existing entry - just update that one. */
1009 memcpy(key->key, newkey, newkeylen); //更新之
1010 key->keylen = newkeylen;
1011 return 0;
1012 }
1013
1014 md5sig = rcu_dereference_protected(tp->md5sig_info,
1015 sock_owned_by_user(sk));
1016 if (!md5sig) {
1017 md5sig = kmalloc(sizeof(*md5sig), gfp);
1018 if (!md5sig)
1019 return -ENOMEM;
1020
1021 sk_nocaps_add(sk, NETIF_F_GSO_MASK);
1022 INIT_HLIST_HEAD(&md5sig->head);
1023 rcu_assign_pointer(tp->md5sig_info, md5sig);
1024 }
1025
1026 key = sock_kmalloc(sk, sizeof(*key), gfp);
1027 if (!key)
1028 return -ENOMEM;
1029 if (hlist_empty(&md5sig->head) && !tcp_alloc_md5sig_pool(sk)) {
1030 sock_kfree_s(sk, key, sizeof(*key));
1031 return -ENOMEM;
1032 }
1033
1034 memcpy(key->key, newkey, newkeylen); //导入密钥
1035 key->keylen = newkeylen;
1036 key->family = family;
1037 memcpy(&key->addr, addr,
1038 (family == AF_INET6) ? sizeof(struct in6_addr) :
1039 sizeof(struct in_addr)); //导入地址信息
1040 hlist_add_head_rcu(&key->node, &md5sig->head);
1041 return 0;
1042 }
1043 EXPORT_SYMBOL(tcp_md5_do_add);
1044
1045 int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int family)
1046 {
1047 struct tcp_sock *tp = tcp_sk(sk);
1048 struct tcp_md5sig_key *key;
1049 struct tcp_md5sig_info *md5sig;
1050
1051 key = tcp_md5_do_lookup(sk, addr, family);
1052 if (!key)
1053 return -ENOENT;
1054 hlist_del_rcu(&key->node);
1055 atomic_sub(sizeof(*key), &sk->sk_omem_alloc);
1056 kfree_rcu(key, rcu);
1057 md5sig = rcu_dereference_protected(tp->md5sig_info,
1058 sock_owned_by_user(sk));
1059 if (hlist_empty(&md5sig->head))
1060 tcp_free_md5sig_pool();
1061 return 0;
1062 }
在TCP发送数据前构建选项信息(tcp_syn_options、tcp_established_options、tcp_synack_options)时都会执行类似下面的代码:
1
2
3
4
5
6
7
8
9
#ifdef CONFIG_TCP_MD5SIG
507 *md5 = tp->af_specific->md5_lookup(sk, sk); //指向tcp_v4_md5_lookup
508 if (*md5) {
509 opts->options |= OPTION_MD5;
510 remaining -= TCPOLEN_MD5SIG_ALIGNED;
511 }
512 #else
513 *md5 = NULL;
514 #endif
tcp_v4_md5_lookup用于查找MD5签名的key:
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
949 struct tcp_md5sig_key *tcp_md5_do_lookup(struct sock *sk,
950 const union tcp_md5_addr *addr,
951 int family)
952 {
953 struct tcp_sock *tp = tcp_sk(sk);
954 struct tcp_md5sig_key *key;
955 unsigned int size = sizeof(struct in_addr);
956 struct tcp_md5sig_info *md5sig;
957
958 /* caller either holds rcu_read_lock() or socket lock */
959 md5sig = rcu_dereference_check(tp->md5sig_info,
960 sock_owned_by_user(sk) ||
961 lockdep_is_held(&sk->sk_lock.slock));
962 if (!md5sig)
963 return NULL;
964 #if IS_ENABLED(CONFIG_IPV6)
965 if (family == AF_INET6)
966 size = sizeof(struct in6_addr);
967 #endif
968 hlist_for_each_entry_rcu(key, &md5sig->head, node) {
969 if (key->family != family)
970 continue;
971 if (!memcmp(&key->addr, addr, size)) //地址匹配
972 return key;
973 }
974 return NULL;
975 }
976 EXPORT_SYMBOL(tcp_md5_do_lookup);
977
978 struct tcp_md5sig_key *tcp_v4_md5_lookup(struct sock *sk,
979 struct sock *addr_sk)
980 {
981 union tcp_md5_addr *addr;
982
983 addr = (union tcp_md5_addr *)&inet_sk(addr_sk)->inet_daddr;
984 return tcp_md5_do_lookup(sk, addr, AF_INET);
985 }
可见如果应用进程导入了key,在构建选项时就会找到。选项信息构建完毕后,tcp_options_write函数会将选项信息写入TCP报头中:
1
2
3
4
5
6
7
8
9
10
11
12
13
409 static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp,
410 struct tcp_out_options *opts)
411 {
412 u16 options = opts->options; /* mungable copy */
413
414 if (unlikely(OPTION_MD5 & options)) {
415 *ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
416 (TCPOPT_MD5SIG << 8) | TCPOLEN_MD5SIG);
417 /* overload cookie hash location */
418 opts->hash_location = (__u8 *)ptr; //hash_location指向digest所在内存的首地址
419 ptr += 4; //digest大小为16个字节
420 }
...
tcp_options_write并没有写入MD5 digest,这个工作在后面完成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
828 static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
829 gfp_t gfp_mask)
830 {
...
870 if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
871 tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
872 else
873 tcp_options_size = tcp_established_options(sk, skb, &opts,
874 &md5);
...
925 tcp_options_write((__be32 *)(th + 1), tp, &opts);
...
929 #ifdef CONFIG_TCP_MD5SIG
930 /* Calculate the MD5 hash, as we have all we need now */
931 if (md5) {
932 sk_nocaps_add(sk, NETIF_F_GSO_MASK);
933 tp->af_specific->calc_md5_hash(opts.hash_location,
934 md5, sk, NULL, skb); //指向tcp_v4_md5_hash_skb
935 }
936 #endif
...
tcp_v4_md5_hash_skb函数计算MD5 digest:
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
1165 int tcp_v4_md5_hash_skb(char *md5_hash, struct tcp_md5sig_key *key,
1166 const struct sock *sk, const struct request_sock *req,
1167 const struct sk_buff *skb)
1168 {
1169 struct tcp_md5sig_pool *hp;
1170 struct hash_desc *desc;
1171 const struct tcphdr *th = tcp_hdr(skb);
1172 __be32 saddr, daddr;
1173
1174 if (sk) {
1175 saddr = inet_sk(sk)->inet_saddr;
1176 daddr = inet_sk(sk)->inet_daddr;
1177 } else if (req) {
1178 saddr = inet_rsk(req)->loc_addr;
1179 daddr = inet_rsk(req)->rmt_addr;
1180 } else {
1181 const struct iphdr *iph = ip_hdr(skb);
1182 saddr = iph->saddr;
1183 daddr = iph->daddr;
1184 }
1185
1186 hp = tcp_get_md5sig_pool();
1187 if (!hp)
1188 goto clear_hash_noput;
1189 desc = &hp->md5_desc;
1190
1191 if (crypto_hash_init(desc))
1192 goto clear_hash;
1193
1194 if (tcp_v4_md5_hash_pseudoheader(hp, daddr, saddr, skb->len)) //伪首部
1195 goto clear_hash;
1196 if (tcp_md5_hash_header(hp, th)) //TCP头
1197 goto clear_hash;
1198 if (tcp_md5_hash_skb_data(hp, skb, th->doff << 2)) //TCP数据
1199 goto clear_hash;
1200 if (tcp_md5_hash_key(hp, key)) //key
1201 goto clear_hash;
1202 if (crypto_hash_final(desc, md5_hash)) //将MD5 digest写入
1203 goto clear_hash;
1204
1205 tcp_put_md5sig_pool();
1206 return 0;
1207
1208 clear_hash:
1209 tcp_put_md5sig_pool();
1210 clear_hash_noput:
1211 memset(md5_hash, 0, 16);
1212 return 1;
1213 }
TCP在收到报文时会在入口函数检查MD5选项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1800 int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
1801 {
1802 struct sock *rsk;
1803 #ifdef CONFIG_TCP_MD5SIG
1804 /*
1805 * We really want to reject the packet as early as possible
1806 * if:
1807 * o We're expecting an MD5'd packet and this is no MD5 tcp option
1808 * o There is an MD5 option and we're not expecting one
1809 */
1810 if (tcp_v4_inbound_md5_hash(sk, skb))
1811 goto discard;
1812 #endif
...
tcp_v4_inbound_md5_hash函数返回false时检查通过:
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
1216 static bool tcp_v4_inbound_md5_hash(struct sock *sk, const struct sk_buff *skb)
1217 {
1218 /*
1219 * This gets called for each TCP segment that arrives
1220 * so we want to be efficient.
1221 * We have 3 drop cases:
1222 * o No MD5 hash and one expected.
1223 * o MD5 hash and we're not expecting one.
1224 * o MD5 hash and its wrong.
1225 */
1226 const __u8 *hash_location = NULL;
1227 struct tcp_md5sig_key *hash_expected;
1228 const struct iphdr *iph = ip_hdr(skb);
1229 const struct tcphdr *th = tcp_hdr(skb);
1230 int genhash;
1231 unsigned char newhash[16];
1232
1233 hash_expected = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&iph->saddr,
1234 AF_INET); //根据源IP地址查找key
1235 hash_location = tcp_parse_md5sig_option(th); //找到MD5 digest在TCP报头中的位置
1236
1237 /* We've parsed the options - do we have a hash? */
1238 if (!hash_expected && !hash_location) //进程没有导入key信息且没有找到MD5选项
1239 return false; //OK
1240
1241 if (hash_expected && !hash_location) { //进程导入了key信息且没有找到MD5选项
1242 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPMD5NOTFOUND);
1243 return true; //接收端期望有MD5选项而发送端没有,不行
1244 }
1245
1246 if (!hash_expected && hash_location) { //进程没有导入key信息但找到了MD5选项
1247 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPMD5UNEXPECTED);
1248 return true; //接收端不期望有MD5选项而发送端有,也不行
1249 }
1250
1251 /* Okay, so this is hash_expected and hash_location -
1252 * so we need to calculate the checksum.
1253 */
1254 genhash = tcp_v4_md5_hash_skb(newhash,
1255 hash_expected,
1256 NULL, NULL, skb); //使用key计算digest
1257
1258 if (genhash || memcmp(hash_location, newhash, 16) != 0) { //生成digest失败或digest不一样则检查不通过,丢弃之
1259 net_info_ratelimited("MD5 Hash failed for (%pI4, %d)->(%pI4, %d)%s\n",
1260 &iph->saddr, ntohs(th->source),
1261 &iph->daddr, ntohs(th->dest),
1262 genhash ? " tcp_v4_calc_md5_hash failed"
1263 : "");
1264 return true;
1265 }
1266 return false;
1267 }
tcp_parse_md5sig_option用于解析MD5选项:
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
3635 #ifdef CONFIG_TCP_MD5SIG
3636 /*
3637 * Parse MD5 Signature option
3638 */
3639 const u8 *tcp_parse_md5sig_option(const struct tcphdr *th)
3640 {
3641 int length = (th->doff << 2) - sizeof(*th);
3642 const u8 *ptr = (const u8 *)(th + 1);
3643
3644 /* If the TCP option is too short, we can short cut */
3645 if (length < TCPOLEN_MD5SIG)
3646 return NULL;
3647
3648 while (length > 0) {
3649 int opcode = *ptr++;
3650 int opsize;
3651
3652 switch(opcode) {
3653 case TCPOPT_EOL:
3654 return NULL;
3655 case TCPOPT_NOP:
3656 length--;
3657 continue;
3658 default:
3659 opsize = *ptr++;
3660 if (opsize < 2 || opsize > length)
3661 return NULL;
3662 if (opcode == TCPOPT_MD5SIG)
3663 return opsize == TCPOLEN_MD5SIG ? ptr : NULL;
3664 }
3665 ptr += opsize - 2;
3666 length -= opsize;
3667 }
3668 return NULL;
3669 }
3670 EXPORT_SYMBOL(tcp_parse_md5sig_option);
3671 #endif
使用TCP MD5选项带来安全性的同时,由于需要计算MD5 digest会带来一些性能损耗,且每包都携带18字节的MD5选项字段也会降低数据发送效率。不过对于类似BGP这样对安全性要求较高的应用来说,这些代码应该是可以承受的。
server.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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include <linux/ip.h>
#include <netinet/tcp.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr, client;
socklen_t clen;
char buff[4096];
int n;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
struct tcp_md5sig cmd;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
memcpy(&cmd.tcpm_addr, &servaddr, sizeof(servaddr));
cmd.tcpm_keylen = 5;
memcpy(cmd.tcpm_key, "1234567890", 5);
setsockopt(listenfd, SOL_TCP, TCP_MD5SIG, &cmd, sizeof(cmd));
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
printf("======waiting for client's request======\n");
while (1) {
clen = sizeof(struct sockaddr);
if ((connfd = accept(listenfd, (struct sockaddr*)&client, &clen)) == -1) {
printf("accept socket error: %s(errno: %d)", strerror(errno), errno);
continue;
}
n = recv(connfd, buff, MAXLINE, 0);
buff[n] ='\0';
printf("recv msg from client: %s\n", buff);
close(connfd);
}
close(listenfd);
return 0;
}
client.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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include <linux/ip.h>
#include <netinet/tcp.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
if (argc != 2) {
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
printf("inet_pton error for %s\n", argv[1]);
exit(0);
}
struct tcp_md5sig cmd;
memcpy(&cmd.tcpm_addr, &servaddr, sizeof(servaddr));
cmd.tcpm_keylen = 5;
memcpy(cmd.tcpm_key, "1234567890", 5);
setsockopt(sockfd, SOL_TCP, TCP_MD5SIG, &cmd, sizeof(cmd));
if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
printf("connect error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
if (send(sockfd, sendline, strlen(sendline), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
close(sockfd);
return 0;
}