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
随机推荐
- 2 java开发环境的配置步骤
1 首先需要下载JDK(以java se development kit java标准版开发包) 8.0 如果只是单纯的运行java程序则只需要安装JRE(java runtime envirome ...
- vue下载后台传过来的乱码流的解决办法
后台返回的乱码流 解决办法: 请求方式用的是axios,主要加关键的 {responseType: 'blob'} axios封装 export function postDownload(url, ...
- linux 删除文件空间未释放问题
现象:我们测试环境上,导出数据文件时,由于作业报错,重复导出,空间使用到达100%,按理说,导出的文件时在相同的路径下,文件名也是一致的,会自动替换. 那么之前导出的文件会被删除,问题就出现在删除这一 ...
- 第十章、sys模块
目录 第十章.sys模块 第十章.sys模块 方法 详解 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.modules.keys() 返回所有已经导入的模块列表 sys.ex ...
- 【JAVASCRIPT】call和apply的用法以及区别
function add(c,d){ return this.a + this.b + c + d; } var s = {a:"鸡", b:"你"}; con ...
- (转)ORACLE中关于外键缺少索引的探讨和总结
在ORACLE数据库中,定义外键约束时,ORACLE是不会自动创建对应索引的,必须手动在外键约束相关的列上创建索引.那么外键字段上是否有必要创建索引呢?如果有必要的话,巡检时,如何找出外键字段上没有创 ...
- mysql下优化表和修复表命令(repair table、optimize table)
随着mysql的长期使用,肯定会出现一些问题,一般情况下mysql表无法访问,就可以修复表了,优化时减少磁盘占用空间,方便备份. repair table table_name //修复表 optim ...
- java线程基础巩固---数据同步引入并结合jconsole,jstack以及汇编指令认识synchronized关键字
对于多线程编程而言其实老生成谈的就是数据同步问题,接下来就会开始接触这块的东东,比较麻烦,但是也是非常重要,所以按部就班的一点点去专研它,下面开始. 数据同步引入: 这里用之前写过的银行叫号的功能做为 ...
- Centos7网卡配置命令nmcli
在配置Centos6时,大家第一想到的就是把networkManager这个服务关掉,让它消失,这个服务台太鸡肋了,不该起作用的时候经常起作用,给管理带来了不便,但是在Centos7当中network ...
- linux 发送 post 请求
curl -H "Content-type: application/json" -X POST -d '{"accoId":"IDAP_000000 ...