Linux tcp被动打开内核源码分析

【我是从2个角度来看,其实所谓2个角度,是发现我分析源码时,分析重复了,写了2个分析报告,所以现在都贴出来。】

【如果你是想看看,了解一下内核tcp被动打开时如何实现的话,我觉得还是看看概念就可以了,因为即使看了源码,过一个个礼拜你就忘了,如果是你正在修改协议栈,为不知道流程而发愁,那么希望你能看看源码以及注释,希望你给你帮助。】

概念

tcp被动打开,前提是你listen,这个被动打开的前提。你listen过后,其实创建了一个监听套接字,专门负责监听,不会负责传输数据。

当第一个syn包到达你的服务器时,其实linux 内核并不会创建sock结构体,而是创建一个轻量级的request_sock结构体,里面能唯一确定某个客户端发来的syn的信息,接着我们就发送syn、ack给客户端,对的,服务器就做了这2个动作,1:建立request_sock,2:回复syn、ack。

客户端肯定接着回ack,这时,我们能从ack中,取出信息,在一堆request_sock匹配,看看是否之前有这个ack对应的syn发过来过。如果之前发过syn,那么现在我们就能找到request_sock,也就是客户端syn时建立的request_sock。此时,我们内核才会为这条流创建sock结构体,毕竟,sock结构体比request_sock大的多,犯不着三次握手都没建立起来我就建立一个大的结构体吧,三次握手没建立起来我就建立一个轻量级的request_sock,当三次握手建立以后,我就建立一个相对完整的sock,所谓相对完整,其实也是不完整,因为如果你写过socket程序你就知道,所谓的真正完整,是建立socket,而不是sock(socket结构体中有一个指针sock
* sk,显然sock只是socket的一个子集)。那么我们什么时候才会创建完整的socket,或者换句话说,什么时候使得sock结构体和文件系统关联从而绑定一个fd,用这个fd就可以用来传输数据呢?

如果你有socket编程经验,那么你一定能想到,那就是在accetp系统调用时,返回了一个fd,所以说,是你在accept时,你三次握手完成后建立的sock才绑定了一个 fd。

角度一:

所谓被动打开,前提是你的服务器listen后,才算被动打开。所以我们从listen系统调用开始。

sys_listen->inet_listen

直接从inet_listen()开始。

int inet_listen(struct socket *sock, int backlog)

{

struct sock *sk = sock->sk;

unsigned char old_state;

int err;

lock_sock(sk);

err = -EINVAL;

//检测socket状态,inet_create 时,初始化状态为SS_UNCONNECTED 。

if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)

goto out;

//检测sock状态

old_state = sk->sk_state;

if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))

goto out;

/* Really, if the socket is already in listen state

* we can only allow the backlog to be adjusted.

*/

//注意:listen系统调用,最主要是这个函数inet_csk_listen_start

if (old_state != TCP_LISTEN) {

err = inet_csk_listen_start(sk, backlog);

...........

}

现在我们来看看inet_csk_listen_start

int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)

{

struct inet_sock *inet = inet_sk(sk);

struct inet_connection_sock *icsk = inet_csk(sk);

//重点:为inet_connection_sock结构体 中的 icsk_accept_queue结构体中listen_sock结构体指针,其指定一个监听队列

//具体的来说,你服务器某一个进程,肯定会listen一个端口,这是一个专门用来listen的套接字,是的专门用来监听,不进行数据传输。

//某个进程肯定单独有一个socket结构,即sock结构,如果程序是当服务器使用,那么肯定listen,你一listen

//就在sock结构体中,新建一个listen_sock结构体,里面是一个散列表,散列着你收到的syn包。

//当服务器接被动收到syn时,怎么判断这个syn是否需要呢?当然遍历你不同sock的listen_sock。

//当你的第一个syn到来时,listen_sock里面当然是空的,然后把这个syn信息放到request_sock结构体中,然后request_sock挂到listen_sock上,

//然后服务器回syn、ack,然后完事了。接着这个客户端回发送ack,我们接着从listen_sock里面找,.

//发现找打了它对应的syn的request_sock,ok,

//我们就给他创建一个sock结构体,然后把listen_sock中的request_sock摘链,放到icsk_accept_queue中的rskq_accetp_head队列中,

//等待accept系统调用。当服务器一accept,就

//从accetp队列中取出sock,然后和文件系统关联(就是确定其fd),这样,用户态就能用这个fd,进行数据传输了。

int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);

