tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端;

tcp_v4_connect为发起连接主流程,首先对必要参数进行检查,获取路由信息,改变连接状态成SYN_SENT,再调用inet_hash_connect将控制块加入到ehash,最后调用tcp_connect发送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;
struct rtable *rt;
int err;
struct ip_options_rcu *inet_opt; /* timewait控制块结构 */
struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row; /* 地址长度不合法 */
if (addr_len < sizeof(struct sockaddr_in))
return -EINVAL; /* 地址族不合法 */
if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT; /* 设置下一跳和目的地址 */
nexthop = daddr = usin->sin_addr.s_addr; /* 获取ip选项 */
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;
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;
} /* 查找成功 */ /* 路由是组播或者广播 */
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
ip_rt_put(rt);
return -ENETUNREACH;
} /* 选项为空或者未启用源路由选项 */
/* 设置目的地址为路由缓存中地址 */
if (!inet_opt || !inet_opt->opt.srr)
daddr = fl4->daddr; /* 源地址为空 */
/* 使用路由缓存中的源地址 */
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 = ;
tp->rx_opt.ts_recent_stamp = ;
if (likely(!tp->repair))
tp->write_seq = ;
} /* 设置目的端口 */
inet->inet_dport = usin->sin_port;
/* 设置目的地址 */
sk_daddr_set(sk, daddr); /* 获取ip选项长度 */
inet_csk(sk)->icsk_ext_hdr_len = ;
if (inet_opt)
inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; /* 设置mss */
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 */
tcp_set_state(sk, TCP_SYN_SENT); /* 端口绑定,加入ehash */
err = inet_hash_connect(tcp_death_row, sk);
if (err)
goto failure; /* 设置hash值 */
sk_set_txhash(sk); /*
如果源端口或者目的端口发生变化,
重新获取路由,并更新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);
rt = NULL; if (likely(!tp->repair)) {
/* 获取发送序号 */
if (!tp->write_seq)
tp->write_seq = secure_tcp_seq(inet->inet_saddr,
inet->inet_daddr,
inet->inet_sport,
usin->sin_port);
/* 时间戳偏移 */
tp->tsoffset = secure_tcp_ts_off(inet->inet_saddr,
inet->inet_daddr);
} /* 设置ip首部的id */
inet->inet_id = tp->write_seq ^ jiffies; /* fastopen */
if (tcp_fastopen_defer_connect(sk, &err))
return err;
if (err)
goto failure; /* 发送syn */
err = tcp_connect(sk); if (err)
goto failure; return ; 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 = ;
inet->inet_dport = ;
return err;
}

__inet_hash_connect将端口检查通过的控制块加入到ehash;函数对是否设置端口进行了不同处理,若未设置端口,则需要查找一个端口;函数还调用check_established检查是否可以复用处在TIME_WAIT的控制块,以及调用inet_ehash_nolisten将端口对应的控制块加入的ehash;

 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;
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) {
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) {
/* 将控制块加入只ehash */
inet_ehash_nolisten(sk, NULL);
spin_unlock_bh(&head->lock);
return ;
}
spin_unlock(&head->lock);
/* No definite answer... Walk to established hash table */
/* 检查复用情况 */
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 > ))
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 = ; i < remaining; i += , port += ) {
if (unlikely(port >= high))
port -= remaining;
/* 保留端口 */
if (inet_is_local_reserved_port(net, port))
continue; /* 找到端口对应的绑定hash桶 */
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 >= ||
tb->fastreuseport >= )
goto next_port;
WARN_ON(hlist_empty(&tb->owners)); /* 检查timewait复用情况 */
if (!check_established(death_row, sk,
port, &tw))
goto ok;
goto next_port;
}
} /* 遍历没有重复 */ /* 创建该端口的绑定信息节点,加入绑定hash */
tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
net, head, port);
if (!tb) {
spin_unlock_bh(&head->lock);
return -ENOMEM;
} /* 设置默认重用标记 */
tb->fastreuse = -;
tb->fastreuseport = -;
goto ok;
next_port:
spin_unlock_bh(&head->lock);
cond_resched();
} /* 继续从下一半端口中找 */
offset++;
if ((offset & ) && remaining > )
goto other_parity_scan; return -EADDRNOTAVAIL; ok:
hint += i + ; /* Head lock still held and bh's disabled */ /* 控制块加入该端口的使用者列表 */
inet_bind_hash(sk, tb, port); /* 初始化源端口,加入到ehash */
if (sk_unhashed(sk)) {
inet_sk(sk)->inet_sport = htons(port);
inet_ehash_nolisten(sk, (struct sock *)tw);
}
/*有timewait控制块则从bind列表中移除 */
if (tw)
inet_twsk_bind_unhash(tw, hinfo);
spin_unlock(&head->lock); /* 调度销毁timewait控制块 */
if (tw)
inet_twsk_deschedule_put(tw);
local_bh_enable();
return ;
}

