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
随机推荐
- python之输入一系列整数输出最大值
在python学习中,我们经常会遇到:编写一个程序,输入若干整数或者是在一串字符中,输出最大值(数)的问题.那么在这里,我给出了几种常见的,也是几种比较常用的方法,希望能给大家的学习带来一定的帮助. ...
- DDOS攻击脚本
import sysimport osimport timeimport socketimport random#Code Timefrom datetime import datetimenow = ...
- HTML实现三角形和旗帜代码
1.三角形 1.1html+css <style> *{ margin: 0px; padding: 0px; } span{ display: block; margin-left: 2 ...
- 短信对接——一种jdbc链接运用
package sms; import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamRead ...
- PyCharm 创建指定版本的 Django (超详细图解)
最近在学习胡阳老师(the5fire)的<Django企业级开发实战>,想要使用pycharm创建django项目时,在使用virtualenv创建虚拟环境后,在pycharm内,无论如何 ...
- kubernetes之service
service出现的动机 Kubernetes Pods 是有生命周期的.他们可以被创建,而且销毁不会再启动. 如果您使用 Deployment 来运行您的应用程序,则它可以动态创建和销毁 Pod. ...
- Python3多线程爬取meizitu的图片
python环境:python3 运行环境:win10和linux都可以,其他系统没测 1 安装依赖 pip install requests pip install lxml pip install ...
- Linux 命令配置IP
配置静态IP:ip addr add 192.168.18.18/24 dev eth0 启动网卡:ifup eth0/ifup ifcfg-eth0 添加默认网关路由:ip route add de ...
- Spring-AOP-学习笔记(2)-AspectJ
1.启用@AspectJ,需要下载aspectjweaver.jar <!-- 默认启用动态代理 --><aop:aspectj-autoproxy/> <!-- 注解启 ...
- Hive优化(十一)
Hive优化 Hive的存储层依托于HDFS,Hive的计算层依托于MapReduce,一般Hive的执行效率主要取决于SQL语句的执行效率,因此,Hive的优化的核心思想是MapReduce的优 ...