本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
socket编程中,在发送数据时,通过向api传递socket从而告诉kernel使用哪个socket发送数据。在接收数据时,也是向api传递socket来告诉kernel,应用程序想从哪个socket接收数据。乍一看,这一过程很清楚。但是仔细一想,当接收数据时,应用程序向kernel传递了socket,实际上只是告诉kernel,应用程序想从哪个socket的接收缓冲中接收数据。而kernel如何将数据放到对应的socket的接收缓冲中的呢?这是我们今天学习的目的。
在前面的博文中,学习了完整的接受数据的流程。在L3 IP层的处理函数ip_local_deliver_finish中,调用raw_local_deliver,将数据包的clone传给对应的raw socket。
- int raw_local_deliver(struct sk_buff *skb, int protocol)
-
{
-
int hash;
-
struct sock *raw_sk;
/* 使用protocol作为hash值,找到对应的raw socket链表 */
-
hash = protocol & (RAW_HTABLE_SIZE - 1);
-
raw_sk = sk_head(&raw_v4_hashinfo.ht[hash]);
-
-
/* If there maybe a raw socket we must check - if not we
-
* don't care less
-
*/
- /* 如果有对应该IP协议的raw socket,则通过raw_v4_input继续匹配 */
-
if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
-
raw_sk = NULL;
-
-
return raw_sk != NULL;
-
-
}
从上面可以看出,kernel中的raw socket是以协议为key,每个协议的raw socket为一个链表。通过协议来确定去哪个raw socket 链表进行匹配。这里可以看出,IP层的协议是第一个raw socket的匹配条件。
- static int raw_v4_input(struct sk_buff *skb, struct iphdr *iph, int hash)
-
{
-
struct sock *sk;
-
struct hlist_head *head;
-
int delivered = 0;
-
struct net *net;
-
-
read_lock(&raw_v4_hashinfo.lock);
-
head = &raw_v4_hashinfo.ht[hash];
-
if (hlist_empty(head))
-
goto out;
/* 查找匹配的raw socket */
-
net = dev_net(skb->dev);
-
sk = __raw_v4_lookup(net, __sk_head(head), iph->protocol,
-
iph->saddr, iph->daddr,
-
skb->dev->ifindex);
-
-
while (sk) {
-
delivered = 1;
-
if (iph->protocol != IPPROTO_ICMP || !icmp_filter(sk, skb)) {
- /*
- 如果数据包不是ICMP 或者不是需要过滤的ICMP数据包,
- 那么就clone一个数据包
- */
-
struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);
-
-
/* Not releasing hash */
- /* clone 成功,将clone的数据包传给找到的raw socket */
-
if (clone)
-
raw_rcv(sk, clone);
-
}
- /* 继续匹配下一个raw socket */
-
sk = __raw_v4_lookup(net, sk_next(sk), iph->protocol,
-
iph->saddr, iph->daddr,
-
skb->dev->ifindex);
-
}
-
out:
-
read_unlock(&raw_v4_hashinfo.lock);
-
return delivered;
-
}
这个函数主要是循环匹配raw socket,可以看出,其遍历了对应协议的raw socket。这就是为什么所有匹配的raw socket都可以收到一份数据包的clone。
下面是真正的raw socket的匹配函数。
- static struct sock *__raw_v4_lookup(struct net *net, struct sock *sk,
-
unsigned short num, __be32 raddr, __be32 laddr, int dif)
-
{
-
struct hlist_node *node;
-
-
sk_for_each_from(sk, node) {
-
struct inet_sock *inet = inet_sk(sk);
-
-
if (net_eq(sock_net(sk), net) && inet->inet_num == num &&
-
!(inet->inet_daddr && inet->inet_daddr != raddr) &&
-
!(inet->inet_rcv_saddr && inet->inet_rcv_saddr != laddr) &&
-
!(sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif))
-
goto found; /* gotcha */
-
}
-
sk = NULL;
-
found:
-
return sk;
-
}
这个函数是真正的匹配函数,去比较了socket的net——这个是一个network namespace,在一般应用中,肯定是相等的,协议,目的地址,源地址,绑定的interface。在比较的时候,都是在raw socket设置了对应的匹配条件才进行比较的。也就是说,如果创建的raw socket没有调用过bind,connect等,只要对应的协议匹配,那么这个raw socket就是匹配的。
这就是raw socket的匹配过程,下面看L4 传输层的匹配。以UDP为例。
在__udp4_lib_rcv中,其调用__udp4_lib_rcv->__udp4_lib_lookup->udp4_lib_lookup2查找最佳的一个匹配的UDP socket。主要这里与raw socket的区别。到了L4层,只确定一个最佳的socket,也就是说即使有多个匹配的socket,也只有一个socket能收到数据包。
- static struct sock *udp4_lib_lookup2(struct net *net,
-
__be32 saddr, __be16 sport,
-
__be32 daddr, unsigned int hnum, int dif,
-
struct udp_hslot *hslot2, unsigned int slot2)
-
{
- ...... ......
- udp_portaddr_for_each_entry_rcu(sk, node, &hslot2->head) {
-
score = compute_score2(sk, net, saddr, sport,
-
daddr, hnum, dif);
-
if (score > badness) {
-
result = sk;
-
badness = score;
-
if (score == SCORE2_MAX)
-
goto exact_match;
-
}
-
}
...... ......
- }
该函数调用compute_score2计算socket的匹配程度,也就是得分score,分数最高的即为最佳匹配的socket。
下面看comute_score2的代码
- static inline int compute_score2(struct sock *sk, struct net *net,
-
__be32 saddr, __be16 sport,
-
__be32 daddr, unsigned int hnum, int dif)
-
{
-
int score = -1;
-
-
if (net_eq(sock_net(sk), net) && !ipv6_only_sock(sk)) {
-
struct inet_sock *inet = inet_sk(sk);
-
/* socket的本地地址不匹配数据包的目的地址 */
-
if (inet->inet_rcv_saddr != daddr)
-
return -1;
- /*
- socket的端口不匹配数据包的目的端口
- */
-
if (inet->inet_num != hnum)
-
return -1;
/* 匹配family */
-
score = (sk->sk_family == PF_INET ? 1 : 0);
- /* 比较socket的目的地址和数据包的源地址 */
-
if (inet->inet_daddr) {
-
if (inet->inet_daddr != saddr)
-
return -1;
-
score += 2;
-
}
- /* 比较socket的目的端口和数据包的源端口 */
-
if (inet->inet_dport) {
-
if (inet->inet_dport != sport)
-
return -1;
-
score += 2;
-
}
- /* 比较socket bind的interface */
-
if (sk->sk_bound_dev_if) {
-
if (sk->sk_bound_dev_if != dif)
-
return -1;
-
score += 2;
-
}
-
}
-
return score;
-
}
看这个函数,可能大家会有一点疑问。这个函数的前两个条件是比较socket的源地址和源端口,对于udp socket来说,如果不调用bind,可以使用通用地址和端口,那么这两个比较失败了。怎么办?岂不是使用通用地址和端口的socket,无法收到数据包了?
这需要我们回头看__udp4_lib_lookup这个函数
- static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr,
-
__be16 sport, __be32 daddr, __be16 dport,
-
int dif, struct udp_table *udptable)
-
{
-
struct sock *sk, *result;
-
struct hlist_nulls_node *node;
-
unsigned short hnum = ntohs(dport);
-
unsigned int hash2, slot2, slot = udp_hashfn(net, hnum, udptable->mask);
-
struct udp_hslot *hslot2, *hslot = &udptable->hash[slot];
-
int score, badness;
-
-
rcu_read_lock();
-
if (hslot->count > 10) {
- /* 当socket个数过多时,使用精确匹配 */
- ...... ......
- /* 如上面所示,要精确匹配地址和端口 */
-
result = udp4_lib_lookup2(net, saddr, sport,
-
daddr, hnum, dif,
-
hslot2, slot2);
-
if (!result) {
- /*
- 精确匹配失败,则使用通用地址进行匹配。
- 利用通用地址的所关联的hash替代原来精确匹配的hash。
- 直接使用通用地址代替数据包的的目的地址
- */
-
-
hash2 = udp4_portaddr_hash(net, htonl(INADDR_ANY), hnum);
-
slot2 = hash2 & udptable->mask;
-
hslot2 = &udptable->hash2[slot2];
-
if (hslot->count < hslot2->count)
-
goto begin; //跳到begin
-
-
result = udp4_lib_lookup2(net, saddr, sport,
-
htonl(INADDR_ANY), hnum, dif,
-
hslot2, slot2);
-
}
-
rcu_read_unlock();
-
return result;
-
}
-
begin:
-
result = NULL;
-
badness = -1;
- /* 使用compute_score查找匹配的socket */
-
sk_nulls_for_each_rcu(sk, node, &hslot->head) {
-
score = compute_score(sk, net, saddr, hnum, sport,
-
daddr, dport, dif);
-
if (score > badness) {
-
result = sk;
-
badness = score;
-
}
-
}
...... ......
- }
compute_score与compute_score2的代码基本相同,不过只有bind了目的地址时,才会进行比较。这样通用的地址和端口也可以匹配了。具体的代码就不在此列出了。
到此,基本上对于kernel如何查找对应数据包的socket已经基本清晰了。对于raw socket,只要匹配了raw socket设置的一些过滤条件,如地址,端口等,那么所有的raw socket均可以收到一个数据包的clone。对于L4 传输层的socket,kernel会遍历socket,找出一个最匹配的socket,匹配条件为协议,端口,地址,interface等。通用匹配的优先级要低于精确匹配。
阅读(900) | 评论(0) | 转发(0) |