if (rc != 0)

return rc;

//清除连接数

sk->sk_max_ack_backlog = 0;

sk->sk_ack_backlog = 0;

inet_csk_delack_init(sk);

/* There is race window here: we announce ourselves listening,

* but this transition is still not validated by get_port().

* It is OK, because this socket enters to hash table only

* after validation is complete.

*/

//sock状态置成TCP_LISTEN

sk->sk_state = TCP_LISTEN;

//绑定端口

if (!sk->sk_prot->get_port(sk, inet->num)) {

inet->sport = htons(inet->num);

sk_dst_reset(sk);

//把sock加入到散列桶listening_hash

sk->sk_prot->hash(sk);

#if defined(CONFIG_KSSL) && defined(CONFIG_KSSL_THREAD)

if (sk->ssl)

sk->sk_socket->thread_id = kernel_thread(ssl_handle_accept, (void *)sk,

CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND);

#endif

return 0;

}

sk->sk_state = TCP_CLOSE;

__reqsk_queue_destroy(&icsk->icsk_accept_queue);

return -EADDRINUSE;

}

现在我们来看看reqsk_queue_alloc

struct listen_sock {

u8 max_qlen_log;

/* 3 bytes hole, try to use */

int qlen;

int qlen_young;

int clock_hand;

u32 hash_rnd;

u32 nr_table_entries;

struct request_sock *syn_table[0];

};

syn_table是动态开辟的。

int reqsk_queue_alloc(struct request_sock_queue *queue,

unsigned int nr_table_entries)

{

size_t lopt_size = sizeof(struct listen_sock);

struct listen_sock *lopt;

//计算合理的listen_sock参数,用以动态开辟syn_table[0]。

//sysctl_max_syn_backlog:系统剩余未完成三次握手的请求数量

nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);

nr_table_entries = max_t(u32, nr_table_entries, 8);

//确保lopt_size 大小是2^n,这样容易按页来分配内存

nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);

lopt_size += nr_table_entries * sizeof(struct request_sock *);

//根据大小(大于还是小于一个页),用不同方式开辟内存空间

if (lopt_size > PAGE_SIZE)

lopt = __vmalloc(lopt_size,

GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,

PAGE_KERNEL);

else

lopt = kzalloc(lopt_size, GFP_KERNEL);

if (lopt == NULL)

return -ENOMEM;

for (lopt->max_qlen_log = 3;

(1 << lopt->max_qlen_log) < nr_table_entries;

lopt->max_qlen_log++);

get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));

rwlock_init(&queue->syn_wait_lock);

queue->rskq_accept_head = NULL;

lopt->nr_table_entries = nr_table_entries;

write_lock_bh(&queue->syn_wait_lock);

//指针指向listen_sock

queue->listen_opt = lopt;

write_unlock_bh(&queue->syn_wait_lock);

return 0;

}

至此listen_sock开辟完成。其中

struct listen_sock {

u8 max_qlen_log;

/* 3 bytes hole, try to use */

int qlen;

int qlen_young;

int clock_hand;

u32 hash_rnd;

u32 nr_table_entries;

struct request_sock *syn_table[0];

};

我们动态开辟了syn_table

客户端第一个SYN包

首先,当客户端syn到来时,我们肯定在__inet_lookup_listener中找到相应的sock结构体。

然后进入tcp_v4_do_rcv函数,tcp_v4_do_rcv判断 

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

return 0;

}

}

的确我们的sk->sk_state 状态的确是TCP_LISTEN,不记得的话回头看看inet_csk_listen_start函数。

我们会进入tcp_v4_hnd_req 函数

static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)

{

struct tcphdr *th = skb->h.th;

struct iphdr *iph = skb->nh.iph;

struct sock *nsk;

struct request_sock **prev;

/* Find possible connection requests. */

//找啊找啊找啊找

struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,

iph->saddr, iph->daddr);

