TCP被动打开 之 第一次握手-接收SYN
假定客户端执行主动打开,服务器执行被动打开,客户端发送syn包到服务器,服务器接收该包,进行建立连接请求的相关处理,即第一次握手;本文主要分析第一次握手中被动打开端的处理流程,主动打开端的处理请查阅本博客内另外的文章;
IPv4携带的TCP报文最终会进入到tcp_v4_do_rcv函数,服务器准备接收连接请求时,是处于LISTEN状态的,所以我们只关心这部分的相关处理;函数中LISTEN条件分支中,主要是对启用了syn cookies的检查,我们暂且不做分析;主要看tcp_rcv_state_process这个函数,syn连接请求最终会进入到该函数中进行处理;
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk; if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
struct dst_entry *dst = sk->sk_rx_dst; sock_rps_save_rxhash(sk, skb);
sk_mark_napi_id(sk, skb);
if (dst) {
if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif ||
!dst->ops->check(dst, )) {
dst_release(dst);
sk->sk_rx_dst = NULL;
}
}
tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len);
return ;
} if (tcp_checksum_complete(skb))
goto csum_err; /* LISTEN状态处理 */
if (sk->sk_state == TCP_LISTEN) { /* syn cookies检查 */
struct sock *nsk = tcp_v4_cookie_check(sk, skb); if (!nsk)
goto discard;
if (nsk != sk) {
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return ;
}
} else
sock_rps_save_rxhash(sk, skb); /* ESTABLISHED and TIME_WAIT状态以外的其他状态处理 */
if (tcp_rcv_state_process(sk, skb)) {
rsk = sk;
goto reset;
}
return ; 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 ; csum_err:
TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
tcp_rcv_state_process对syn包进行处理,不接收ack包,丢弃含有rst和fin的包,对于合格的syn请求包,则继续调用conn_request回调进行处理,TCPv4中对应的函数为tcp_v4_conn_request;
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
/* 省略了一些无关代码 */ switch (sk->sk_state) {
case TCP_CLOSE:
goto discard; case TCP_LISTEN:
/* 不接收ack */
if (th->ack)
return ; /* 丢弃带有rst标记的包 */
if (th->rst)
goto discard; /* 处理syn请求包 */
if (th->syn) {
/* 丢弃带有fin标志的包 */
if (th->fin)
goto discard;
/* It is possible that we process SYN packets from backlog,
* so we need to make sure to disable BH right there.
*/
local_bh_disable();
/* 进入连接请求处理 */
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= ;
local_bh_enable(); /* 连接失败 */
if (!acceptable)
return ; /* 连接成功 */
consume_skb(skb);
return ;
}
goto discard;
}
/* 省略了一些无关代码 */
}
tcp_v4_conn_request函数对传入包的路由类型进行检查,如果是发往广播或者组播的,则丢弃该包,合法包进入tcp_conn_request函数继续进行请求处理,其中参数传入了请求控制块操作函数结构指针;
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
/* Never answer to SYNs send to broadcast or multicast */
if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
goto drop; return tcp_conn_request(&tcp_request_sock_ops,
&tcp_request_sock_ipv4_ops, sk, skb); drop:
tcp_listendrop(sk);
return ;
}
tcp_conn_request函数为syn请求的核心处理流程,我们暂且忽略其中的syn cookies和fastopen相关流程,其核心功能为分析请求参数,新建连接请求控制块,注意,新建请求控制操作中会将连接状态更新为TCP_NEW_SYN_RECV ,并初始化相关成员,初始化完毕之后,加入到半连接队列accept queue中,然后恢复syn+ack包给客户端;
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 tcp_fastopen_cookie foc = { .len = - };
__u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn;
struct tcp_options_received tmp_opt;
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
struct sock *fastopen_sk = NULL;
struct dst_entry *dst = NULL;
struct request_sock *req;
bool want_cookie = false;
struct flowi fl; /* TW buckets are converted to open requests without
* limitations, they conserve resources and peer is
* evidently real one.
*/
if ((net->ipv4.sysctl_tcp_syncookies == ||
inet_csk_reqsk_queue_is_full(sk)) && !isn) {
want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
if (!want_cookie)
goto drop;
} /* 如果连接队列长度已达到上限,丢包 */
if (sk_acceptq_is_full(sk)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
} /*
分配请求控制块,请求控制块的操作指向rsk_ops ,
注意: 这个函数将连接状态更新为TCP_NEW_SYN_RECV
*/
req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
if (!req)
goto drop; /* 初始化特定操作函数 */
tcp_rsk(req)->af_specific = af_ops;
tcp_rsk(req)->ts_off = ; /* 情况保存tcp选项的相关字段 */
tcp_clear_options(&tmp_opt); /* 初始化最大mss */
tmp_opt.mss_clamp = af_ops->mss_clamp;
/* 初始化用户定义mss */
tmp_opt.user_mss = tp->rx_opt.user_mss; /* 解析tcp选项,其中会取user_mss和对端通告mss的较小值记录到mss_clamp中 */
tcp_parse_options(skb, &tmp_opt, , want_cookie ? NULL : &foc); if (want_cookie && !tmp_opt.saw_tstamp)
tcp_clear_options(&tmp_opt); /* 记录是否在syn中有时间戳选项 */
tmp_opt.tstamp_ok = tmp_opt.saw_tstamp; /* 使用对端信息对请求控制块做初始化 */
tcp_openreq_init(req, &tmp_opt, skb, sk); /* 不做源地址检查?? */
inet_rsk(req)->no_srccheck = inet_sk(sk)->transparent; /* Note: tcp_v6_init_req() might override ir_iif for link locals */
inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb); /* 初始化控制块中的目的地址,源地址,ip选项 */
af_ops->init_req(req, sk, skb); if (security_inet_conn_request(sk, skb, req))
goto drop_and_free; /* 有时间戳选项,计算时间戳偏移?? */
if (tmp_opt.tstamp_ok)
tcp_rsk(req)->ts_off = af_ops->init_ts_off(skb); /* 不需要cookie,序号未初始化 */
if (!want_cookie && !isn) {
/* Kill the following clause, if you dislike this way. */
/* 未开启cookie && 队列剩余小于队列大小的一半&& 对端验证未通过 */
if (!net->ipv4.sysctl_tcp_syncookies &&
(net->ipv4.sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
(net->ipv4.sysctl_max_syn_backlog >> )) &&
!tcp_peer_is_proven(req, dst)) {
/* Without syncookies last quarter of
* backlog is filled with destinations,
* proven to be alive.
* It means that we continue to communicate
* to destinations, already remembered
* to the moment of synflood.
*/
pr_drop_req(req, ntohs(tcp_hdr(skb)->source),
rsk_ops->family);
goto drop_and_release;
} /* 根据源目的地址和端口初始化序号 */
isn = af_ops->init_seq(skb);
} /* 没有路由要查路由 */
if (!dst) {
dst = af_ops->route_req(sk, &fl, req);
if (!dst)
goto drop_and_free;
} /* ecn 相关*/
tcp_ecn_create_request(req, skb, sk, dst); /* syn cookies相关 */
if (want_cookie) {
isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
req->cookie_ts = tmp_opt.tstamp_ok;
if (!tmp_opt.tstamp_ok)
inet_rsk(req)->ecn_ok = ;
} /* 初始化发送序号和hash */
tcp_rsk(req)->snt_isn = isn;
tcp_rsk(req)->txhash = net_tx_rndhash(); /* 窗口相关初始化todo */
tcp_openreq_init_rwin(req, sk, dst); if (!want_cookie) {
/* 记录syn包头 */
tcp_reqsk_record_syn(sk, req, skb);
fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst);
} /* fastopen相关 */
if (fastopen_sk) {
af_ops->send_synack(fastopen_sk, dst, &fl, req,
&foc, TCP_SYNACK_FASTOPEN);
/* Add the child socket directly into the accept queue */
inet_csk_reqsk_queue_add(sk, req, fastopen_sk);
sk->sk_data_ready(sk);
bh_unlock_sock(fastopen_sk);
sock_put(fastopen_sk);
} else {
/* 不是fastopen */
tcp_rsk(req)->tfo_listener = false; /* 加入ehash,启动请求重传定时器 */
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT); /* 发送syn+ack */
af_ops->send_synack(sk, dst, &fl, req, &foc,
!want_cookie ? TCP_SYNACK_NORMAL :
TCP_SYNACK_COOKIE);
if (want_cookie) {
reqsk_free(req);
return ;
}
}
reqsk_put(req);
return ; drop_and_release:
dst_release(dst);
drop_and_free:
reqsk_free(req);
drop:
tcp_listendrop(sk);
return ;
}
TCP被动打开 之 第一次握手-接收SYN的更多相关文章
- TCP主动打开 之 第一次握手-发送SYN
tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端: tcp_v4_connect为发起连接主流程,首先对必要参数进行检查 ...
- TCP主动打开 之 第二次握手-接收SYN+ACK
假设客户端执行主动打开,已经经过第一次握手,即发送SYN包到服务器,状态变为SYN_SENT,服务器收到该包后,回复SYN+ACK包,客户端收到该包,进行主动打开端的第二次握手部分:流程中涉及到的函数 ...
- TCP被动打开 之 第二次握手-发送SYN+ACK
假定客户端执行主动打开,发送syn包到服务器,服务器执行完该包的第一次握手操作后,调用af_ops->send_synack向客户端发送syn+ack包,该回调实际调用tcp_v4_send_s ...
- TCP被动打开 之 第三次握手-接收ACK
假定客户端主动打开,发送syn包到服务器,服务器创建连接请求控制块加入到队列,进入TCP_NEW_SYN_RECV 状态,发送syn+ack给客户端,并启动定时器,等待客户端回复最后一个握手ack: ...
- tcp syn-synack-ack 服务端 接收 SYN tcp_v4_do_rcv分析
rcv 分析: /* The socket must have it's spinlock held when we get * here, unless it is a TCP_LISTEN soc ...
- TCP连接建立系列 — 服务端接收SYN段
本文主要分析:服务器端接收到SYN包时的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 接收入口 1. 状态为ESTABLISHED时,用tcp_rcv_esta ...
- IP封包协议头/TCP协议头/TCP3次握手/TCP4次挥手/UDP协议头/ICMP协议头/HTTP协议(请求报文和响应报文)/IP地址/子网掩码(划分子网)/路由概念/MAC封包格式
IP协议头IP包头格式: 1.版本号:4个bit,用来标识IP版本号.这个4位字段的值设置为二进制的0100表示IPv4,设置为0110表示IPv6.目前使用的IP协议版本号是4. 2.首部长度:4个 ...
- 详解TCP连接的“三次握手”与“四次挥手”(上)
一.TCP connection 客户端与服务器之间数据的发送和返回的过程当中需要创建一个叫TCP connection的东西: 由于TCP不存在连接的概念,只存在请求和响应,请求和响应都是数据包,它 ...
- tcp协议:三次握手四次挥手详解-转
https://www.cnblogs.com/welan/p/9925119.html
随机推荐
- 客户端注册Cannot execute request on any known server解决
在对eureka注册中心服务端添加安全验证后,新版本springcloud出现一个问题就是,在客户端注册到服务中心时报了一个错:Cannot execute request on any known ...
- JS和JS是IE上JavaScript或JScript的缩写。
JS和JS是IE上JavaScript或JScript的缩写.javascript是所有浏览器的开放式标准脚本语言JScript是微软自己的开放式脚本语言标准,只有微软的IE浏览器遵循.JScript ...
- javascript&jquery方法比对
参考链接:https://juejin.im/post/5d2705d8e51d4577407b1dda 参考评论链接http://youmightnotneedjquery.com/ javascr ...
- 小程序API接口调用
1.在config.js中写入api接口及appkey 2.在HTTP.js中引入config.js,然后新建HTTP.js,在里进行wx.request的封装. 定义一个HTTP的类,来类里定义 ...
- 2.OR Mapping 介绍
定义: ORM(Object Relational Mapping) -- 是一种为了解决面向对象与关系型数据库存在的互不匹配的现象的技术. 简单说:ORM是通过使用描述对象和数据库之间的映射的元数据 ...
- Vue异步请求最佳实践
一.当前存在的问题 目前项目前端请求后台数据的方式是这样的: 页面中method中dispatch到action action调用mutation,请求axios 请求到数据后存储到state中 页面 ...
- Delphi WriteFile函数
- C语言特殊函数的应用
1. va_list相关函数的学习: va_list是一种变参量的指针类型定义. va_list使用方法如下: 1)首先在函数中定义一个具有va_list型的变量,这个变量是指向参数的指针. 2)首先 ...
- Java SE 核心 II【Collection 集合框架】
Collection集合框架 在实际开发中,需要将使用的对象存储于特定数据结构的容器中.而 JDK 提供了这样的容器——集合框架,集合框架中包含了一系列不同数据结构(线性表.查找表)的实现类.集合的引 ...
- python常用模块:sys、os、path、setting、random、shutil
今日内容讲了3个常用模块 一.sys模块二.os模块三.os下path模块四.random模块五.shutil模块 一.sys模块 import sys #环境变量 print(sys.path) # ...