假定客户端执行主动打开,服务器执行被动打开,客户端发送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的更多相关文章

  1. TCP主动打开 之 第一次握手-发送SYN

    tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端: tcp_v4_connect为发起连接主流程,首先对必要参数进行检查 ...

  2. TCP主动打开 之 第二次握手-接收SYN+ACK

    假设客户端执行主动打开,已经经过第一次握手,即发送SYN包到服务器,状态变为SYN_SENT,服务器收到该包后,回复SYN+ACK包,客户端收到该包,进行主动打开端的第二次握手部分:流程中涉及到的函数 ...

  3. TCP被动打开 之 第二次握手-发送SYN+ACK

    假定客户端执行主动打开,发送syn包到服务器,服务器执行完该包的第一次握手操作后,调用af_ops->send_synack向客户端发送syn+ack包,该回调实际调用tcp_v4_send_s ...

  4. TCP被动打开 之 第三次握手-接收ACK

    假定客户端主动打开,发送syn包到服务器,服务器创建连接请求控制块加入到队列,进入TCP_NEW_SYN_RECV 状态,发送syn+ack给客户端,并启动定时器,等待客户端回复最后一个握手ack: ...

  5. 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 ...

  6. TCP连接建立系列 — 服务端接收SYN段

    本文主要分析:服务器端接收到SYN包时的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 接收入口 1. 状态为ESTABLISHED时,用tcp_rcv_esta ...

  7. IP封包协议头/TCP协议头/TCP3次握手/TCP4次挥手/UDP协议头/ICMP协议头/HTTP协议(请求报文和响应报文)/IP地址/子网掩码(划分子网)/路由概念/MAC封包格式

    IP协议头IP包头格式: 1.版本号:4个bit,用来标识IP版本号.这个4位字段的值设置为二进制的0100表示IPv4,设置为0110表示IPv6.目前使用的IP协议版本号是4. 2.首部长度:4个 ...

  8. 详解TCP连接的“三次握手”与“四次挥手”(上)

    一.TCP connection 客户端与服务器之间数据的发送和返回的过程当中需要创建一个叫TCP connection的东西: 由于TCP不存在连接的概念,只存在请求和响应,请求和响应都是数据包,它 ...

  9. tcp协议:三次握手四次挥手详解-转

    https://www.cnblogs.com/welan/p/9925119.html

随机推荐

  1. python获取文件及文件夹大小

    Python3.3下测试通过 获取文件大小 使用os.path.getsize函数,参数是文件的路径 获取文件夹大小 import os from os.path import join, getsi ...

  2. 首次给app添加页面

    app添加页面的步骤(含泪史,都是自己摸索出来的) 1.通过页面上的文字,利用搜索功能找到这个页面 2.根据这个页面找到这个页面的action(注意了,这个R.layout.后面这个是页面文件名字) ...

  3. Cannot assign to read only property 'exports' of object at webpack ....BaseClient

    网上找了很多资料说是import和export不能一起用,改代码 其实根本原因是es6和es5混合使用造成的兼容性问题 只需要配置.babelrc就可以了 首先安装 npm install -D tr ...

  4. C# 字符串拼接性能探索 c#中+、string.Concat、string.Format、StringBuilder.Append四种方式进行字符串拼接时的性能

    本文通过ANTS Memory Profiler工具探索c#中+.string.Concat.string.Format.StringBuilder.Append四种方式进行字符串拼接时的性能. 本文 ...

  5. dart 函数练习

    import 'dart:convert'; import 'dart:html'; void main() { _getIPAddress() { final url = 'https://http ...

  6. How to: Compile Linux kernel 2.6

      Compiling custom kernel has its own advantages and disadvantages. However, new Linux user / admin ...

  7. laravel5.8 IoC 容器

    网上 对容器的解释有很多,这里只是记录,搬运! 1.简单理解: 2019-10-10 11:24:09 解析 lavarel 容器 IoC 容器 作用 就是 “解耦” .“依赖注入(DI) IoC 容 ...

  8. Redis Sentinel机制与用法说明

    本文来自 https://www.cnblogs.com/zhoujinyi/p/5569462.html 概述 Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Red ...

  9. zencart新增categories分类表字段步骤

    zencart新增分类字段步骤 1.categories表新增字段related_categories.related_products ) ) NOT NULL; 2.修改admin\categor ...

  10. 天刀默认src截图保存文件夹位置在哪里?

    C:\Users\Public\Documents\WuXia 注意有的电脑显示的是public documents,实际进去就是documents