if (req)

return tcp_check_req(sk, skb, req, prev);

nsk = inet_lookup_established(sock_vrf(sk), sock_litevrf_id(sk), &tcp_hashinfo, skb->nh.iph->saddr,

th->source, skb->nh.iph->daddr,

th->dest, inet_iif(skb));

if (nsk) {

if (nsk->sk_state != TCP_TIME_WAIT) {

bh_lock_sock(nsk);

return nsk;

}

inet_twsk_put(inet_twsk(nsk));

return NULL;

}

#ifdef CONFIG_SYN_COOKIES

if (!th->rst && !th->syn && th->ack)

sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));

#endif

return sk;

}

struct request_sock *inet_csk_search_req(const struct sock *sk,

struct request_sock ***prevp,

const __be16 rport, const __be32 raddr,

const __be32 laddr)

{

const struct inet_connection_sock *icsk = inet_csk(sk);

// icsk->icsk_accept_queue.listen_opt是我们listen初始化时指向的结构体

 struct listen_sock *lopt = icsk->icsk_accept_queue.listen_opt;

 struct request_sock *req, **prev;

//如果是客户端的第一个syn包(即被动打开),我们当然屁都找不到,返回req,其值是null( (req = *prev) != NULL)

//我们再回到上一个函数tcp_v4_hnd_req

for (prev = &lopt->syn_table[inet_synq_hash(raddr, rport, lopt->hash_rnd,

lopt->nr_table_entries)];

(req = *prev) != NULL;

prev = &req->dl_next) {

const struct inet_request_sock *ireq = inet_rsk(req);

if (ireq->rmt_port == rport &&

ireq->rmt_addr == raddr &&

.................................

return req;

}

tcp_v4_hnd_req:

if (req)

return tcp_check_req(sk, skb, req, prev);

显然返回了null,这个判断条件不满足。然后走啊走啊走啊走就return sk;这个sk就是传参传进来的sk,我们直接返回了、

在tcp_v4_do_rcv中

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk =tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

//显然nsk是等于sk的,因为我们刚才直接返回了传参进去的sk。

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

return 0;

}

}

所以接着下来执行tcp_v4_do_rcv,其调用了tcp_rcv_state_process,我们显然走这个分支:

caseTCP_LISTEN:

if(th->ack)

return 1;

if(th->rst)

goto discard;

if(th->syn) {

if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)

return 1;

conn_request被初始化为:tcp_v4_conn_request,所以我们看看tcp_v4_conn_request函数。

简单的来说, tcp_v4_conn_request,发送了syn,ack,接着创建了一个request_sock,挂在syn_table[i]中的链表中。

这个过程在tcp_v4_conn_request函数的最后,在inet_csk_reqsk_queue_hash_add中完成。

至此,第一个syn包接收完成。

客户端回ACK包

我们还是回到tcp_v4_rcv函数中

显然,established的哈希表中找不到sk,listen哈希表中能找到sk,状态是TCP_LISTEN。

进tcp_v4_do_rcv

老样子,这个分支:

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

return 0;

}

}

这个分支我们之前见过了,不过和之前的流程不一样的是,tcp_v4_hnd_req返回的是新的sk,并非传参传进去的sk(上次第一个syn报文过来,由于没有找到request_sock,所以tcp_v4_hnd_req返回原先的sk),而这个新的sk,就是完成三次握手后传输用的sk。如果你写过socket编程,那么这个就是accept返回的socket fd,专门用于传输数据。现在我们来看看新的sk如何被创建。

struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)

{

struct tcphdr *th = skb->h.th;

struct iphdr *iph = skb->nh.iph;

struct sock *nsk;

struct request_sock **prev;

/* Find possible connection requests. */

//这次,req不再是null,而是上次syn过来时创建的req。

struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,

iph->saddr, iph->daddr);

//显然,满足条件,我们调用tcp_check_req

if (req)

return tcp_check_req(sk, skb, req, prev);

................................

}

让我们来看看tcp_check_req是干嘛的?

