/*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, intaddr_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;interr;struct ip_options_rcu *inet_opt;if (addr_len < sizeof(structsockaddr_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);returnerr;
}/*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)gotofailure;
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;gotofailure;
}/*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)gotofailure;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;returnerr;
}
EXPORT_SYMBOL(tcp_v4_connect);