内核中的TCP的追踪分析-5-再谈TCP(IPV4)的socket的地址绑定

2020-05-25 00:00:00 函数 代码 地址 端口 端口号

今天我们继续完成bind()函数的追踪为了避免离开主线而失去方向,关于具体的初始化过程我们还是放在必要的时候逐段来看,这里还是回到第3节中的__inet_dev_addr_type()函数中继续往下追踪,函数fib_get_table()查找init_net网络空间中安装的路由表hash队列中找到要使用的路由表,然后在路由表中找到对应的地址类型,后返回到inet_addr_type()再返回到inet_bind中,下面接着是判断是否绑定了非本地的IP地址及地址类型是否正确,然后
snum = ntohs(addr->sin_port);
取得地址的端口号我们在练习中设置的是9734,检查是否小于1024及是否有绑定权限。接着
if (sk->sk_state != TCP_CLOSE || inet->num)
goto out_release_sock;
检查一下sock的状态是否处于关闭或者已经绑定端口
inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
接着把我们设定的ip地址127.0.0.1绑定给ipv4的sock结构变量inet的接收地址和起源地址。如果地址类型是多播或者是广播的话就将起源地址设置为0,表示使用设备。

if (sk->sk_prot->get_port(sk, snum)) {
inet->saddr = inet->rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
}
这里朋友们接合第2节tcp_prot结构中的钩子函数
http://blog.chinaunix.net/u2/64681/showart_1360583.html
在那里有这样一句
.get_port = inet_csk_get_port,
所以我们这里进入inet_csk_get_port函数()
int inet_csk_get_port(struct sock *sk, unsigned short snum)
{
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
struct inet_bind_hashbucket *head;
struct hlist_node *node;
struct inet_bind_bucket *tb;
int ret;
struct net *net = sock_net(sk);
local_bh_disable();
if (!snum) {
int remaining, rover, low, high;
inet_get_local_port_range(&low, &high);
remaining = (high - low) + 1;
rover = net_random() % remaining + low;
do {
head = &hashinfo->bhash[inet_bhashfn(rover, hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)
if (tb->ib_net == net && tb->port == rover)
goto next;
break;
next:
spin_unlock(&head->lock);
if (++rover > high)
rover = low;
} while (--remaining > 0);
/* Exhausted local port range during search? It is not
* possible for us to be holding one of the bind hash
* locks if this test triggers, because if 'remaining'
* drops to zero, we broke out of the do/while loop at
* the top level, not from the 'break;' statement.
*/
ret = 1;
if (remaining = 0)
goto fail;
/* OK, here is the one we will use. HEAD is
* non-NULL and we hold it's mutex.
*/
snum = rover;
} else {
head = &hashinfo->bhash[inet_bhashfn(snum, hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)
if (tb->ib_net == net && tb->port == snum)
goto tb_found;
}
tb = NULL;
goto tb_not_found;
tb_found:
if (!hlist_empty(&tb->owners)) {
if (tb->fastreuse > 0 &&
sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
goto success;
} else {
ret = 1;
if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
goto fail_unlock;
}
}
tb_not_found:
ret = 1;
if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
net, head, snum)) == NULL)
goto fail_unlock;
if (hlist_empty(&tb->owners)) {
if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)
tb->fastreuse = 1;
else
tb->fastreuse = 0;
} else if (tb->fastreuse &&
(!sk->sk_reuse || sk->sk_state == TCP_LISTEN))
tb->fastreuse = 0;
success:
if (!inet_csk(sk)->icsk_bind_hash)
inet_bind_hash(sk, tb, snum);
BUG_TRAP(inet_csk(sk)->icsk_bind_hash == tb);
ret = 0;
fail_unlock:
spin_unlock(&head->lock);
fail:
local_bh_enable();
return ret;
}
函数感觉非常复杂,我们逐层分析,上面代码中首先从struct proto tcp_prot结构中的.h.hashinfo = &tcp_hashinfo,取得tcp_hashinfo,这是个struct inet_hashinfo结构变量

