TCP连接建立系列 — 客户端发送SYN段
主要内容:客户端调用connect()时的TCP层实现。
内核版本:3.15.2
我的博客:http://blog.csdn.net/zhangskd
connect的TCP层实现
SOCK_STREAM类socket的TCP层操作函数集实例为tcp_prot,其中客户端使用tcp_v4_connect()来发送SYN段。
struct proto tcp_prot = {
.name = "TCP",
...
.connect = tcp_v4_connect,
...
.h.hashinfo = &tcp_hashinfo,
...
};
tcp_v4_connect()主要做了以下事情:
1. 检查socket的地址长度和使用的协议族。
2. 查找路由缓存。
3. 设置本端的IP。
4. 如果传输控制块已经被使用过了,则重新初始化相关变量。
5. 记录服务器端的IP和端口。
6. 把连接的状态更新为TCP_SYN_SENT。
7. 选取本地端口,可以是未被使用过的端口,也可以是允许重用的端口。
这部有点复杂,下一篇会详细介绍。
8. 把sock链入本地端口的使用者哈希队列,把sock链入ehash哈希表。
9. 如果源端口或者目的端口发生改变,则需要重新查找路由。
10. 根据四元组,设置本端的初始序列号。
11. 根据初始序号和当前时间,设置IP首部ID字段值。
12. 构造一个SYN段,并发送出去。
/* This will initiate an outgoing connection. */ 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;
struc rtable *rt;
int err;
struct ip_options_rcu *inet_opt; /* Socket地址的长度不正确 */
if (addr_len < sizeof(struct sockaddr_in))
return -EINVAL; /* Socket地址的协议族不正确 */
if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT; nexthop = daddr = usin->sin_addr.s_addr; /* 服务器IP */ inet_opt = rcu_dereference_protected(inet->inet_opt, sock_owned_by_user(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; /* 查找路由缓存项 */
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 == -ENETUREACH)
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
return err;
} /* 不能使用类型为多播或广播的路由缓存 */
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
ip_rt_put(rt);
return -ENETUNREACH;
} /* 如果没有使用源路由选项 */
if (! inet_opt || ! inet_opt->opt.srr)
daddr = fl4->daddr; /* 如果本端的源IP还没有设置 */
if (! inet->inet_saddr)
inet->inet_saddr = fl4->saddr;
inet->inet_rcv_saddr = 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; /* 使用TCP_REPAIR选项时,不会重置当前发送缓存的最后一个字节的序号 */
if (likely(! tp->repair))
tp->write_seq = 0; /* Tail +1 of data held in tcp send buffer */
} /* 如果启用了tcp_tw_recycle,那么从路由缓存中获取时间戳来初始化相关变量 */
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; /* 记录服务器端口 */
inet->inet_daddr = daddr; /* 记录服务器IP */
inet_csk(sk)->icsk_ext_hdr_len = 0; /* IP选项长度 */ if (inet_opt)
inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; /* IP选项长度 */
tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; /* 默认对端的MSS,之后会更新 */ /* 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 complete
* initialization after this.
*/
tcp_set_state(sk, TCP_SYN_SENT); /* 把连接状态更新为TCP_SYN_SENT */ /* 选取本地端口,把sk链入端口的使用者队列,把sk链入ehash哈希表 */
err = inet_hash_connect(&tcp_death_row, sk);
if (err)
goto failure; /* 如果源端口或者目的端口发生改变,需要重新查找路由 */
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); /* 根据路由缓存,设置网卡的特性 */ /* 根据四元组,设置本端的初始序列号,计算方式和服务器端的一样。
* 如果使用了TCP_REPAIR选项,就不用重新计算。
*/
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; /* 根据初始序号和当前时间,设置IP首部ID字段值 */ err = tcp_connect(sk); /* 构造一个SYN段,并发送出去 */ 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;
}
SYN段的构造和发送
tcp_connect()用于构造和发送SYN段,主要做了以下事情:
1. 初始化传输控制块中和TCP层或连接相关的变量。
2. 申请一个skb,进行控制字段的初始化。
3. 把skb插入到发送队列的尾部,并更新相关的变量。
4. 调用发送函数,把skb传递到IP层。
5. 开启超时重传定时器,SYN段的初始超时时间为1s。
/* Build a SYN and send it off. */ int tcp_connect(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *buff;
int err; /* 初始化TCP控制块中的相关变量 */
tcp_connect_init(sk); /* 如果使用TCP_REPAIR选项,那么不发送SYN和等待SYNACK,而是直接进入成功状态 */
if (unlikely(tp->repair)) {
tcp_finsih_connect(sk, NULL);
} buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);
if (unlikely(buff == NULL))
return -ENOBUFS; /* No buffer space available */ /* Reserve space for headers. 预留报文头部的空间*/
skb_reserve(buff, MAX_TCP_HEADER); /* 初始化不携带数据的skb的一些控制字段 */
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
tp->retrans_stamp = TCP_SKB_CB(buff)->when = tcp_time_stamp; /* 记下SYN的发送时间 */
tcp_connect_queue_skb(sk, buff); /* 把skb加入到发送队列的尾部,并更新相关变量 */
TCP_ECN_send_syn(sk, buff); /* 设置ECN相关变量 */ /* 根据是否使用TCP Fast Open选项,来选择SYN段的发送函数 */
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
if (err = -ECONNREFUSED) /* Connection refused */
return err; /* We change tp->snd_nxt after the tcp_transmit_skb() call in order to
* make this packet get counted in tcpOutSegs.
*/
tp->snd_nxt = tp->write_seq;
tp->pushed_seq = tp->write_seq; /* Last pushed seq, required to talk to windows */
TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS); /* Timer for repeating the SYN until an answer.
* 开启超时重传定时器,SYN段的初始超时时间为1s。
*/
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
}
对tcp_sock和inet_connection_sock中的部分变量进行初始化。
/* Do all connect socket setups that can be done AF independent. */ static void tcp_connect_init(struct sock *sk)
{
const struct dst_entry *dst = __sk_dst_get(sk); /* 路由缓存 */
struct tcp_sock *tp = tcp_sk(sk);
__u8 rcv_wscale; /* We'll fix this up when we get a response from the other end.
* See tcp_input.c: tcp_rcv_state_process case TCP_SYN_SENT.
*/
tp->tcp_header_len = sizeof(struct tcphdr) +
(sysctl_tcp_timestamp ? TCPOLEN_TSTAMP_ALIGNED : 0); /* TCP报头长度,包含时间戳选项 */ #ifdef CONFIG_TCP_MD5SIG
if (tp->af_specific->md5_lookup(sk, sk) != NULL)
tp->tcp_header_len += TCPOLEN_MD5SIG_ALIGNED; /* MD5SIG选项 */
#endif /* If user gave his TCP_MAXSEG, record it to clamp */
/* 如果用户通过TCP_MAXSEG选项设置了MSS上限 */
if (tp->rx_opt.user_mss)
tp->rx_opt.mss_clamp = tp->rx_opt.user_mss; /* 设置对端的接收MSS上限 */ tp->max_window = 0; /* 见过的对端最大接收窗口 */
tcp_mtup_init(sk); /* TCP的PMTU初始化 */
tcp_sync_mss(sk, dst_mtu(dst)); /* 更新MSS */ if (! tp->window_clamp) /* 本端接收窗口的上限 */
tp->window_clamp = dst_metric(dst, RTAX_WINDOW);
tp->advmss = dst_metric_advmss(dst); /* 本端在建立连接时通告的MSS */ if (tp->rx_opt.user_mss && tp->rx_opt.user_mss < tp->advmss)
tp->advmss = tp->rx_opt.user_mss; /* 不能超过用户设置的MSS */ tcp_initialize_rcv_mss(sk); /* 对端有效的发送MSS估算值做初始化 */ /* 如果用户使用SO_RCVBUF选项限制接收缓存。
* limit the window selection if the user enforce a smaller rx buffer.
*/
if (sk->sk_userlocks & SOCK_RCVBUF_LOCK &&
(tp->window_clamp > tcp_full_space(sk) || tp->window_clamp == 0))
tp->window_clamp = tcp_full_space(sk); /* 3/4 sk->sk_rcvbuf */ /* 获取接收窗口的初始值、窗口扩大因子和接收窗口的上限 */
tcp_select_initial_window(tcp_full_space(sk),
tp->advmss - (tp->rx_opt.ts_recent_stamp? tp->tcp_header_len - sizeof(struct tcphdr) : 0),
&tp->rcv_wnd,
&tp->window_clamp,
sysctl_tcp_window_scaling,
&rcv_wscale,
dst_metric(dst, RTAX_INITRWND)); tp->rx_opt.rcv_wscale = rcv_wscale; /* 设置接收窗口的扩大因子 */
tp->rcv_ssthresh = tp->rcv_wnd; sk->sk_err = 0;
sock_reset_flag(sk, SOCK_DONE);
tp->snd_wnd = 0;
tcp_init_wl(tp, 0); /* tp->snd_wl1为最近更新发送窗口的ACK段序号 */
tp->snd_una = tp->write_seq;
tp->snd_sml = tp->write_seq;
tp->snd_up = tp->write_seq;
tp->snd_nxt = tp->write_seq; if (likely(! tp->repair)) /* TCP Repair选项相关 */
tp->rcv_nxt = 0;
else
tp->rcv_tstamp = tcp_time_stamp; tp->rcv_wup = tp->rcv_nxt;
tp->copied_seq = tp->rcv_nxt; inet_csk(sk)->icsk_rto = TCP_TIMEOUT_INIT; /* RTO的初始值为1s */
inet_csk(sk)->icsk_retransmits = 0;
tcp_clear_retrans(tp);
}
把skb加入到发送队列的尾部,并更新相关变量。
static void tcp_connect_queue_skb(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); tcb->end_seq += skb->len;
skb_header_release(skb); /* 增加skb负荷部分的引用计数 */ __tcp_add_write_queue_tail(sk, skb); /* 把skb放入发送队列的尾部 */ /* sk_wmem_queued为发送队列的总大小,包含发送队列中skb数据区、
* sk_buff、sk_shared_info结构体,以及协议头等额外开销。
*/
sk->sk_wmem_queued += skb->truesize; /* 更新发送队列的总大小 */
sk_mem_charge(sk, skb->truesize); /* 减少预分配但未使用的内存 */ tp->write_seq += tcb->end_seq; /* 更新发送缓存中的最后一个字节序号(+1) */
tp->packets_out += tcp_skb_pcount(skb); /* 更新发送且未确认的数据段个数 */
}
/* skb_header_release - release reference to header
* @skb: buffer to operate on
*
* Drop a reference to the header part of the buffer.
* This is done by acquiring a payload reference.
* You must not read from the header part of skb->data after this.
*/
static inline void skb_header_release(struct sk_buff *skb)
{
BUG_ON(skb->nohdr);
skb->nohdr = 1; /* skb没有协议头 */ /* 增加skb负荷部分的引用计数 */
atomic_add(1 << SKB_DATAREF_SHIFT, &skb_shinfo(skb)->dataref);
} /* We divide dataref into two halves. The higher 16 bits hold references to
* the payload part of skb->data. The lower 16 bits hold references to the
* entire skb->data. A clone of a headerless skb holds the length of the header
* in skb->hdr_len.
*
* All users must obey the rule that the skb->data reference count must be greater
* than or equal to the payload reference count.
*
* Holding a reference to the payload part means that the user does not care about
* modifications to the header part of skb->data.
*/
#define SKB_DATAREF_SHIFT 16
#define SKB_DATAREF_MASK ((1 << SKB_DATAREF_SHIFT - 1)
SYN段的ECN标志设置。
/* Packet ECN state for a SYN. */ static inline void TCP_ECN_send_syn(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
tp->ecn_flags = 0; /* 如果支持ECN */
if (sock_net(sk)->ipv4.sysctl_tcp_ecn == 1) {
TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ECE | TCPHDR_CWR;
tp->ecn_flags = TCP_ECN_OK;
}
}
TCP连接建立系列 — 客户端发送SYN段的更多相关文章
- TCP连接建立系列 — 客户端接收SYNACK和发送ACK
主要内容:客户端接收SYNACK.发送ACK,完成连接的建立. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 接收入口 tcp_v4_rcv |--&g ...
- TCP连接建立系列 — 客户端的端口选取和重用
主要内容:connect()时的端口选取和端口重用. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 端口选取 connect()时本地端口是如何选取的呢 ...
- TCP连接建立系列 — 服务端接收ACK段(一)
http://blog.csdn.net/zhangskd/article/details/17923917 分类: Linux TCP/IP Linux Kernel 2014-01-07 09 ...
- tcp 客户端 发送syn
简介 sys_connect->inet_stream_connect->inet_stream_connect->tcp_v4_connect->tcp_connect对于t ...
- TCP连接建立系列 — 服务端接收SYN段
本文主要分析:服务器端接收到SYN包时的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 接收入口 1. 状态为ESTABLISHED时,用tcp_rcv_esta ...
- TCP连接建立系列 — 服务端发送SYNACK段
本文主要分析:服务器端如何构造和发送SYNACK段. 内核版本:3.6 Author:zhangskd @ csdn blog 发送入口 tcp_v4_send_synack()用于发送SYNACK段 ...
- TCP连接建立系列 — 服务端接收ACK段(二)
本文主要分析:三次握手中最后一个ACK段到达时,服务器端的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 创建新sock 协议族相关的操作函数,我们要看的是TCP ...
- TCP连接建立系列 — TCP选项解析
本文主要分析:在收到客户端的SYN包时,服务器端是如何解析它所携带的TCP选项,并结合本端情况决定是否予以支持. 内核版本:3.6 Author:zhangskd @ csdn blog 概述 收到客 ...
- TCP 同步传输:客户端发送,服务器段接收
1.服务器端程序 可以在TcpClient上调用GetStream()方法来获的链接到远程计算机的网络流NetworkStream.当在客户端调用时,他获的链接服务器端的流:当在服务器端调用时,他获得 ...
随机推荐
- 例10-5 uva12716
题意:gcd(a,b) = a^b,( 1≤ a , b ≤ n) 思路: ① a^b = c, 所以 a^c = b,而且c是a的约数,枚举a,c,再gcd判断 ② 打表可知 a-b = c,而且a ...
- 笔记9 AOP练习3(通过注解引入新功能 )
切面可以为Spring bean添加新方法. 在Spring中,切面只是实现了它们所包装bean相同接口的 代理.如果除了实现这些接口,代理也能暴露新接口的话,会怎么样 呢?那样的话,切面所通知的be ...
- 华科机考:N阶楼梯上楼
时间限制:1秒空间限制:32768K 题目描述 N阶楼梯上楼问题:一次可以走两阶或一阶,问有多少种上楼方式.(要求采用非递归) 输入描述: 输入包括一个整数N,(1<=N<90). 输出描 ...
- 手写JAVA虚拟机(二)——实现java命令行
查看手写JAVA虚拟机系列可以进我的博客园主页查看. 我们知道,我们编译.java并运行.class文件时,需要一些java命令,如最简单的helloworld程序. 这里的程序最好不要加包名,因为加 ...
- TCP/IP学习笔记__mbuf
Socket发送和接收数据都是写入和读取mbuf(存储器缓存)来完成的.下面着重介绍下Sendto函数与mbuf的关系: 以UDP协议为例: 1.UDP的输出执行过程: UDP的输出执行过程 2.协议 ...
- moment.js常用时间示例,时间管理
'今天': moment() '昨天': moment().subtract(1, 'days') '过去7天':moment().subtract(7, 'days'),moment() '上月': ...
- VLAN之间单臂路由通信
实验目的 理解单臂路由的应用场景 掌握路由器子接口的配置方法 掌握子接口封装VLAN的配置方法 理解单臂路由的工作原理 实验原理 单臂路由解决用户需要跨越VLAN实现通信的情况. 原理:通过一台路由器 ...
- OpenSuSE Linux下安装Oracle10g的步骤
OpenSuSE Linux下安装Oracle10g的步骤: --root用户 --1.vi etc/profile 添加脚本: if [ \$USER = "oracle" ]; ...
- python中的printf:%号拼接字符串和format函数
在C语言中,我们使用printf("%s","hello")这种形式进行字符串的拼接 在python中,进行这样的拼接有两种实现方式,分别是%号拼接以及使用fo ...
- Linux 新系统个人配置
1,装codeblocks 2,装vim,检查gcc,g++,修改vim环境 cd ~vim .vimrc添加如下几行:set shiftwidth=4 (表示每一级缩进的长度)s ...