粗略的说,他就是返回一个新建的sk。

struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,

struct request_sock *req,

struct request_sock **prev)

{

.......................

........................

/* OK, ACK is valid, create big socket and

* feed this segment to it. It will repeat all

* the tests. THIS SEGMENT MUST MOVE SOCKET TO

* ESTABLISHED STATE. If it will be dropped after

* socket is created, wait for troubles.

*/

//前面就是一些验证,我们不需要关心

child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,

req, NULL);

if (child == NULL)

goto listen_overflow;

}

syn_recv_sock 被初始化为 tcp_v4_syn_recv_sock

/*

* The three way handshake has completed - we got a valid synack -

* now create the new socket.

*/

struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,

struct request_sock *req,

struct dst_entry *dst)

{

struct inet_request_sock *ireq;

struct inet_sock *newinet;

struct tcp_sock *newtp;

struct sock *newsk;

if (sk_acceptq_is_full(sk))

goto exit_overflow;

if (!dst && (dst = inet_csk_route_req(sk, req)) == NULL)

goto exit;

//新建sk,并且初始化tcp_sock域的一些值

newsk = tcp_create_openreq_child(sk, req, skb);

if (!newsk)

goto exit;

................

..................

//把newsk加入到ehash散列桶中,然后就返回了,回到函数tcp_v4_hnd_req

__inet_hash_nolisten(newsk);

__inet_inherit_port(sk, newsk);

return newsk;

exit_overflow:

....................

}

我们会带到了函数tcp_v4_hnd_req中,

 tcp_v4_hnd_req 调用 inet_csk_reqsk_queue_add(sk, req, child); 

强调一点,sk是监听sock,req是监听时,收到syn后,创建的request_sock,挂在icsk(sk的inet_connect_sock域)里,child是我们新创建的sk,

执行inet_csk_reqsk_queue_add前,child仅仅被加入到了ehash中,和sk,req 没有任何关系,so,当你socket层调用accept,虽然传参传入了listen的sk,但你又怎么知道listen sk对应的传输sk呢?

所以inet_csk_reqsk_queue_add就是来完成listen sk 和传输用的sk(child)之间的联系:

static inline void inet_csk_reqsk_queue_add(struct sock *sk,

struct request_sock *req,

struct sock *child)

{

reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);

}

//parent指的是listen 的sk,child指的是新建的sk

//把child挂在req上,req挂到listen sk 的accept队列上。这样三者就建立起了关系。

static inline void reqsk_queue_add(struct request_sock_queue *queue,

struct request_sock *req,

struct sock *parent,

struct sock *child)

{

req->sk = child;

sk_acceptq_added(parent);

if (queue->rskq_accept_head == NULL)

queue->rskq_accept_head = req;

else

queue->rskq_accept_tail->dl_next = req;

queue->rskq_accept_tail = req;

req->dl_next = NULL;

}

至此,新的sk,即child,也即传输用的sk创建完毕。我们一路返回到tcp_v4_do_rcv。

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

//肯定不相等了,因为nsk是我新建的child。显然执行tcp_child_processs

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

int tcp_child_process(struct sock *parent, struct sock *child,

struct sk_buff *skb)

{

int ret = 0;

int state = child->sk_state;

if (!sock_owned_by_user(child)) {

//注意,此时tcp_rcv_state_process第一个参数是child,其状态是初始化的TCP_SYN_RECV。

ret = tcp_rcv_state_process(child, skb, skb->h.th, skb->len);

/* Wakeup parent, send SIGIO */

if (state == TCP_SYN_RECV && child->sk_state != state)

parent->sk_data_ready(parent, 0);

} else............

return ret;

}

又见tcp_rcv_state_process

但是和之前的流程不一样,这次,客户端给我们发了sck,并且child的状态是syn_recv,所以走这个分支:

if (th->ack) {

int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);

