在上一篇文章中我们讲到,connect方法会发送syn消息给服务端,之后客户端会进入TCP_SYN_SENT状态。
这就是tcp三次握手的第一步。
接下来我们看下,服务端收到syn消息后的处理逻辑。
首先,ip层在收到消息后,会调用tcp_v4_rcv方法将消息转给tcp层。
// net/ipv4/tcp_ipv4.c
int tcp_v4_rcv(struct sk_buff *skb)
{
...
const struct iphdr *iph;
const struct tcphdr *th;
...
struct sock *sk;
int ret;
...
th = (const struct tcphdr *)skb->data;
iph = ip_hdr(skb);
...
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, sdif, &refcounted);
...
if (sk->sk_state == TCP_LISTEN) {
ret = tcp_v4_do_rcv(sk, skb);
goto put_and_return;
}
...
return ret;
...
}
方法描述
1. 调用__inet_lookup_skb方法,根据ip、端口等信息在全局的hashtable中找到对应的sock。
2. 此时sk->sk_state应该是TCP_LISTEN状态,所以会继续调用tcp_v4_do_rcv方法。
// net/ipv4/tcp_ipv4.c
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
...
if (tcp_rcv_state_process(sk, skb)) {
...
goto reset;
}
return 0;
...
}
EXPORT_SYMBOL(tcp_v4_do_rcv);
该方法会继续调用tcp_rcv_state_process方法。
// net/ipv4/tcp_input.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcphdr *th = tcp_hdr(skb);
...
switch (sk->sk_state) {
...
case TCP_LISTEN:
...
if (th->syn) {
...
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
...
return 0;
}
...
}
...
}
EXPORT_SYMBOL(tcp_rcv_state_process);
因为客户端发来的是syn消息,而服务端此时是TCP_LISTEN状态,所以该方法最终会调用icsk->icsk_af_ops->conn_request(sk, skb)方法。
由第一篇文章我们可以知道,icsk->icsk_af_ops字段的值为&ipv4_specific,所以icsk->icsk_af_ops->conn_request指向的方法为tcp_v4_conn_request。
// net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
...
return tcp_conn_request(&tcp_request_sock_ops,
&tcp_request_sock_ipv4_ops, sk, skb);
...
}
EXPORT_SYMBOL(tcp_v4_conn_request);
该方法又调用了tcp_conn_request方法,其中前两个参数为两个结构体,内容如下
// net/ipv4/tcp_ipv4.c
struct request_sock_ops tcp_request_sock_ops __read_mostly = {
.family = PF_INET,
.obj_size = sizeof(struct tcp_request_sock),
.rtx_syn_ack = tcp_rtx_synack,
.send_ack = tcp_v4_reqsk_send_ack,
.destructor = tcp_v4_reqsk_destructor,
.send_reset = tcp_v4_send_reset,
.syn_ack_timeout = tcp_syn_ack_timeout,
};
static const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = {
.mss_clamp = TCP_MSS_DEFAULT,
#ifdef CONFIG_TCP_MD5SIG
.req_md5_lookup = tcp_v4_md5_lookup,
.calc_md5_hash = tcp_v4_md5_hash_skb,
#endif
.init_req = tcp_v4_init_req,
#ifdef CONFIG_SYN_COOKIES
.cookie_init_seq = cookie_v4_init_sequence,
#endif
.route_req = tcp_v4_route_req,
.init_seq = tcp_v4_init_seq,
.init_ts_off = tcp_v4_init_ts_off,
.send_synack = tcp_v4_send_synack,
};
tcp_conn_request方法如下
// net/ipv4/tcp_input.c
int tcp_conn_request(struct request_sock_ops *rsk_ops,
const struct tcp_request_sock_ops *af_ops,
struct sock *sk, struct sk_buff *skb)
{
...
struct request_sock *req;
...
req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
...
tcp_rsk(req)->af_specific = af_ops;
...
if (fastopen_sk) {
...
} else {
...
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req,
tcp_timeout_init((struct sock *)req));
af_ops->send_synack(sk, dst, &fl, req, &foc,
!want_cookie ? TCP_SYNACK_NORMAL :
TCP_SYNACK_COOKIE);
...
}
...
return 0;
...
}
EXPORT_SYMBOL(tcp_conn_request);
方法描述
1. 调用inet_reqsk_alloc方法,分配并初始化一个struct request_sock实例,用于存储连接请求数据。
2. 将tcp_rsk(req)->af_specific的值设置为af_ops,即&tcp_request_sock_ipv4_ops。
3. 调用inet_csk_reqsk_queue_hash_add方法,将连接请求实例req添加到全局hashtable中。
4. 调用af_ops->send_synack发送synack消息给客户端。
我们再来看下inet_reqsk_alloc方法
// net/ipv4/tcp_input.c
struct request_sock *inet_reqsk_alloc(const struct request_sock_ops *ops,
struct sock *sk_listener,
bool attach_listener)
{
struct request_sock *req = reqsk_alloc(ops, sk_listener,
attach_listener);
if (req) {
struct inet_request_sock *ireq = inet_rsk(req);
...
ireq->ireq_state = TCP_NEW_SYN_RECV;
...
}
return req;
}
EXPORT_SYMBOL(inet_reqsk_alloc);
该方法中要注意的就是,ireq->ireq_state的值被设置为TCP_NEW_SYN_RECV,这个后面会用到。
该方法有调用了reqsk_alloc方法,继续看下。
// include/net/request_sock.h
static inline struct request_sock *
reqsk_alloc(const struct request_sock_ops *ops, struct sock *sk_listener,
bool attach_listener)
{
struct request_sock *req;
req = kmem_cache_alloc(ops->slab, GFP_ATOMIC | __GFP_NOWARN);
...
if (attach_listener) {
...
req->rsk_listener = sk_listener;
}
req->rsk_ops = ops;
req_to_sk(req)->sk_prot = sk_listener->sk_prot;
...
return req;
}
该方法需要注意的是
1. req->rsk_listener字段被设置为sk_listener,即listen sock。
2. req->rsk_ops被设置为ops,即&tcp_request_sock_ops。
到这里tcp_conn_request方法就算分析结束了。
至此,tcp三次握手的第二步结束。
我们再来看下客户端在收到synack消息时是如何处理的。
还是回到tcp_v4_rcv方法,此时sock的状态为TCP_SYN_SENT。
// net/ipv4/tcp_ipv4.c
int tcp_v4_rcv(struct sk_buff *skb)
{
...
const struct tcphdr *th;
struct sock *sk;
...
th = (const struct tcphdr *)skb->data;
...
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, sdif, &refcounted);
...
ret = 0;
if (!sock_owned_by_user(sk)) {
ret = tcp_v4_do_rcv(sk, skb);
} else if (tcp_add_backlog(sk, skb)) {
...
}
...
return ret;
...
}
方法描述
1. 调用__inet_lookup_skb方法,根据ip、端口等信息,从全局hashtable中找到对应的sock。
2. 调用tcp_v4_do_rcv方法,继续处理。
// net/ipv4/tcp_ipv4.c
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
...
if (tcp_rcv_state_process(sk, skb)) {
...
goto reset;
}
return 0;
...
}
EXPORT_SYMBOL(tcp_v4_do_rcv);
该方法又调用了tcp_rcv_state_process方法。
// net/ipv4/tcp_input.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
const struct tcphdr *th = tcp_hdr(skb);
...
switch (sk->sk_state) {
...
case TCP_SYN_SENT:
...
queued = tcp_rcv_synsent_state_process(sk, skb, th);
...
return 0;
}
...
}
EXPORT_SYMBOL(tcp_rcv_state_process);
因为客户端此时的状态为TCP_SYN_SENT,所以该方法最终会调用tcp_rcv_synsent_state_process方法。
// net/ipv4/tcp_input.c
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
struct inet_connection_sock *icsk = inet_csk(sk);
...
if (th->ack) {
...
if (!th->syn)
goto discard_and_undo;
...
tcp_finish_connect(sk, skb);
...
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk);
...
}
...
if (sk->sk_write_pending ||
icsk->icsk_accept_queue.rskq_defer_accept ||
icsk->icsk_ack.pingpong) {
...
} else {
tcp_send_ack(sk);
}
return -1;
}
...
}
方法描述
1. 如果此ack消息里没有syn标志,则丢弃消息。
2. 调用tcp_finish_connect方法,将sk->sk_state设置为TCP_ESTABLISHED,表示tcp建立连接成功。
// net/ipv4/tcp_input.c
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
...
tcp_set_state(sk, TCP_ESTABLISHED);
...
}
3. 调用sk->sk_state_change方法,通知sock状态发生变化。
在上一篇文章中我们讲到,如果socket没设置nonblock状态,则当connect时,会阻塞等待状态变化,而这里的sk->sk_state_change方法,正是用于告知等待线程sock状态变化了,可以从阻塞状态退出了。
4. 调用tcp_send_ack方法发送三次握手的最终的ack消息。
我们再来看下,服务端在收到这个ack消息后是如何处理的。
还是看tcp_v4_rcv方法
// net/ipv4/tcp_ipv4.c
int tcp_v4_rcv(struct sk_buff *skb)
{
...
const struct tcphdr *th;
...
struct sock *sk;
...
th = (const struct tcphdr *)skb->data;
...
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, sdif, &refcounted);
...
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk);
struct sock *nsk;
sk = req->rsk_listener;
...
nsk = NULL;
if (!tcp_filter(sk, skb)) {
...
nsk = tcp_check_req(sk, skb, req, false);
}
...
if (nsk == sk) {
...
} else if (tcp_child_process(sk, nsk, skb)) {
...
} else {
...
return 0;
}
}
...
}
方法描述
1. 调用__inet_lookup_skb方法,根据ip、端口等信息找到对应的sock。
2. 因为sk->sk_state为TCP_NEW_SYN_RECV,所以将sk转成struct request_sock类型,并赋值给req。
3. 将sk值设置为req->rsk_listener,即listen sock。
4. 调用tcp_check_req方法,创建一个新的struct sock实例newsk,把newsk->sk_state状态设置为TCP_SYN_RECV,之后把struct request_sock实例中的数据赋值到newsk中,再之后把newsk放到全局的hashtable中,最后把newsk放到listen sock的icsk_accept_queue队列中,这样后面的accept方法就能从这个队列获取这个sock了。
由于涉及到的代码太多,这里就不贴代码了,只说下相关方法
// net/ipv4/tcp_ipv4.c tcp_v4_syn_recv_sock // net/ipv4/inet_connection_sock.c inet_csk_complete_hashdance
5. 调用tcp_child_process将nsk->sk_state设置为TCP_ESTABLISHED,表示连接建立成功。
// net/ipv4/tcp_minisocks.c
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);
...
} else {
...
}
...
return ret;
}
EXPORT_SYMBOL(tcp_child_process);
该方法调用了tcp_rcv_state_process方法,此时参数child的sk_state为TCP_SYN_RECV。
// net/ipv4/tcp_input.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
switch (sk->sk_state) {
case TCP_SYN_RECV:
...
tcp_set_state(sk, TCP_ESTABLISHED);
sk->sk_state_change(sk);
...
break;
...
}
...
return 0;
}
EXPORT_SYMBOL(tcp_rcv_state_process);
6. return 0 给上层,表示成功。
至此,tcp三次握手全部结束,tcp连接建立成功。
完。
本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!