主要参考了《深入linux内核架构》和《精通Linux内核网络》相关章节
linux版本 4.12
// sock的状态
enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN,
TCP_CLOSING, /* 现在是有效状态 */
TCP_MAX_STATES /* 标志状态定义的结束! */
};
close->listen->sys_recv->established
close->sys_sent->established
int listen(int sockfd, int backlog);
Accept incoming connections and a queue limit for incoming connections.
backlog保存的是完成三次握手、等待accept的全连接的最大长度,而不是半连接的。
负载不高时,backlog不用太大。(For complete connections)
系统最大的、未处理的全连接数量为:min(backlog, somaxconn),net.core.somaxconn默认为128。
这个值最终存储于sk->sk_max_ack_backlog,sk->sk_ack_backlog 保存现有半连接队列大小。
net\socket.c
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
/* 通过文件描述符fd,找到对应的socket。
* 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
* 然后从file实例的private_data成员中获取socket实例。
*/
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
/* backlog不能超过系统参数somaxconn */
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
if (! err)
/* socket层的操作函数,如果是SOCK_STREAM的话,proto_ops是inet_stream_ops,
* 接下来调用的是inet_listen()。
*/
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
}
这是传输层提供给socket层(与协议无关层)的 listen 接口
net\ipv4\af_inet.c
/*
* Move a socket into listening state.
*/
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;
lock_sock(sk);
err = -EINVAL;
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM) // struct socket的检验
goto out;
old_state = sk->sk_state;
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN))) // struct sock的检验
goto out;
/* Really, if the socket is already in listen state
* we can only allow the backlog to be adjusted.
*/
if (old_state != TCP_LISTEN) {
/* Enable TFO w/o requiring TCP_FASTOPEN socket option.
* Note that only TCP sockets (SOCK_STREAM) will reach here.
* Also fastopen backlog may already been set via the option
* because the socket was in TCP_LISTEN state previously but
* was shutdown() rather than close().
*/
// 开启TFO(TCP FAST OPEN)选项
if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
(sysctl_tcp_fastopen & TFO_SERVER_ENABLE) &&
!inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
fastopen_queue_tune(sk, backlog);
tcp_fastopen_init_key_once(true);
}
/* 启动监听 */
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
}
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
EXPORT_SYMBOL(inet_listen);
启动监听时,做的工作主要包括:
创建半连接队列的实例,初始化全连接队列。
初始化sock的一些变量,把它的状态设为TCP_LISTEN。
检查端口是否可用,防止bind()后其它进程修改了端口信息。
把sock链接进入监听哈希表listening_hash中。
int inet_csk_listen_start(struct sock *sk, int backlog)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct inet_sock *inet = inet_sk(sk);
int err = -EADDRINUSE;
/* 初始化全连接队列 */
reqsk_queue_alloc(&icsk->icsk_accept_queue);
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk); /* icsk->icsk_ack结构清零 */
/* There is race window here: we announce ourselves listening,
* but this transition is still not validated by get_port().
* It is OK, because this socket enters to hash table only
* after validation is complete.
*/
sk_state_store(sk, TCP_LISTEN);
/* 检查端口是否仍然可用,防止bind()后其它进程修改了端口信息 */
if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
inet->inet_sport = htons(inet->inet_num);
sk_dst_reset(sk);
err = sk->sk_prot->hash(sk); /* 把sock链接入监听哈希表中 */
if (likely(!err))
return 0;
}
sk->sk_state = TCP_CLOSE;
return err;
}
EXPORT_SYMBOL_GPL(inet_csk_listen_start);
接收到消息(客户端收到SYN+ACK,或者是服务器端收到SYN)后,如果套接字处于TCP_ESTABLISHED状态,tcp_v4_rcv(接收TCP消息的函数)就会调用tcp_v4_do_rcv,tcp_v4_do_rcv来调用该方法。
这里线分析 服务器端收到SYN后的处理(第一次握手)
net\ipv4\tcp_input.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcphdr *th = tcp_hdr(skb); // 取出头部段
struct request_sock *req;
int queued = 0;
bool acceptable;
...
case TCP_LISTEN:
//服务器端收到SYN
/*
* 在半连接的LISTEN状态下,只处理SYN段。如果是
* ACK段,此时连接尚未开始建立,因此返回1。在调用
* tcp_rcv_state_process()函数中会给对方发送RST段;
* 如果接收的是RST段,则丢弃
*/
if (th->ack)
return 1;
if (th->rst)
goto discard;
if (th->syn) { // 收到syn
if (th->fin)
goto discard;
/*
* 处理SYN段,主要由conn_request接口(TCP中为tcp_v4_conn_request,接着调用tcp_conn_request)处理,
* icsk_af_ops成员在创建套接字时被初始化,参见tcp_v4_init_sock()
*/
/*收到三次握手的第一步SYN,
则在tcp_v4_conn_request中创建连接请求控制块request_sock
*/
/* 我们可能会处理来自 backlog 队列的 SYN 数据包,因此我们需要确保在此处禁用 BH。*/
local_bh_disable();
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
local_bh_enable();
if (!acceptable)
return 1;
consume_skb(skb);
return 0;
}
goto discard;
net\ipv4\tcp_input.c
...
/*
* 可以接收并处理连接请求,调用inet_reqsk_alloc()分配一个连接请求
* 块,用于保存连接请求信息,同时初始化在建立连接过程中用来发送
* ACK、RST段的操作集合,以便在建立连接过程中能方便地调用这些接口
*/
req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie); // 分配request_sock, 进入TCP_NEW_SYN_RECV状态
if (!req)
goto drop;
tcp_rsk(req)->af_specific = af_ops; // af_ops 为 全局静态常量tcp_request_sock_ipv4_ops
tcp_rsk(req)->ts_off = 0;
// TCP选项置空后初始化 mss_clamp和user_mss
tcp_clear_options(&tmp_opt);
tmp_opt.mss_clamp = af_ops->mss_clamp; // TCP_MSS_DEFAULT=536
tmp_opt.user_mss = tp->rx_opt.user_mss; // listen sock设置的或是tw的 ,用户在 ioctl 中请求的 mss
tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc); // 开启syncookie后则不用考虑fastopen, syncookie不允许使用tcp扩展
...
if (want_cookie) {
/*
* 如果启动了syncookies,则每60秒警告一次可能受
* synflood攻击,同时由客户端IP地址、客户端端口、
* 服务器IP地址、服务器端口、客户端初始序列号
* 等要素经hash运算后加密得到服务端初始化序列号
*/
//如果开启了syncookie选项,则需要检查收到的第三步ack和这个isn值是否一致
isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
//cookie_v4_init_sequence生成syncookie,并作为ack的起始序号
req->cookie_ts = tmp_opt.tstamp_ok;
if (!tmp_opt.tstamp_ok)
inet_rsk(req)->ecn_ok = 0;
}
tcp_rsk(req)->snt_isn = isn;
tcp_rsk(req)->txhash = net_tx_rndhash();
tcp_openreq_init_rwin(req, sk, dst);// 设置初始化rwnd
if (!want_cookie) {
tcp_reqsk_record_syn(sk, req, skb);// 如果设置保存TCP_SAVE_SYN标记,则保存
fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst);
// 验证后创建fastopen sock,并把数据部分放入接收队列中
}
if (fastopen_sk) {// 验证并创建fastsocket成功, 进入TCP_SYN_RCV状态
af_ops->send_synack(fastopen_sk, dst, &fl, req,
&foc, TCP_SYNACK_FASTOPEN);//tcp_v4_send_synac
/* Add the child socket directly into the accept queue */
inet_csk_reqsk_queue_add(sk, req, fastopen_sk);// 直接添加到等待accept的队列
sk->sk_data_ready(sk);
bh_unlock_sock(fastopen_sk);
sock_put(fastopen_sk);
} else {
tcp_rsk(req)->tfo_listener = false;
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);// 调用reqsk_queue_hash_req,转成socket *插入ehash,并设置定时器
af_ops->send_synack(sk, dst, &fl, req, &foc,
!want_cookie ? TCP_SYNACK_NORMAL :
TCP_SYNACK_COOKIE);// tcp_v4_send_synack,发送synack
if (want_cookie) {
reqsk_free(req);// 启用syncookie的话,可以直接释放req
return 0;
}
}
reqsk_put(req);
return 0;
...
...
lookup:
/* 查找控制块 查找传输控制块 先查找ehash 然后查找listen hash*/
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, &refcounted);
if (!sk)
goto no_tcp_socket;
process:
/* TIME_WAIT转过去处理 */
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
/*
三次握手的第二阶段,服务器发送synack后,
会进入TCP_NEW_SYN_RECV状态,并插入ehash中。
收到握手最后一个ack后,会找到TCP_NEW_SYN_RECV状态的req
,然后创建一个新的sock进入TCP_SYN_RECV状态
,最终进入TCP_ESTABLISHED状态. 并放入accept队列通知select/epoll
*/
/* TCP_NEW_SYN_RECV状态处理 */
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk);
struct sock *nsk;
/* 获取控制块 */
sk = req->rsk_listener;
if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {
reqsk_put(req);
goto discard_it;
}
if (unlikely(sk->sk_state != TCP_LISTEN)) {
/* 从连接队列移除控制块 */
inet_csk_reqsk_queue_drop_and_put(sk, req);
goto lookup;/* 根据skb参数重新查找控制块 */
}
/* We own a reference on the listener, increase it again
* as we might lose it too soon.
*/
sock_hold(sk);
refcounted = true;
/* 处理第三次握手ack,成功返回新控制块 */
nsk = tcp_check_req(sk, skb, req, false); // 创建新的sock进入TCP_SYN_RECV state 并插入accept队列
if (!nsk) {
reqsk_put(req);
goto discard_and_relse;
}
/* 未新建控制块,进一步处理 */
if (nsk == sk) {
reqsk_put(req);
/*tcp_child_process中调用tcp_rcv_state_process来
处理TCP_SYN_RECV状态的child sock
。并进入TCP_ESTABLISHED状态*/
} else if (tcp_child_process(sk, nsk, skb)) {
//nsk进入ehash
/* 有新建控制块,进行初始化等 */
tcp_v4_send_reset(nsk, skb); /* 失败发送rst */
goto discard_and_relse;
} else {
sock_put(sk);
return 0;
}
}
...
}
服务器接收到消息(ACK)后(第三次握手),如果套接字处于TCP_ESTABLISHED状态,tcp_v4_rcv(接收TCP消息的函数)就会调用tcp_v4_do_rcv(tcp_v4_do_rcv来调用该方法)或者直接调用该方法。
收到握手最后一个ack后,会找到TCP_NEW_SYN_RECV状态的req,然后创建一个新的sock进入TCP_SYN_RECV状态,最终进入TCP_ESTABLISHED状态, 并放入accept队列通知select/epoll
net\ipv4\tcp_minisocks.c
/*
* Queue segment on the new socket if the new socket is active, otherwise we just shortcircuit this and continue with the new socket.
*
* For the vast majority of cases child->sk_state will be TCP_SYN_RECV when entering. But other states are possible due to a race condition where after __inet_lookup_established() fails but before the listener locked is obtained, other packets cause the same connection to be created.
*/
int tcp_child_process(struct sock *parent, struct sock *child,
struct sk_buff *skb)
{
int ret = 0;
int state = child->sk_state;
/* record NAPI ID of child */
sk_mark_napi_id(child, skb);
tcp_segs_in(tcp_sk(child), skb);
// 如果用户进程没有锁住child,则让child重新处理该ACK报文,这可以让child
// 套接字由TCP_SYN_RECV迁移到TCP_ESTABLISH状态
if (!sock_owned_by_user(child)) {
ret = tcp_rcv_state_process(child, skb);
// child套接字状态发生了迁移,唤醒监听套接字上的进程,可能由于调用accept()而block
if (state == TCP_SYN_RECV && child->sk_state != state)
parent->sk_data_ready(parent);
} else {
/* Alas, it is possible again, because we do lookup
* in main socket hash table and lock on listening
* socket does not protect us more.
*/
// 缓存该skb后续处理
__sk_add_backlog(child, skb);
}
bh_unlock_sock(child);
sock_put(child);
return ret;
}
EXPORT_SYMBOL(tcp_child_process);
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
...
switch (sk->sk_state) {
case TCP_SYN_RECV:
if (!acceptable)
return 1;
if (!tp->srtt_us)
tcp_synack_rtt_meas(sk, req);
/* Once we leave TCP_SYN_RECV, we no longer need req so release it. */
if (req) {
tp->total_retrans = req->num_retrans;
reqsk_fastopen_remove(sk, req, false); // 回收fastopen req
} else {
/* Make sure socket is routed, for correct metrics. */
icsk->icsk_af_ops->rebuild_header(sk); // 调用inet_sk_rebuild_header或inet6_sk_rebuild_header,根据ACK包的信息重新计算路由
tcp_init_congestion_control(sk); // 初始化拥塞控制算法
tcp_mtup_init(sk); // 初始化MTU探测功能
tp->copied_seq = tp->rcv_nxt;
tcp_init_buffer_space(sk);// 初始化接收缓存和发送缓存的空间
}
smp_mb();
tcp_set_state(sk, TCP_ESTABLISHED); // TCP_SYN_RECV->TCP_ESTABLISHED
/*
* 发信号给那些将通过该套接字发送数据的进程,
* 通知他们套接字目前已经可以发送数据了
sk_state_change()->sock_def_wakeup()->ep_poll_callback(), 添加到epoll的ready list中,并唤醒阻塞中的epoll。
epoll然后调用ep_send_events->ep_scan_ready_list->ep_send_events_proc->ep_item_poll->tcp_poll
*/
sk->sk_state_change(sk); // sock_def_wakeup, 唤醒epoll
// 进行其他的初始化,如初始化传输控制块各字段
...
break
}
...
/* step 7: process the segment text */
switch (sk->sk_state) {
...
case TCP_ESTABLISHED:
tcp_data_queue(sk, skb); // 如果带数据部分则处理,比如客户端设置了deferaccept的时候
queued = 1;
break;
}
/* tcp_data could move socket to TIME-WAIT */
if (sk->sk_state != TCP_CLOSE) {
tcp_data_snd_check(sk); // 给数据一个发送机会,tcp_push_pending_frame
tcp_ack_snd_check(sk); // 检查是否有ack被推迟,判断是否需要立即发送
}
if (!queued) {
discard:
tcp_drop(sk, skb);
}
return 0
}
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
int, addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed); /* 查找文件句柄对应的socket */
if (!sock)
goto out;
/* 从用户态复制地址参数到内核中 */
err = move_addr_to_kernel(uservaddr, addrlen, &address);
if (err < 0)
goto out_put;
/* 安全审计 */
err =
security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
if (err)
goto out_put;
/* 调用传输层的connet方法inet_stream_connect或inet_dgram_connect */
err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
sock->file->f_flags);
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
inet_stream_connect加了个锁(锁住socket的sock)然后调用了__inet_stream_connect
__inet_stream_connect()函数主要功能如下:
(1)调用tcp_v4_connect函数建立与服务器联系并发送SYN段;
(2)获取连接超时时间timeo,如果timeo不为0,则会调用inet_wait_for_connect一直等待到连接成功或超时;
/*
* Connect to a remote host. There is regrettably still a little
* TCP 'magic' in here.
*/
int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags, int is_sendmsg)
{
struct sock *sk = sock->sk;
int err;
long timeo;
/*
* uaddr 可以为 NULL 并且 addr_len 可以为 0 如果: sk 是 TCP fastopen 活动套接字并且 TCP_FASTOPEN_CONNECT sockopt 已设置并且我们已经有此套接字的有效 cookie。
* 在这种情况下,用户可以在 connect() 之后调用 write()。 write() 将调用调用 __inet_stream_connect() 的 tcp_sendmsg_fastopen()。
*/
if (uaddr) { // uaddr不为空
if (addr_len < sizeof(uaddr->sa_family))
return -EINVAL;
if (uaddr->sa_family == AF_UNSPEC) { /* 未指定地址类型,错误 */
err = sk->sk_prot->disconnect(sk, flags);
sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
goto out;
}
}
switch (sock->state) {
default:
err = -EINVAL;
goto out;
case SS_CONNECTED: /* 已经与对方端口连接 */
err = -EISCONN;
goto out;
case SS_CONNECTING: /* 正在连接过程中 */
if (inet_sk(sk)->defer_connect)
err = is_sendmsg ? -EINPROGRESS : -EISCONN;
else
err = -EALREADY;
/* Fall out of switch with err, set for this state */
break;
case SS_UNCONNECTED: /* 只有此状态才能调用connect */
err = -EISCONN;
if (sk->sk_state != TCP_CLOSE) /* 如果不是TCP_CLOSE状态,说明已经连接了 */
goto out;
/* 调用传输层接口tcp_v4_connect建立与服务器连接,并发送SYN段 */
err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)
goto out;
/* 发送SYN段后,设置状态为SS_CONNECTING */
sock->state = SS_CONNECTING;
/* 表示设置了 fastopen_connect 并且存在 cookie,因此我们将连接推迟到写入第一个数据帧 */
if (!err && inet_sk(sk)->defer_connect)
goto out;
/* Just entered SS_CONNECTING state; the only
* difference is that return value in non-blocking
* case is EINPROGRESS, rather than EALREADY.
*/
/* 如果是以非阻塞方式进行连接,则默认的返回值为EINPROGRESS,表示正在连接 */
err = -EINPROGRESS;
break;
}
/* 获取连接超时时间,如果指定非阻塞方式,则不等待直接返回 */
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
/* 发送完SYN后,连接状态一般为这两种状态,但是如果连接建立非常快,则可能越过这两种状态 */
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
tcp_sk(sk)->fastopen_req &&
tcp_sk(sk)->fastopen_req->data ? 1 : 0;
/* Error code is set above */
if (!timeo || !inet_wait_for_connect(sk, timeo, writebias)) /* 等待连接完成或超时 */
goto out;
err = sock_intr_errno(timeo);
if (signal_pending(current))
goto out;
}
/* Connection was closed by RST, timeout, ICMP error
* or another process disconnected us.
*/
if (sk->sk_state == TCP_CLOSE) /* 运行到这里说明连接建立失败 */
goto sock_error;
/* sk->sk_err may be not zero now, if RECVERR was ordered by user
* and error was received after socket entered established state.
* Hence, it is handled normally after connect() return successfully.
*/
sock->state = SS_CONNECTED; /* 连接建立成功,设置为已经连接状态 */
err = 0;
out:
return err;
sock_error:
err = sock_error(sk) ? : -ECONNABORTED;
sock->state = SS_UNCONNECTED;
if (sk->sk_prot->disconnect(sk, flags))
sock->state = SS_DISCONNECTING;
goto out;
}
EXPORT_SYMBOL(__inet_stream_connect);
net\ipv4\tcp_ipv4.c
/* 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;
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;
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)) { /* 对TCP来说,不能使用多播和组播路由项 */
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 = 0;
tp->rx_opt.ts_recent_stamp = 0;
if (likely(!tp->repair))
tp->write_seq = 0;
}
/* 设置目的地址和目标端口 */
inet->inet_dport = usin->sin_port;
sk_daddr_set(sk, daddr);
/* 设置IP首部选项长度 */
inet_csk(sk)->icsk_ext_hdr_len = 0;
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_set_state(sk, TCP_SYN_SENT); /* 设置为TCP_SYN_SENT状态 */
err = inet_hash_connect(tcp_death_row, sk); /* 将传输控制添加到ehash散列表中,并动态分配端口 */
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); /* 设置路由,并根据路由更新网络设备的特性 */
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);
}
/* 根据初始序号和当前时间,随机算一个初始id */
inet->inet_id = tp->write_seq ^ jiffies;
if (tcp_fastopen_defer_connect(sk, &err))
return err;
if (err)
goto failure;
/* 建立一个SYN段并调用tcp_transmit_skb发送 */
err = tcp_connect(sk);
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);
对于协议栈的接收 synack 路径
当客户端connect()之后,sock进入TCP_SYN_SENT状态,并插入到ehash中, 如果是阻塞socket则connect()等待握手完成
本文考虑收到服务端synack的过程,也就是客户端握手的第二阶段;
发送SYN段后,连接的状态变为SYN_SENT。此时如果收到SYNACK段,处理函数为tcp_rcv_state_process()。
连接状态不为ESTABLISHED或TIME_WAIT时的处理函数为tcp_rcv_state_process()。
tcp_rcv_synsent_state_process()用于SYN_SENT状态的处理,具体又分两种场景
(1) 接收到SYNACK
一般情况下会收到服务端的SYNACK,处理如下:
检查ack_seq是否合法。如果使用了时间戳选项,检查回显的时间戳是否合法。检查TCP的标志位是否合法。如果SYNACK是合法的,更新sock的各种信息。
把连接的状态设置为TCP_ESTABLISHED,唤醒调用connect()的进程。判断是马上发送ACK,还是延迟发送。
(2) 接收到SYN
本端之前发送出一个SYN,现在又接收到了一个SYN,双方同时向对端发起建立连接的请求。
处理如下:把连接状态置为SYN_RECV。更新sock的各种信息。构造和发送SYNACK。接者对端也会回应SYNACK,之后的处理流程和服务器端接收ACK类似
当tcp_rcv_synsent_state_process()的返回值大于0时,会导致上层调用函数发送一个被动的RST。
Q:那么什么情况下此函数的返回值会大于0?
A:收到一个ACK段,但ack_seq的序号不正确,或者回显的时间戳不正确。
同时打开时,在SYN_SENT状态,收到SYN段后,状态变为SYN_RECV,然后发送SYNACK。之后如果收到合法的SYNACK后,就能完成连接的建立。
对于TCP_SYN_SENT状态的sock,会调用tcp_rcv_synsent_state_process来进行处理
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
struct inet_connection_sock *icsk = inet_csk(sk); // 客户端sk
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_fastopen_cookie foc = { .len = -1 };
int saved_clamp = tp->rx_opt.mss_clamp;
bool fastopen_fail;
tcp_parse_options(skb, &tp->rx_opt, 0, &foc); // 解析tcp选项,可能带fastopen cookie
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
tp->rx_opt.rcv_tsecr -= tp->tsoffset; // 在repair模式下的时间修正
if (th->ack) {
/* rfc793:
* "If the state is SYN-SENT then
* first check the ACK bit
* If the ACK bit is set
* If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
* a reset (unless the RST bit is set, if so drop
* the segment and return)"
*/
if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) || // 初始化的时候snd_una设置为syn序号,返回的ack为syn+1, 或者fastopen的时候更大
after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) // ack的是还没有发送的数据
goto reset_and_undo;
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
!between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, // retrans_stamp会在发送syn的时候记录,接收包需要在时间范围内
tcp_time_stamp)) {
NET_INC_STATS(sock_net(sk),
LINUX_MIB_PAWSACTIVEREJECTED);
goto reset_and_undo;
}
/* Now ACK is acceptable.
*
* "If the RST bit is set
* If the ACK was acceptable then signal the user "error:
* connection reset", drop the segment, enter CLOSED state,
* delete TCB, and return."
*/
if (th->rst) {
tcp_reset(sk); // 进入TCP_CLOSE状态
goto discard; // 丢弃包
}
/* rfc793:
* "fifth, if neither of the SYN or RST bits is set then
* drop the segment and return."
*
* See note below!
* --ANK(990513)
*/
if (!th->syn) // 如果rst和syn都没被设置,则丢弃并返回
goto discard_and_undo;
/* rfc793:
* "If the SYN bit is on ...
* are acceptable then ...
* (our SYN has been ACKed), change the connection
* state to ESTABLISHED..."
*/
tcp_ecn_rcv_synack(tp, th);
tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
tcp_ack(sk, skb, FLAG_SLOWPATH); // ack确认,有可能fastopen的数据被确认了
/* Ok.. it's good. Set up sequence numbers and
* move to established.
*/
tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
/* RFC1323: The window in SYN & SYN/ACK segments is
* never scaled.
*/
tp->snd_wnd = ntohs(th->window); // 更新收到的窗口通告
if (!tp->rx_opt.wscale_ok) { // 如果对方不支持wsacle
tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0; // 本机发送给对方的最大窗口也不能带wscale的大小
tp->window_clamp = min(tp->window_clamp, 65535U);
}
if (tp->rx_opt.saw_tstamp) { /* 有时间戳选项 */
tp->rx_opt.tstamp_ok = 1; /* 设置时间戳选项 */
tp->tcp_header_len =
sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; /* tcp首部需要增加时间戳长度 */
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; /* mss需要减去时间戳长度 */
tcp_store_ts_recent(tp); /* 设置回显时间戳 */
} else {
tp->tcp_header_len = sizeof(struct tcphdr);
}
if (tcp_is_sack(tp) && sysctl_tcp_fack) // 服务端支持sack,并且系统支持fack,则开启fack
tcp_enable_fack(tp);
tcp_mtup_init(sk); // 此时收到对方的tcp MSS选项,可以初始化mtu探测区间
tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); // 使用pmtu更新探测区间和mss
tcp_initialize_rcv_mss(sk); // 更新对对方mss的猜测,不会超过TCP_MSS_DEFAULT=536
/* Remember, tcp_poll() does not lock socket!
* Change state from SYN-SENT only after copied_seq
* is initialized. */
/* 记录用户空间待读取的序号 */
tp->copied_seq = tp->rcv_nxt;
smp_mb();
// tcp_finish_connect主要是客户端进入连接完成状态(TCP_ESTABLISHED),可以发送数据了
tcp_finish_connect(sk, skb); // TCP_SYN_SENT->TCP_ESTABLISHED
fastopen_fail = (tp->syn_fastopen || tp->syn_data) && // fastopen处理,保存cookie
tcp_rcv_fastopen_synack(sk, skb, &foc);
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk); /* 指向sock_def_wakeup,唤醒调用connect()的进程 */
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); /* 如果使用了异步通知,则发送SIGIO通知进程可写 */
}
if (fastopen_fail)
return -1;
if (sk->sk_write_pending || // 还有数据等待写
icsk->icsk_accept_queue.rskq_defer_accept || // client设置了TCP_DEFER_ACCEPT, 先不ack,等待有数据发送的时候
icsk->icsk_ack.pingpong) { // pingpong模式,没有开启快速ack
// 延时ack,等待数据一起发送
/* Save one ACK. Data will be ready after
* several ticks, if write_pending is set.
*
* It may be deleted, but with this feature tcpdumps
* look so _wonderfully_ clever, that I was not able
* to stand against the temptation 8) --ANK
*/
inet_csk_schedule_ack(sk); // 标记有ack被推迟
tcp_enter_quickack_mode(sk); // 进入快速ack模式,加速慢启动
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, // 重置延时ack定时器
TCP_DELACK_MAX, TCP_RTO_MAX);
discard:
tcp_drop(sk, skb);
return 0;
} else {
tcp_send_ack(sk); // 不需要等待,立即发送ack
}
return -1;
}
/* No ACK in the segment */
// 没有ack,但是设置rst, 忽略这个包
if (th->rst) {
/* rfc793:
* "If the RST bit is set
*
* Otherwise (no ACK) drop the segment and return."
*/
goto discard_and_undo;
}
/* PAWS check. */
if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
tcp_paws_reject(&tp->rx_opt, 0))
goto discard_and_undo;
if (th->syn) { /* 收到了SYN段,即同时打开 */ //相互connect
/* We see SYN without ACK. It is attempt of
* simultaneous connect with crossed SYNs.
* Particularly, it can be connect to self.
*/
/* 发送SYN后,状态为SYN_SENT,如果此时也收到SYN,
* 状态则变为SYN_RECV。
*/
tcp_set_state(sk, TCP_SYN_RECV);
if (tp->rx_opt.saw_tstamp) {
tp->rx_opt.tstamp_ok = 1;
tcp_store_ts_recent(tp); /* 记录对端的时间戳,作为下次发送的回显值 */
tp->tcp_header_len =
sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
} else {
tp->tcp_header_len = sizeof(struct tcphdr);
}
tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; /* 更新接收窗口的要接收的下一个序号 */
tp->copied_seq = tp->rcv_nxt;
tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; /* 更新接收窗口的左端 */
/* RFC1323: The window in SYN & SYN/ACK segments is
* never scaled.
*/
/* 更新对端接收窗口的大小。在三次握手时,不使用窗口扩大因子。*/
tp->snd_wnd = ntohs(th->window);
tp->snd_wl1 = TCP_SKB_CB(skb)->seq; /* 记录最近更新发送窗口的ACK序号 */
tp->max_window = tp->snd_wnd; /* 目前见过的对端的最大通告窗口 */
tcp_ecn_rcv_syn(tp, th);
tcp_mtup_init(sk); /* TCP的MTU初始化 mss更新 */
tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
tcp_initialize_rcv_mss(sk); /* 对端有效发送MSS估值的初始化 */
tcp_send_synack(sk);
#if 0
/* Note, we could accept data and URG from this segment.
* There are no obstacles to make this (except that we must
* either change tcp_recvmsg() to prevent it from returning data
* before 3WHS completes per RFC793, or employ TCP Fast Open).
*
* However, if we ignore data in ACKless segments sometimes,
* we have no reasons to accept it sometimes.
* Also, seems the code doing it in step6 of tcp_rcv_state_process
* is not flawless. So, discard packet for sanity.
* Uncomment this return to process the data.
*/
return -1;
#else
goto discard;
#endif
}
/* "fifth, if neither of the SYN or RST bits is set then
* drop the segment and return."
*/
discard_and_undo:
tcp_clear_options(&tp->rx_opt);
tp->rx_opt.mss_clamp = saved_clamp;
goto discard;
reset_and_undo:
tcp_clear_options(&tp->rx_opt);
tp->rx_opt.mss_clamp = saved_clamp;
return 1;
}
tcp_finish_connect()用来完成连接的建立,主要做了以下事情:
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
tcp_set_state(sk, TCP_ESTABLISHED); /* 设置为已连接状态 */
if (skb) { /* 设置接收路由缓存 */
icsk->icsk_af_ops->sk_rx_dst_set(sk, skb); // inet_sk_rx_dst_set
security_inet_conn_established(sk, skb);
}
/* Make sure socket is routed, for correct metrics. */
icsk->icsk_af_ops->rebuild_header(sk); /* 检查或重建路由 */
/* 根据路由缓存,初始化TCP相关变量 */
tcp_init_metrics(sk);
tcp_init_congestion_control(sk); // 调用拥塞算法init函数
/* Prevent spurious tcp_cwnd_restart() on first data
* packet.
*/
tp->lsndtime = tcp_time_stamp; /* 记录最后一次发送数据包的时间 */
tcp_init_buffer_space(sk); // 根据收到的对端信息初始化缓存配置
if (sock_flag(sk, SOCK_KEEPOPEN)) /* 开启了保活,则打开保活定时器 */
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));
/* 如果对端的窗口扩大因子为0 */
if (!tp->rx_opt.snd_wscale)
__tcp_fast_path_on(tp, tp->snd_wnd); /* 设置首部预测字段 */
else
tp->pred_flags = 0;
}
使用的内核版本不太同
tcp 客户端 synack的接收 以及 相互connect
tcp syn-synack-ack 服务端 接收 SYN tcp_v4_do_rcv分析
tcp syn-synack-ack 服务端发送syn-ack
…