switch(sk->sk_state) {

case TCP_SYN_RECV:

if (acceptable) {

tp->copied_seq = tp->rcv_nxt;

smp_mb();

//至此,三次握手结束。

tcp_set_state(sk, TCP_ESTABLISHED);

sk->sk_state_change(sk);

...............................................

角度二:

/* The socket must have it‘s spinlock held when we get

* here.

*

* We have a potential double-lock case here, so even when

* doing backlog processing we use the BH locking scheme.

* This is because we cannot sleep with the original spinlock

* held.

*/

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)

{

struct sock *rsk;

#ifdef CONFIG_TCP_MD5SIG

/*

* We really want to reject the packet as early as possible

* if:

* o We‘re expecting an MD5‘d packet and this is no MD5 tcp option

* o There is an MD5 option and we‘re not expecting one

*/

if (tcp_v4_inbound_md5_hash(sk, skb))

goto discard;

#endif

if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */

TCP_CHECK_TIMER(sk);

if (tcp_rcv_established(sk, skb, skb->h.th, skb->len)) {

rsk = sk;

goto reset;

}

TCP_CHECK_TIMER(sk);

return 0;

}

if (skb->len < (skb->h.th->doff << 2) || tcp_checksum_complete(skb))

goto csum_err;

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

return 0;

}

}

TCP_CHECK_TIMER(sk);

if (tcp_rcv_state_process(sk, skb, skb->h.th, skb->len)) {

rsk = sk;

goto reset;

}

TCP_CHECK_TIMER(sk);

return 0;

reset:

tcp_v4_send_reset(rsk, skb);

discard:

kfree_skb(skb);

/* Be careful here. If this function gets more complicated and

* gcc suffers from register pressure on the x86, sk (in %ebx)

* might be destroyed here. This current version compiles correctly,

* but you have been warned.

*/

return 0;

csum_err:

TCP_INC_STATS_BH(sock_vrf(sk), TCP_MIB_INERRS);

goto discard;

}

tcp_v4_do_rcv 的第一个参数sk,可能是listen 的sk,其状态肯定一直是listen。

第一个参数也可能是established的sk。

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

return 0;

}

}

一般都是处理被动三次握手的流程。

因为当调用socket listen 系统调用时,就把一个监听套接字放到了监听队列的哈希表中。

当新客户端访问服务器,只能在listen的哈希表中找到sk,其状态永远是listen。

当第一次的syn过来时,tcp_v4_hnd_req 返回listen的sk,所以只能进:

if (tcp_rcv_state_process(sk, skb, skb->h.th, skb->len)) {

rsk = sk;

goto reset;

}

流程,在tcp_rcv_state_process中,判断sk(仍然是listen的sk)的状态,显然是Listen状态,执行tcp_v4_conn_request,此函数会建立一个request_sock结构体,挂在listen的sk上。然后给客户端发送syn,ack。

客户端回一个ack。此时tcp_v4_do_rcv中的第一个参数还是listen的sk,因为上次的syn包我们根本没去建立sock,而是建立一个挂在listen的sk的request_sock,我们肯定不能在established的哈希表中找到sk,所以只能在listen哈希表中找到sk。

这是还是进这个流程。

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {

if (tcp_child_process(sk, nsk, skb)) {

rsk = nsk;

goto reset;

}

return 0;

}

}

我们先看看:tcp_v4_hnd_req

static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)

{

struct tcphdr *th = skb->h.th;

struct iphdr *iph = skb->nh.iph;

struct sock *nsk;

struct request_sock **prev;

/* Find possible connection requests. */

struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,

iph->saddr, iph->daddr);          //第一个syn包进来肯定找不到request_sock ,所以在tcp_v4_conn_request中建立一个request_sock ,并且插入

//当ack回来时,肯定能查到,也就是在tcp_v4_conn_request中建立的request_sock 

if (req)

return tcp_check_req(sk, skb, req, prev);
//是ack回来时,肯定满足if (req) ,因为查到req了嘛!tcp_check_req函数很重要。我们下里面就来讲讲这个函数。

nsk = inet_lookup_established(sock_vrf(sk), sock_litevrf_id(sk), &tcp_hashinfo, skb->nh.iph->saddr,

th->source, skb->nh.iph->daddr,

th->dest, inet_iif(skb));

