tcp 客户端 发送syn
简介
sys_connect->inet_stream_connect->inet_stream_connect->tcp_v4_connect->tcp_connect
对于tcp,inet_stream_connect()调用tcp_v4_connect发送三次握手的第一次syn请求, 并根据socket是否阻塞来决定是否调用inet_wait_for_connect来等待
tcp_v4_connect
- 调用ip_route_connect和ip_route_newports创建或者获取路由缓存,并决定发送地址/设备, 下一跳
- 更新状态机TCP_CLOSE->TCP_SYN_SENT
- inet_hash_connect(&tcp_death_row, sk); 如果socket没有bind到特定端口,这里选择端口进行bind, 如果是reuseport判断能否recycle tw
- tp->write_seq = secure_tcp_sequence_number() 生产初始seq序号
- tcp_connect()发送握手包
/* This will initiate an outgoing connection.
1. 检查socket的地址长度和使用的协议族。
2. 查找路由缓存。
3. 设置本端的IP。
4. 如果传输控制块已经被使用过了,则重新初始化相关变量。
5. 记录服务器端的IP和端口。
6. 把连接的状态更新为TCP_SYN_SENT。
7. 选取本地端口,可以是未被使用过的端口,也可以是允许重用的端口。
8. 把sock链入本地端口的使用者哈希队列,把sock链入ehash哈希表。
9. 如果源端口或者目的端口发生改变,则需要重新查找路由。
10. 根据四元组,设置本端的初始序列号。
11. 根据初始序号和当前时间,设置IP首部ID字段值。
12. 构造一个SYN段,并发送出去。
调用ip_route_connect和ip_route_newports创建或者获取路由缓存,并决定发送地址/设备, 下一跳
更新状态机TCP_CLOSE->TCP_SYN_SENT
inet_hash_connect(&tcp_death_row, sk); 如果socket没有bind到特定端口,这里选择端口进行bind, 如果是reuseport判断能否recycle tw
tp->write_seq = secure_tcp_sequence_number() 生产初始seq序号
tcp_connect()发送握手包
*/
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
struct inet_sock *inet = inet_sk(sk);
struct tcp_sock *tp = tcp_sk(sk);
__be16 orig_sport, orig_dport;
__be32 daddr, nexthop;
struct flowi4 *fl4;
struct rtable *rt;
int err;
struct ip_options_rcu *inet_opt; if (addr_len < sizeof(struct sockaddr_in))
return -EINVAL; if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT;
//connect的时候s_addr里面对应的是目的地址,即对端ip地址
nexthop = daddr = usin->sin_addr.s_addr;
inet_opt = rcu_dereference_protected(inet->inet_opt,
lockdep_sock_is_held(sk));
if (inet_opt && inet_opt->opt.srr) {
if (!daddr)
return -EINVAL;
nexthop = inet_opt->opt.faddr;
} orig_sport = inet->inet_sport;
orig_dport = usin->sin_port;
fl4 = &inet->cork.fl.u.ip4;
/*根据fl4,查找或创建路由缓存
* 调用ip_route_connect()根据下一跳地址等信息查找目的路由缓存项,如果路由查找命中,则生成一个相应的路由缓存项,这个缓存项不但
* 可以用于当前待发送SYN段,而且对后续的所有数据包都可以起到一个加速路由查找的作用。
*/
rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
IPPROTO_TCP,
orig_sport, orig_dport, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
if (err == -ENETUNREACH)
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
return err;
}
/*TCP不能使用类型为组播或多播的路由缓存项。*/
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) { // tcp不支持多播和广播
ip_rt_put(rt);
return -ENETUNREACH;
}
/* 如果没有启用源路由选项,则使用获取到路由缓存项中的目的地址。*/
if (!inet_opt || !inet_opt->opt.srr)
daddr = fl4->daddr;
/* 如果还未设置传输控制块中的源地址,则使用路由缓存项中的源地址对其进行设置。*/
//这里说明了客户端在连接的时候可以不用指明本地IP地址,由路由缓存找到对应目的IP的时候,就可以确定本地IP地址了。
if (!inet->inet_saddr)
inet->inet_saddr = fl4->saddr;
sk_rcv_saddr_set(sk, inet->inet_saddr);
/* 如果传输控制块中的时间戳和目的地址已被使用过,则说明该传输控制块之前已建立连接并进行过通信,需重新初始化相关成员。 */
if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
/* Reset inherited state */
tp->rx_opt.ts_recent = 0;
tp->rx_opt.ts_recent_stamp = 0;
if (likely(!tp->repair))
tp->write_seq = 0;
}
/* 如果启用了sysctl_tw_recycle并接收过时间戳选项,从对端信息块中获取相应的值来初始化ts_recent_stamp和ts_recent。*/
if (tcp_death_row.sysctl_tw_recycle &&
!tp->rx_opt.ts_recent_stamp && fl4->daddr == daddr)
tcp_fetch_timewait_stamp(sk, &rt->dst); inet->inet_dport = usin->sin_port;
sk_daddr_set(sk, daddr); inet_csk(sk)->icsk_ext_hdr_len = 0;
if (inet_opt)
inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; /* Socket identity is still unknown (sport may be zero).
* However we set state to SYN-SENT and not releasing socket
* lock select source port, enter ourselves into the hash tables and
* complete initialization after this.
*/
/* 将TCP设置为SYN_SENT,动态绑定一个本地端口,并将传输控制块添加到ehash散列表中。由于在动态分配端口时,如果找到的是已使用的端口,则
* 需在TIME_WAIT状态中进行相应的确认,因此调用inet_hash_connect()时需用timewait传输控制块和参数管理器tcp_death_row作为参数。*/
tcp_set_state(sk, TCP_SYN_SENT);
//bind local port,tw_recycle
/*/没有bind端口,随机生成一个偏移,随机化端口分配过程*/
err = inet_hash_connect(&tcp_death_row, sk);
if (err)
goto failure; sk_set_txhash(sk); rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
inet->inet_sport, inet->inet_dport, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
goto failure;
}
/* OK, now commit destination to socket. */
sk->sk_gso_type = SKB_GSO_TCPV4;
sk_setup_caps(sk, &rt->dst); /*
* 如果write_seq字段值为零,则说明该传输控制块还
* 未设置初始序号,因此需调用secure_tcp_sequence_number(),
* 根据双方的地址、端口计算初始序列号,同时根据
* 发送需要和当前时间得到用于设置IP首部ID域的值。
*/
if (!tp->write_seq && likely(!tp->repair))
tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
inet->inet_daddr,
inet->inet_sport,
usin->sin_port); inet->inet_id = tp->write_seq ^ jiffies; err = tcp_connect(sk); rt = NULL;
if (err)
goto failure; return 0; failure:
/*
* This unhashes the socket and releases the local port,
* if necessary.
*/
tcp_set_state(sk, TCP_CLOSE);
ip_rt_put(rt);
sk->sk_route_caps = 0;
inet->inet_dport = 0;
return err;
}
EXPORT_SYMBOL(tcp_v4_connect);
- 对于已经bind端口的socket
- 判断是否有人reuseport, 如果只有自己bind到这个port, 则调用inet_ehash_nolisten(sk, NULL);插入ehash中
- 如果有其他人bind到这个端口,则调用__inet_check_established,
-确认其他人是否在ehash中,不在ehash中,则可以使用这个port
-在ehash中,并存在满足五元组的timewait状态sk,则调用tcp_twsk_unique判断是否能被回收
-在ehash中,但是满足五元组的sk不是timewait状态,则不能使用这个port来connect。 这个就说明了两个tcp connect(), 开启reuseport后bind到相同端口,bind()能成功,但是第二个connect会失败
- 对于没有bind端口的socket, 则需要尝试分配端口
- inet_sk_port_offset随机生成一个port_offset, 通过port_offset来保证端口搜索区间的随机性, 遍历这个区间,尝试分配
- 在bhash中查找是否有其他socket bind到这个端口上,没有则表示可以分配
- 如果有其他socket在相同的bhash bucket上,调用__inet_check_established来确认是否能分配这个端口, 过程同上
- 分配成功后inet_bind_hash,设置端口和bhash, 并调用inet_ehash_nolisten插入ehash中
- 如果需要,还需要释放tw socket
/*
* Bind a port for a connect operation and hash it.
*/
/*
* inet_hash_connect()主要用于在主动连接时动态绑定一个端口。
* 1)在动态端口范围内,从通过源地址、目的地址和目的端口
* 计算得到的偏移开始,确认一个可用的端口号
* 2)如果该端口已使用,则进而确定该端口是否能使用,不能
* 则递增端口号继续确认;能使用则可用端口已找到。
* 3)如果该端口未使用,则可使用该端口
* 4)最后完成绑定过程。
*/
/* 动态绑定一个本地端口,并将传输控制块添加到ehash散列表中。由于在动态分配端口时,如果找到的是已使用的端口,则
* 需在TIME_WAIT状态中进行相应的确认,因此调用inet_hash_connect()时需用timewait传输控制块和参数管理器tcp_death_row作为参数。*/
//这里面会把sk添加到ehash中,虽然连接还没建立起来。该函数外的tcp_connect才是真正发送SYN报文的地方
int inet_hash_connect(struct inet_timewait_death_row *death_row,
struct sock *sk)
{
return __inet_hash_connect(death_row, sk, inet_sk_port_offset(sk),
__inet_check_established, __inet_hash_nolisten);
}
/*从这个函数的实现可以看出,主要是由于可用的端口被占满了,所以找不到一个可用的端口,导致连接失败。
运行netstat可以发现确实存在很多TIME_WAIT状态的socket,这些socket将可用端口占满了。
netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state)
print key,"\t",state[key]}'
TIME_WAIT 26837
ESTABLISHED 30
*/
//参考:http://www.yunstorage.org/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/socket-connect-error-99cannot-assign-requested-address/
//如果快速回收TIME_WAIT状态的端口
int __inet_hash_connect(struct inet_timewait_death_row *death_row,
struct sock *sk, u32 port_offset,
int (*check_established)(struct inet_timewait_death_row *,
struct sock *, __u16, struct inet_timewait_sock **))
{
struct inet_hashinfo *hinfo = death_row->hashinfo;
/* 通过tcp_death_row中的成员hashinfo,获取指向TCP中散列表管理器hashinfo。 */
struct inet_timewait_sock *tw = NULL;
struct inet_bind_hashbucket *head;
int port = inet_sk(sk)->inet_num;
struct net *net = sock_net(sk);
struct inet_bind_bucket *tb;
u32 remaining, offset;
int ret, i, low, high;
static u32 hint; if (port) {//如果是应用程序bind的时候指定了端口,则无需端口复用检查。
head = &hinfo->bhash[inet_bhashfn(net, port,
hinfo->bhash_size)];
tb = inet_csk(sk)->icsk_bind_hash;
spin_lock_bh(&head->lock);
if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {//也就是说只有自己bind到这个端口, 没有reuseport
inet_ehash_nolisten(sk, NULL);//插入ehash
spin_unlock_bh(&head->lock);
return 0;
}
spin_unlock(&head->lock);
/* No definite answer... Walk to established hash table */
//否则检查ehash,查看bind到相同端口的socket是否进入timewait,进入timewait则判断是否能recycle,否则就是说还在连接状态或是没在ehash中
ret = check_established(death_row, sk, port, NULL);
local_bh_enable();
return ret;
} inet_get_local_port_range(net, &low, &high);
high++; /* [32768, 60999] -> [32768, 61000[ */
remaining = high - low;
if (likely(remaining > 1))
remaining &= ~1U; offset = (hint + port_offset) % remaining;
/* In first pass we try ports of @low parity.
* inet_csk_get_port() does the opposite choice.
*/
offset &= ~1U;
other_parity_scan:
port = low + offset;
for (i = 0; i < remaining; i += 2, port += 2) {
if (unlikely(port >= high))
port -= remaining;
if (inet_is_local_reserved_port(net, port))
continue;
head = &hinfo->bhash[inet_bhashfn(net, port,
hinfo->bhash_size)];
spin_lock_bh(&head->lock); /* Does not bother with rcv_saddr checks, because
* the established check is already unique enough.
*/
inet_bind_bucket_for_each(tb, &head->chain) {
if (net_eq(ib_net(tb), net) && tb->port == port) {
if (tb->fastreuse >= 0 ||
tb->fastreuseport >= 0)
goto next_port;
WARN_ON(hlist_empty(&tb->owners));
if (!check_established(death_row, sk,
port, &tw))//在ehash中查找timewait,如果满足五元组,并调用tcp_twsk_unique判断
goto ok;
goto next_port;
}
} tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
net, head, port);
if (!tb) {
spin_unlock_bh(&head->lock);
return -ENOMEM;
}
tb->fastreuse = -1;
tb->fastreuseport = -1;
goto ok;
next_port:
spin_unlock_bh(&head->lock);
cond_resched();
} offset++;
if ((offset & 1) && remaining > 1)
goto other_parity_scan; return -EADDRNOTAVAIL; ok:
hint += i + 2; /* Head lock still held and bh's disabled */
inet_bind_hash(sk, tb, port);//设置snum和tb
if (sk_unhashed(sk)) {
inet_sk(sk)->inet_sport = htons(port);
inet_ehash_nolisten(sk, (struct sock *)tw);//删除tw,插入sk
}
if (tw)
inet_twsk_bind_unhash(tw, hinfo);//删除tw的bind关系
spin_unlock(&head->lock);
if (tw)
inet_twsk_deschedule_put(tw);//回收tw
local_bh_enable();
return 0;
}
tcp 客户端 发送syn的更多相关文章
- TCP连接建立系列 — 客户端发送SYN段
主要内容:客户端调用connect()时的TCP层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd connect的TCP层实现 SOCK_STRE ...
- TCP被动打开 之 第二次握手-发送SYN+ACK
假定客户端执行主动打开,发送syn包到服务器,服务器执行完该包的第一次握手操作后,调用af_ops->send_synack向客户端发送syn+ack包,该回调实际调用tcp_v4_send_s ...
- TCP主动打开 之 第一次握手-发送SYN
tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端: tcp_v4_connect为发起连接主流程,首先对必要参数进行检查 ...
- TCP的状态 (SYN, FIN, ACK, PSH, RST, URG)
状态说明 SYN表示建立连接, FIN表示关闭连接, ACK表示响应, PSH表示有 DATA数据传输, RST表示连接重置. 其中,ACK是可能与SYN,FIN等同时使用的,比如SYN和ACK可能同 ...
- 【RL-TCPnet网络教程】第14章 RL-TCPnet之TCP客户端
第14章 RL-TCPnet之TCP客户端 本章节为大家讲解RL-TCPnet的TCP客户端实现,学习本章节前,务必要优先学习第12章TCP传输控制协议基础知识.有了这些基础知识之后,再搞本 ...
- 服务器重复发送SYN ACK 和 TCP_DEFER_ACCEPT设置
现象: 以下为其他网站提供,和我遇到的情况一样. 就是服务器老是重复发送 SYN, ACK. 4414.229553 client -> server TCP 62464 > http ...
- TCP半连接和syn攻击(转)
TCP半连接和syn攻击 转载 2014年04月06日 21:36:10 4243 摘自:http://blog.sina.com.cn/s/blog_54b5ea250100g2r8.html SY ...
- UDP广播 与 TCP客户端 --服务端
随着倒计时的响声,自觉无心工作,只想为祖国庆生. 最近有遇到过这样一个问题,将摄像头识别的行人,车辆实时显示在客户端中.有提供接口,会以Json的数据的形式将实时将识别的对象进行Post提交.所以我们 ...
- 24-ESP8266 SDK开发基础入门篇--Android TCP客户端.控制 Wi-Fi输出PWM的占空比,调节LED亮度
https://www.cnblogs.com/yangfengwu/p/11204436.html 刚才有人说需要点鸡汤.... 我想想哈;我还没问关于哪方面的鸡汤呢!!! 我所一直走的路线 第一: ...
随机推荐
- 第一个月多测师讲解__项目讲解以及注意事项(肖sir)
一.目的讲解流程:(讲述业务时长10-15分钟为宜)1.自我介绍礼貌用语,姓名,籍贯,学校,个人技能,经验,表现,兴趣爱好等 ,1分钟 ,谢谢2.介绍项目的名字 ,项目的背景,(涉及什么架构)3.对项 ...
- MeteoInfoLab脚本示例:闪电位置图
这个脚本示例读取文本格式的闪电数据,读出每条闪电记录的经纬度和强度,在地图上绘制出每个闪电的位置,并用符号和颜色区分强度正负.数据格式如下:0 2009-06-06 00:01:16.6195722 ...
- MeteoInfoLab脚本示例:FY-3A AOD HDF数据
FY3A卫星有HDF格式的AOD产品数据,全球范围,分辨率为0.05度.读取数据文件变量后要重新设定X/Y维,数据是Y轴反向的,且需要除以10000得到AOD值. 脚本程序: #Add data fi ...
- Pythonic【15个代码示例】
Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手. 要写出 Pythonic(优雅的.地道的.整洁的)代码,还要平时多观察那些大牛代码,Github 上有很 ...
- C语言编程丨循环链表实现约瑟夫环!真可谓无所不能的C!
循环链表 把链表的两头连接,使其成为了一个环状链表,通常称为循环链表. 和它名字的表意一样,只需要将表中最后一个结点的指针指向头结点,链表就能成环儿,下图所示. 需要注意的是,虽然循环链表成环 ...
- 【思维】Luogu P3941 入阵曲
题目大意 洛谷链接 给出一个矩阵和 \(K\) ,问有多少子矩阵中的元素和能整除 \(K\). 数据范围 \(2\leq n,m\leq 400\),\(0\leq K\leq 10^6\). 思路 ...
- matplotlib条形图
三个班级平均分 import matplotlib.pyplot as plt import matplotlib as mpl classes = ['class1','class2','class ...
- Linux运维学习第六周记
四月上夏渐热 善疗也须调摄 文殊眼裹抽筋 金刚脑后拔楔 网络的世界让人变得不那么真实! 第六周学记 用了一周的时间学习了计算机网络基础知识,说是基础,更应该说是必备的常识! 网络的协议和管理 TCP/ ...
- day72:drf:
目录 1.续:反序列化功能(5-8) 1.用户post类型提交数据,反序列化功能的步骤 2.反序列化功能的局部钩子和全局钩子 局部钩子和全局钩子在序列化器中的使用 反序列化相关校验的执行顺序 3.反序 ...
- 在 Istio 中实现 Redis 集群的数据分片、读写分离和流量镜像
Redis 是一个高性能的 key-value 存储系统,被广泛用于微服务架构中.如果我们想要使用 Redis 集群模式提供的高级特性,则需要对客户端代码进行改动,这带来了应用升级和维护的一些困难.利 ...