__inet_check_established用于检查与相同端口中处于TIME_WAIT状态的控制块是否可以复用;

 /* called with local bh disabled */
static int __inet_check_established(struct inet_timewait_death_row *death_row,
struct sock *sk, __u16 lport,
struct inet_timewait_sock **twp)
{
struct inet_hashinfo *hinfo = death_row->hashinfo;
struct inet_sock *inet = inet_sk(sk);
__be32 daddr = inet->inet_rcv_saddr;
__be32 saddr = inet->inet_daddr;
int dif = sk->sk_bound_dev_if;
INET_ADDR_COOKIE(acookie, saddr, daddr);
const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);
struct net *net = sock_net(sk);
unsigned int hash = inet_ehashfn(net, daddr, lport,
saddr, inet->inet_dport);
struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
spinlock_t *lock = inet_ehash_lockp(hinfo, hash);
struct sock *sk2;
const struct hlist_nulls_node *node;
struct inet_timewait_sock *tw = NULL; spin_lock(lock); /* 遍历链表 */
sk_nulls_for_each(sk2, node, &head->chain) { /* hash不等 */
if (sk2->sk_hash != hash)
continue; /* 找到节点 */
if (likely(INET_MATCH(sk2, net, acookie,
saddr, daddr, ports, dif))) {
/* 节点连接处于timewait状态 */
if (sk2->sk_state == TCP_TIME_WAIT) {
tw = inet_twsk(sk2); /* 可以复用 */
if (twsk_unique(sk, sk2, twp))
break;
} /* 不处于tw,或者不能复用 */
goto not_unique;
}
} /* Must record num and sport now. Otherwise we will see
* in hash table socket with a funny identity.
*/
/* 设置端口和hash */
inet->inet_num = lport;
inet->inet_sport = htons(lport);
sk->sk_hash = hash;
WARN_ON(!sk_unhashed(sk)); /* 节点加入ehash */
__sk_nulls_add_node_rcu(sk, &head->chain);
if (tw) {
/* 删除tw节点 */
sk_nulls_del_node_init_rcu((struct sock *)tw);
__NET_INC_STATS(net, LINUX_MIB_TIMEWAITRECYCLED);
}
spin_unlock(lock); /* 增加使用计数 */
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, ); /* 设置能复用的控制块 */
if (twp) {
*twp = tw;
} else if (tw) {
/* Silly. Should hash-dance instead... */
inet_twsk_deschedule_put(tw);
}
return ; not_unique:
spin_unlock(lock);
return -EADDRNOTAVAIL;
}

inet_ehash_nolisten用于将控制块加入ehash,并根据结果做不同处理;

 /* 添加到ehash中 */
bool inet_ehash_nolisten(struct sock *sk, struct sock *osk)
{
/* 添加到ehash中 */
bool ok = inet_ehash_insert(sk, osk); if (ok) {
/* 成功增加计数 */
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, );
} else {
/* 增加孤儿数量 */
percpu_counter_inc(sk->sk_prot->orphan_count);
/* 标识连接关闭状态 */
sk->sk_state = TCP_CLOSE;
/* 设置销毁标记 */
sock_set_flag(sk, SOCK_DEAD);
/* 销毁控制块 */
inet_csk_destroy_sock(sk);
}
return ok;
}

tcp_connect用于构造syn包并发送之,发送之后需要设置syn包的重传定时器;

 /* 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; /* 检查重建路由 */
if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
return -EHOSTUNREACH; /* Routing failure or similar. */ /* 初始化控制块中与连接相关的成员 */
tcp_connect_init(sk); if (unlikely(tp->repair)) {
tcp_finish_connect(sk, NULL);
return ;
} /* 分配skb */
buff = sk_stream_alloc_skb(sk, , sk->sk_allocation, true);
if (unlikely(!buff))
return -ENOBUFS; /* 无数据的skb相关控制信息初始化 */
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN); /* 设置发送syn的时间 */
tp->retrans_stamp = tcp_time_stamp; /* 加入发送队列 */
tcp_connect_queue_skb(sk, buff); /* enc拥塞通告支持 */
tcp_ecn_send_syn(sk, buff); /* Send off SYN; include data in Fast Open. */
/* 发送syn */
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, , sk->sk_allocation);
if (err == -ECONNREFUSED)
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;
TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS); /* Timer for repeating the SYN until an answer. */
/* 启动重传定时器 */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
return ;
}