if (nsk) {

if (nsk->sk_state != TCP_TIME_WAIT) {

bh_lock_sock(nsk);

return nsk;

}

inet_twsk_put(inet_twsk(nsk));

return NULL;

}

#ifdef CONFIG_SYN_COOKIES

if (!th->rst && !th->syn && th->ack)

sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));

#endif

return sk;

}

struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,

struct request_sock *req,

struct request_sock **prev)

{

//一些验证

/* OK, ACK is valid, create big socket and

* feed this segment to it. It will repeat all

* the tests. THIS SEGMENT MUST MOVE SOCKET TO

* ESTABLISHED STATE. If it will be dropped after

* socket is created, wait for troubles.

*/ //看到这段话你就知道前面就是invalid的ack,所以我直接跳过了前面的。

child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,

req, NULL);                                                         //syn_recv_sock中会创建将来客户端和你服务器交互传递数据的sock结构体!就是这个!

                                                                                    //注意他叫child,是sock结构提。刚创建的child状态是TCP_SYN_RECV,inet_csk_clone中被赋值。

if (child == NULL)

goto listen_overflow;

#ifdef CONFIG_TCP_MD5SIG

else {

/* Copy over the MD5 key from the original socket */

struct tcp_md5sig_key *key;

struct tcp_sock *tp = tcp_sk(sk);

key = tp->af_specific->md5_lookup(sk, child);

if (key != NULL) {

/*

* We‘re using one, so create a matching key on the

* newsk structure. If we fail to get memory then we

* end up not copying the key across. Shucks.

*/

char *newkey = kmemdup(key->key, key->keylen,

GFP_ATOMIC);

if (newkey) {

if (!tcp_alloc_md5sig_pool())

BUG();

tp->af_specific->md5_add(child, child,

newkey,

key->keylen);

}

}

}

#endif

//既然建立了child,那么之前的request_sock就没用了~,我们做掉它。

//然后把child挂到listen的accetp队列里面去。 

inet_csk_reqsk_queue_unlink(sk, req, prev);

inet_csk_reqsk_queue_removed(sk, req);

inet_csk_reqsk_queue_add(sk, req, child);

return child;

//把child一路返回,直接返回到tcp_v4_do_rcv中了,我们再看看tcp_v4_do_rcv。

}