struct inet_hashinfo {
/* This is for sockets with full identity only. Sockets here will
* always be without wildcards and will have the following invariant:
*
* TCP_ESTABLISHED sk_state
struct inet_ehash_bucket *ehash;
rwlock_t *ehash_locks;
unsigned int ehash_size;
unsigned int ehash_locks_mask;
/* Ok, let's try this, I give up, we do need a local binding
* TCP hash as well as the others for fast bind/connect.
*/
struct inet_bind_hashbucket *bhash;
unsigned int bhash_size;
/* Note : 4 bytes padding on 64 bit arches */
/* All sockets in TCP_LISTEN state will be in here. This is the only
* table where wildcard'd TCP sockets can exist. Hash function here
* is just local port number.
*/
struct hlist_head listening_hash[INET_LHTABLE_SIZE];
/* All the above members are written once at bootup and
* never written again _or_ are predominantly read-access.
*
* Now align to a new cache line as all the following members
* are often dirty.
*/
rwlock_t lhash_lock ____cacheline_aligned;
atomic_t lhash_users;
wait_queue_head_t lhash_wait;
struct kmem_cache *bind_bucket_cachep;
};
这个结构是为了维护tcp中的hash表使用的
struct inet_bind_hashbucket {
spinlock_t lock;
struct hlist_head chain;
};
这个数据结构是为了维护端口使用的hash表桶。我们还看到struct hlist_node *node;这个是声明的hash表的链头,还有一个数据结构是
struct inet_bind_bucket {
struct net *ib_net;
unsigned short port;
signed short fastreuse;
struct hlist_node node;
struct hlist_head owners;
};
这个是绑定端口使用的hash节点的数据结构,我们看了这几个数据结构但是不用担心是否能够记忆,只需要在代码的分析过程中来了解他的具体的作用。我们继续看inet_csk_get_port()函数,首先是使net指向我们的init_net网络空间结构,然后根据参数snum是否为空来执行一段代码,我们知道snum是从前边传递过来的端口号,我们应用程序传递过来当然不为空,我们看if代码,如果我们没有指定端口号的话,就会执行if语句的上半段代码分配一个端口号给snum,先执行inet_get_local_port_range()函数
void inet_get_local_port_range(int *low, int *high)
{
unsigned seq;
do {
seq = read_seqbegin(&sysctl_port_range_lock);
*low = sysctl_local_port_range[0];
*high = sysctl_local_port_range[1];
} while (read_seqretry(&sysctl_port_range_lock, seq));
}
其内部涉及到了一个数组sysctl_local_port_range
int sysctl_local_port_range[2] = { 32768, 61000 };
这个数组是为了端口的号的范围在32768-61000期间,然后下边根据这个范围和随机数设置一个hash用的参考值rover,inet_bhashfn()是将rover与hash表的大小进行hash,然后其结果做为hash桶表的决定值来取得对应的hash桶表struct inet_bind_hashbucket,然后赋值给head,接着进入一个for循环
inet_bind_bucket_for_each(tb, node, &head->chain)
if (tb->ib_net == net && tb->port == rover)
goto next;
inet_bind_bucket_for_each是一个宏声明
#define inet_bind_bucket_for_each(tb, node, head) \
hlist_for_each_entry(tb, node, head, node)
#define hlist_for_each_entry(tpos, pos, head, member) \
for (pos = (head)->first; \
pos && ({ prefetch(pos->next); 1;}) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
结合我们函数中的代码,这个循环的过程其实就是从我们上面得到hash桶表,循环查找是否同样的网络空间下我们要设置的端口是否已经被占用了,如果占用了就增加hash表的参考值rover,进行下一轮的检查,如果通过了这个循环就说明我们的端口还没有被占用此时,struct inet_bind_bucket局部变量指针tb指向了能够使用的hash节点。接着端口号snum就指向了这个参考值。
但是在我们的实践练习中我们指定了端口号为9734,忘记的朋友请看一下相关的文章,在我们这里的追踪会进入if语句的下半段
else {
head = &hashinfo->bhash[inet_bhashfn(snum, hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)
if (tb->ib_net == net && tb->port == snum)
goto tb_found;
}
我是无名小卒,转载请注明出处
http://qinjiana0786.cublog.cn
 尽管08年3月份才开始写博客文章,但是文章得到了大量的转载,说明我对内核的学习和付出的心血没有白费,在此分享自己对内核的知识,希望朋友们继续支持我,虽然我33岁了,但是我始终没有停止学习的脚步,30岁前我是个程序员,30岁以后我还是个程序员,执著和追求着,坚信有一天我会成功。我们继续,参考if上半段的代码,我们这里是以9734指定的端口号为参考值找到hash桶表再进一步找到,接着在这个hash桶表中找到我们要执行绑定端口节点tb。接着要跳转到tb_found处继续往下执行
tb_found:
if (!hlist_empty(&tb->owners)) {
if (tb->fastreuse > 0 &&
sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
goto success;
} else {
ret = 1;
if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
goto fail_unlock;
}
}
检查tb是否已经有了所有者以及sock的状态和允许复用情况,就跳转到success处,但是如果没有所有者的话就会执行inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb),我们暂且跳过这句代码。先围绕主线继续进行
success:
if (!inet_csk(sk)->icsk_bind_hash)
inet_bind_hash(sk, tb, snum);
BUG_TRAP(inet_csk(sk)->icsk_bind_hash == tb);
ret = 0;
看一下inet_csk是个inline函数
static inline struct inet_connection_sock *inet_csk(const struct sock *sk)
{
return (struct inet_connection_sock *)sk;
}
struct inet_connection_sock是专门用于tcp和socket相联系的一个数据结构
struct inet_connection_sock {
/* inet_sock has to be the first member! */
struct inet_sock icsk_inet;
struct request_sock_queue icsk_accept_queue;
struct inet_bind_bucket *icsk_bind_hash;
unsigned long icsk_timeout;
struct timer_list icsk_retransmit_timer;
struct timer_list icsk_delack_timer;
__u32 icsk_rto;
__u32 icsk_pmtu_cookie;
const struct tcp_congestion_ops *icsk_ca_ops;
const struct inet_connection_sock_af_ops *icsk_af_ops;
unsigned int (*icsk_sync_mss)(struct sock *sk, u32 pmtu);
__u8 icsk_ca_state;
__u8 icsk_retransmits;
__u8 icsk_pending;
__u8 icsk_backoff;
__u8 icsk_syn_retries;
__u8 icsk_probes_out;
__u16 icsk_ext_hdr_len;
struct {
__u8 pending; /* ACK is pending */
__u8 quick; /* Scheduled number of quick acks */
__u8 pingpong; /* The session is interactive */
__u8 blocked; /* Delayed ACK was blocked by socket lock */
__u32 ato; /* Predicted tick of soft clock */
unsigned long timeout; /* Currently scheduled timeout */
__u32 lrcvtime; /* timestamp of last received data packet */
__u16 last_seg_size; /* Size of last incoming segment */
__u16 rcv_mss; /* MSS used for delayed ACK decisions */
} icsk_ack;
struct {
int enabled;
/* Range of MTUs to search */
int search_high;
int search_low;
/* Information on the current probe. */
int probe_size;
} icsk_mtup;
u32 icsk_ca_priv[16];
#define ICSK_CA_PRIV_SIZE (16 * sizeof(u32))
};
我是无名小卒,转载请注明出处
http://qinjiana0786.cublog.cn
 尽管08年3月份才开始写博客文章,但是文章得到了大量的转载。继续分析,只是这里我们看到将sock直接转换为了struct inet_connection_sock指针,然后判断这个结构中是否指定了icsk_bind_hash,它是我们上面看到的struct inet_bind_bucket节点,如果没有指定就会执行inet_bind_hash函数
void inet_bind_hash(struct sock *sk, struct inet_bind_bucket *tb,
const unsigned short snum)
{
inet_sk(sk)->num = snum;
sk_add_bind_node(sk, &tb->owners);
inet_csk(sk)->icsk_bind_hash = tb;
}
这里首先是将sock的端口号指定为我们的9734,然后
static __inline__ void sk_add_bind_node(struct sock *sk,
struct hlist_head *list)
{
hlist_add_head(&sk->sk_bind_node, list);
}
将我们的sock中的sk_bind_node链入到了tb中的所有者hash表中。后将icsk_bind_hash再指定为tb。这样端口方面的工作已经在内核完成了,我们从inet_csk_get_port()返回到inet_bind()函数继续往下看
if (inet->rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
if (snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
inet->sport = htons(inet->num);
inet->daddr = 0;
inet->dport = 0;
sk_dst_reset(sk);
err = 0;
out_release_sock:
release_sock(sk);
out:
return err;
根据我们对tcp的sock的是否确定了本地接收地址和端口号来对sock的sk_userlocks用户锁标志增加相应的锁。后将tcp的sock的sport本地端口指定为我们设置的9734端口号。将目标地址daddr设为0,目标端口设为0,此后的代码就与我们在unix的socket的分析是一样了。我们不重复了

文章来源CU社区:内核中的TCP的追踪分析-5-再谈TCP(IPV4)的socket的地址绑定

相关文章