TCP主动打开 之 第一次握手-发送SYN的更多相关文章

  1. TCP被动打开 之 第一次握手-接收SYN

    假定客户端执行主动打开,服务器执行被动打开,客户端发送syn包到服务器,服务器接收该包,进行建立连接请求的相关处理,即第一次握手:本文主要分析第一次握手中被动打开端的处理流程,主动打开端的处理请查阅本 ...

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

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

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

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

  4. TCP主动打开 之 第三次握手-发送ACK

    假定客户端执行主动打开,并且已经收到服务器发送的第二次握手包SYN+ACK,在经过一系列处理之后,客户端发送第三次握手包ACK到服务器:其流程比较简单,主要是分配skb,初始化ack包并发送:需要注意 ...

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

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

  6. tcp 客户端 发送syn

    简介 sys_connect->inet_stream_connect->inet_stream_connect->tcp_v4_connect->tcp_connect对于t ...

  7. TCP连接建立系列 — 客户端发送SYN段

    主要内容:客户端调用connect()时的TCP层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd connect的TCP层实现 SOCK_STRE ...

  8. TCP/IP的三次握手和四次放手

    一开始个人对于三次握手和四次挥手这个东西还是有时候会忘记,可能理解的不是非常深刻,所以今天就自己动手来记录一下这个知识点,方便以后查看.总结完之后发现总结的还是可以的哈哈. 三次握手建立连接 第一次: ...

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

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

随机推荐

  1. 记 Win10 - Archlinux - Archlinux(Emergency) 三系统安装/配置注意事项

    起因是正常使用的archlinux做滚动更新,结果貌似有一个盘块写坏了(?). 手上没有U盘,进入不了linux,不好做fsck.于是直接就直接用win10了. 取消Fast Boot 当晚进入lin ...

  2. 解决 Ubuntu 19 安装openjdk 8后与openjfx不兼容

    小淘气放假了,孩子在上幼儿园的小朋友,报班也不能太变态嘛, 还是让他自己娱乐的时间多一点,但是现在在家的娱乐就是看电视,听说电视看多了越看越傻,就想方设法的给他找一点娱乐活动,把我闲置的树莓派给他装了 ...

  3. php打包下载以及断点续传

    php下载单文件 以及 多文件打包下载,支持断点续传 断点续传的功能未经验证 需要nginx或者apache服务器指定静态文件,png, mp4, zip等后缀文件的目录, 直接实例化并调用 down ...

  4. Use pkgsrc on ARM

    What is this page? This page describes how to use pkgsrc on ARM architecture with EABI support. I bo ...

  5. CentOS7.2安装Airflow

    1 安装pip yum -y install epel-release yum install python-pip 2 更新pip pip install --upgrade pip pip ins ...

  6. deep_learning_Function_ Matplotlib 3D 绘图函数 plot_surface 的 rstride 和 cstride 参数

    今晚开始接触 Matplotlib 的 3D 绘图函数 plot_surface,真的非常强大,图片质量可以达到出版级别,而且 3D 图像可以旋转 ,可以从不同角度来看某个 3D 立体图,但是我发现各 ...

  7. three.js之创建坐标系网格

    <!DOCTYPE html> <html> <head> <meta charset=utf-8> <title>My first thr ...

  8. [ZOJ 3076] Break Standard Weight

    题目连接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5009 题意:给你两个数字,可以把其中一个拆成两个数字,计算这三个数字 ...

  9. kotlin面向对象实战~

    有了java的面向对象的基础,其实对于kotlin这块的东东比较好理解,所以这里以洗衣机洗衣服为例,对面向对象进行一下实战,下面开始. 洗衣机初步: 首先先新建一个洗衣机类: 然后里面先定义基本属性: ...

  10. USRP B210 更改A通道或B通道

    USRP B210 更改A通道或B通道: 默认是A通道进行发射/接收,或设置 Mb0:Subdev Spec: A:A 设置B通道进行发射/接收,设置 Mb0:Subdev Spec: A:B