if (sk->sk_state == TCP_LISTEN) {

struct sock *nsk = tcp_v4_hnd_req(sk, skb);

if (!nsk)

goto discard;

if (nsk != sk) {                                            //ack来时,返回的是我新建的sock,肯定跟listen的sk不一样。为什么不一样,因为是刚刚新建的,名字就是child

if (tcp_child_process(sk, nsk, skb)) {        //所以也不奇怪为什么这里函数名叫tcp_child_process,因为概念上来说,tcp_child_process第二个阐述就是之前的child

rsk = nsk;

goto reset;

}

//parent是listen 的 sk,child是挂在listen sk 中的accept队列。 

int tcp_child_process(struct sock *parent, struct sock *child,

struct sk_buff *skb)

{

int ret = 0;

int state = child->sk_state;

if (!sock_owned_by_user(child)) {

ret = tcp_rcv_state_process(child, skb, skb->h.th, skb->len);

/* Wakeup parent, send SIGIO */

if (state == TCP_SYN_RECV && child->sk_state != state)

parent->sk_data_ready(parent, 0);

} else {

/* Alas, it is possible again, because we do lookup

* in main socket hash table and lock on listening

* socket does not protect us more.

*/

sk_add_backlog(child, skb);

}

bh_unlock_sock(child);

sock_put(child);

return ret;

}

然后对这个child(child是在listen的sk中的accept队列)调用tcp_rcv_state_process,并不是对listen的sk处理了,这个要注意了。

在tcp_rcv_state_process中,我走这个分支:

/* step 5: check the ACK field */

if (th->ack) {

int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);

switch(sk->sk_state) {

case TCP_SYN_RECV:

if (acceptable) {

tp->copied_seq = tp->rcv_nxt;

smp_mb();

tcp_set_state(sk, TCP_ESTABLISHED);

sk->sk_state_change(sk);

/* Note, that this wakeup is only for marginal

* crossed SYN case. Passively open sockets

* are not waked up, because sk->sk_sleep ==

* NULL and sk->sk_socket == NULL.

*/

if (sk->sk_socket) {

sk_wake_async(sk,0,POLL_OUT);

}

tp->snd_una = TCP_SKB_CB(skb)->ack_seq;

tp->snd_wnd = ntohs(th->window) <<

tp->rx_opt.snd_wscale;

tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,

TCP_SKB_CB(skb)->seq);

/* tcp_ack considers this ACK as duplicate

* and does not calculate rtt.

* Fix it at least with timestamps.

*/

if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&

!tp->srtt)

tcp_ack_saw_tstamp(sk, 0);

if (tp->rx_opt.tstamp_ok)

tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;

/* Make sure socket is routed, for

* correct metrics.

*/

icsk->icsk_af_ops->rebuild_header(sk);

tcp_init_metrics(sk);

tcp_init_congestion_control(sk);

/* Prevent spurious tcp_cwnd_restart() on

* first data packet.

*/

tp->lsndtime = tcp_time_stamp;

tcp_mtup_init(sk);

tcp_initialize_rcv_mss(sk);

tcp_init_buffer_space(sk);

tcp_fast_path_on(tp);

} else {

return 1;

}

break;

Linux tcp被动打开内核源码分析,码迷,mamicode.com

时间: 2024-10-07 04:53:19

Linux tcp被动打开内核源码分析的相关文章

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938396.html 在基本分析完内核启动流程的之后,还有一个比较重要的初始化函数没有分析,那就是do_basic_setup.在内核init线程中调用了do_basic_setup,这个函数也做了很多内核和驱动的初始化工作,详解

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938393.html 在分析start_kernel函数的时候,其中有构架相关的初始化函数setup_arch. 此函数根据构架而异,对于ARM构架的详细分析如下: void __init setup_arch(char **cmdlin

Linux内核源码分析方法

  一.内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径.我们都知道,想成为优秀的程序员,需要大量的实践和代码的编写.编程固然重要,但是往往只编程的人很容易把自己局限在自己的知识领域内.如果要扩展自己知识的广度,我们需要多接触其他人编写的代码,尤其是水平比我们更高的人编写的代码.通过这种途径,我们可以跳出自己知识圈的束缚,进入他人的知识圈,了解更多甚至我们一

ARMv8 Linux内核源码分析:__flush_dcache_all()

1.1 /* *  __flush_dcache_all() *  Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY(__flush_dcache_all) //保证之前的访存指令的顺序 dsb sy //读cache level id register mrs x0, clidr_el1           // read clidr //取bits[26:24](Level of Coherency f

linux 内核源码分析 - 获取数组的大小

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 测试程序: #include<stdio.h> #include<stdlib.h> struct dev { int a; char b; float c; }; struct dev devs[]= { { 1,'a',7.0, }, { 1,'a',7.0, }, { 1,'a',7.0, }, }; int main() { printf("int is %d \

内核源码分析之进程地址空间(基于3.16-rc4)

所谓进程的地址空间,指的就是进程的虚拟地址空间.当创建一个进程时,内核会为该进程分配一个线性的地址空间(虚拟地址空间),有了虚拟地址空间后,内核就可以通过页表将进程的物理地址地址空间映射到其虚拟地址空间中,程序员所能看到的其实都是虚拟地址,物理地址对程序员而言是透明的.当程序运行时,MMU硬件机制会将程序中的虚拟地址转换成物理地址,然后在内存中找到指令和数据,来执行进程的代码.下面我们就来分析和进程的地址空间相关的各种数据结构和操作. 用到的数据结构: 1.内存描述符struct mm_stru

Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】

转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html 研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了解内核启动过程.我在研究内核的内存管理的时候,想知道内核启动后的页表的放置,页表的初始化等信息,这促使我这次仔细地研究内核的启动代码. CPU在bootloader的帮助下将内核载入到了内存中,并开始执行.当然,bootloader必须为zImage做好必要的准备:  1. CPU 寄存器的设置: R0