一、问题
当我们希望一个套接口能够接收到一个主机上所有网卡上收到的某个端口的数据的报文的时候,我们一般给bind传递的地址是
/* Address to accept any incoming messages. */
#define INADDR_ANY ((unsigned long int) 0x00000000)
这样就有一个问题,当某一个网卡真的受到了一个报文的时候,它将会怎么进行地址的匹配呢?更一般的说,当bind的是一个特定地址的特定端口的时候,这个报文又是如何处理的呢?
二、内核中的相关处理
废话不多说,我们就看一下真正的端口查找的时候进行的操作吧。
static inline int compute_score(struct sock *sk, struct net *net,
const unsigned short hnum, const __be32 daddr,
const int dif)
{
int score = -1;
struct inet_sock *inet = inet_sk(sk);
if (net_eq(sock_net(sk), net) && inet->inet_num == hnum &&
!ipv6_only_sock(sk)) {
__be32 rcv_saddr = inet->inet_rcv_saddr;
score = sk->sk_family == PF_INET ? 1 : 0;
if (rcv_saddr) {
if (rcv_saddr != daddr)
return -1;
score += 2;
} 这里就是完成INADDRANY的匹配位置,当bind传递的地址是一个全部为零的地址的时候,这里并不会返回,被选中的几率设置为最低的1,如果有更加精确的匹配,或者通过SOBINDDEVICE设置了网口的偏好的情况下,就可以获得更好的匹配。
if (sk->sk_bound_dev_if) {
if (sk->sk_bound_dev_if != dif)
return -1;
score += 2;
}
}
return score;
}
这个函数将会完成从底层接受到的协议向上转发规则。
由于我们知道了这个函数,就可以顺着这个叶子函数向上追踪,从而得到一个转发的路径。如果你足够幸运,那么还可以通过打一个断点,从而可以看到调用是如何通过几十层的调用关系到达这个位置的。
这个调用关系
tcp_v4_rcv--->>>inet_lookup_listener---->>__inet_lookup_listener--->>>compute_score
其中使用到的全局变量tcp_hashinfo也令人震惊,因为TCP中并没有说socket的这个概念,所以内核有义务通过端口和地址来找到匹配的一个地址,从而就需要通过hash表将系统中所有的socket联系在一起,从而当真正的报文来的时候,可以通过目的以及源来知道对应的内核维护的socket结构。
三、地址的绑定
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;
这个端口完成了地址的复制,这里没有对INADDRANY做任何处理,所以内核中保存的这个地址就为零。
四、sockfs文件系统
static int __init sock_init(void)------>>>> register_filesystem(&sock_fs_type);
当分配一个sock的时候:
sock_alloc_inode
static int init_inodecache(void)
{
sock_inode_cachep = kmem_cache_create("sock_inode_cache",
sizeof(struct socket_alloc),
0,
(SLAB_HWCACHE_ALIGN |
SLAB_RECLAIM_ACCOUNT |
SLAB_MEM_SPREAD),
init_once);
if (sock_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
五、bind地址时冲突
inet_bind--->>>>(sk->sk_prot->get_port(sk, snum))--->>>inet_csk_get_port
tb_found:
if (!hlist_empty(&tb->owners)) {
if (tb->fastreuse > 0 &&
sk->sk_reuse && sk->sk_state != TCP_LISTEN &&
smallest_size == -1) {
goto success;
这里会首先进行端口的搜索,如果搜索到,看看端口是不是被设置为可以reuse,如果是可以服用,那么就可以再次绑定,否则就返回错误。从这个函数还可以看到,bind的时候端口号同样可以为零,这一点比较有趣。如果不为零,则简单的同一类型的所有socket进行端口号的直接比较。
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
所以当分配一个cache的时候,事实上已经同时分配了一个内核的socket结构。