TCP在FIN_WAIT1状态到底能持续多久以及TCP假连接问题

近期遇到一个问题,简单点说,主机A上显示一条ESTABLISHED状态的TCP连接到主机B,而主机B上却没有任何关于主机A的连接信息,经查明,这是由于主机A和主机B的发送/接收缓冲区差异巨大,导致主机B进程退出后,主机A暂时憋住,主机B频繁发送零窗口探测,FIN_WAIT1状态超时,进而连接被销毁,然而主机A并不知情导致。

正好昨天也有人咨询另外一个类似的问题,那么就抽昨晚和今天早上的时间,写一篇总结吧。


TCP处处是坑!

不要觉得你对TCP的实现的代码烂熟于心了就能把控它的所有行为!不知道你有没有发现,目前市面上新上市的关于Linux内核协议栈的书可谓是汗牛充栋,然而无论作者是国内的还是国外,几乎都是碰到TCP就草草略过,反而对IP,ARP,DNS这些大书特书,Why?因为Linux内核里TCP的代码太乱太复杂了,很少有人能看明白80%以上的,即便真的有看过的,其中还包括只懂代码而不懂网络技术的,我就发现很多声称自己精通Linux内核TCP/IP源码,结果竟然不知道什么是默认路由…

所以我打算写一篇文章,趁着这个FIN_WAIT1问题,顺便表达一下我是如何学习网络技术,我是如何解决网络问题的方法论观点,都是形而上,个人看法:

  • 设计覆盖全面的复现实验
  • 通读协议标准文档,理解实现建议
  • 再次实验,预测并确认问题以外的现象
  • 核对代码实现,跟踪代码的Changelog
  • 写一个自己的实现或者乱改代码

本文聊聊TCP的FIN_WAIT1以及TCP假连接(死连接)问题。先看FIN_WAIT1。


首先还是从状态机入手,看看和FIN_WAIT1相关的状态机转换图:

这里写图片描述

我们只考虑常规的从ESTABLISHED状态的转换,很简单的一个单一状态转换:

  • ESTAB状态发送FIN即切换到FIN_WAIT1状态;
  • FIN_WAIT1状态下收到针对FIN的ACK即可离开FIN_WAIT1到达FIN_WAIT2.

看一下和上述状态机转换相关的简单时序图:

这里写图片描述

从状态图和时序图上,我们很明确地可以看到,FIN_WAIT1持续1个RTT左右的时间!这个时间段几乎不会被肉眼观察到,转瞬而即逝。

然而,这是真的吗?

我们之所以得到FIN_WAIT1持续1个RTT这个结论,基于两个假设,即:

  1. TCP的对端是一个正常的TCP端;
  2. 两端TCP之间的链路是正常的,可达的。

OK,接下来我们来设计一个实验模拟异常的情况。准备实验拓扑如下:

这里写图片描述

host1和host2的系统内核版本(uname -r获取):

3.10.0-862.2.3.el7.x86_64

首先,我们看一下如果对端TCP针对FIN发送的ACK丢失,会发生什么。按照上述的时序图,正常应该是FIN_WAIT1将会永久持续。我们来验证一下。

  • 实验1:模拟ACK丢失
    在host1上做以下命令:
nc -l -p 1234

host2上完成以下命令:

cat /dev/zero|nc 1.1.1.1 1234

以上保证了host1和host2之间的TCP建立并且连接之间有持续的数据传输。接下来,在host2上执行下列动作:

iptables -A INPUT -p tcp --tcp-flags ACK,FIN ACK
killall nc

此时在host2上:

[root@localhost ~]# netstat  -antp|grep 1234
tcp        0   1229 1.1.1.2:39318               1.1.1.1:1234                FIN_WAIT1   - 

连续上翻命令,这个FIN_WAIT1均不会消失,暂时符合我们的预期…出去抽根烟,刷会儿微博…回来后,发现这个FIN_WAIT1消失了!

它是如何消失的呢?这个时候,我们提取netstat数据,执行“ netstat -st”,会发现:

TcpExt:
...
    1 connections aborted due to timeout

多了一条timeout连接!


我这里直接说答案吧。

虽然说在协议上规范上看,TCP没有必要为链路或者说对端的不合常规的行为而买单,但是从现实角度,TCP的实现必须处理异常情况,TCP的实现必然要有所限制!

我们知道,计算机是无法处理无限,无穷这种抽线的数学概念的,所有如果针对FIN的ACK迟迟不来,那么必然要有一个等待的极限,这个极限在Linux内核协议栈中由以下参数控制:

net.ipv4.tcp_orphan_retries # 默认值是0!这里有坑...

这个参数表示如果一直都收不到针对FIN的ACK,那么在彻底销毁这个FIN_WAIT1的连接前,等待几轮RTO退避

所谓的orphan tcp connection,意思就是说,在Linux进程层面,创建该连接的进程已经退出销毁了,然而在TCP协议层面,它依然在遵循TCP状态机的转换规则存在着。

注意,这个参数不是一个时间量,而是一个次数量。我们知道,TCP每一次超时,都会对下一次超时时间进行指数退避,这里的次数量就是要经过几次退避的时间。举一个例子,如果RTO是2ms,而tcp_orphan_retries 的值是4,那么所计算出的FIN_WAIT1容忍时间就是:
T=21+22+23+24 T = 2 1 + 2 2 + 2 3 + 2 4
还是看看Linux内核文档怎么说的吧:

tcp_orphan_retries - INTEGER
  This value influences the timeout of a locally closed TCP connection, when RTO retransmissions remain unacknowledged.
  See tcp_retries2 for more details.
  The default value is 8.
  If your machine is a loaded WEB server,
  you should think about lowering this value, such sockets
  may consume significant resources. Cf. tcp_max_orphans.

让我们看看tcp_retries2,以获取数值的含义:

tcp_retries2 - INTEGER
  This value influences the timeout of an alive TCP connection,
  when RTO retransmissions remain unacknowledged.
  Given a value of N, a hypothetical TCP connection following
  exponential backoff with an initial RTO of TCP_RTO_MIN would
  retransmit N times before killing the connection at the (N+1)th RTO.
  The default value of 15 yields a hypothetical timeout of 924.6
  seconds and is a lower bound for the effective timeout.
  TCP will effectively time out at the first RTO which exceeds the hypothetical timeout.
  RFC 1122 recommends at least 100 seconds for the timeout,
  which corresponds to a value of at least 8.

虽然说文档上默认值的建议是8,但是大多数的Linux发行版上其默认值都是0。更多详情,就自己看RFC和Linux源码吧。

有了这个参数保底,我们知道,即便是ACK永远不来,FIN_WAIT1状态也不会一直持续下去的,这有效避免了有针对性截获ACK或者不发送ACK而导致的DDoS,退一万步讲,即便是没有DDoS,这种做法也具有资源利用率的容错性,使得资源使用更加高效。

实验1的结论如下:

  • 如果主动断开端调用了close关掉了进程,它会进入FIN_WAIT1状态,此时如果它再也收不到ACK,无论是针对pending在发送缓冲的数据还是FIN,它都会尝试重新发送,在收到ACK前会尝试N次退避,该N由tcp_orphan_retries参数控制。

接下来,我们来看一个更加复杂一点的问题,还是先从实验说起。

  • 实验2:模拟对端TCP不收数据,接收窗口憋死
    在host1上做以下命令:
# 模拟小接收缓存,使得憋住接收窗口更加容易
sysctl -w net.ipv4.tcp_rmem="16  32  32"
nc -l -p 1234

host2上完成以下命令:


cat /dev/zero|nc 1.1.1.1 1234
sleep 5 # 稍微等一下
killall nc

此时,我们发现host2的TCP连接进入了FIN_WAIT1状态。然而抓包看的话,数据传输依然在进行:

05:15:51.674630 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 305:321, ack 1, win 5840, options [nop,nop,TS val 1210945 ecr 238593370], length 16
05:15:51.674690 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 321, win 0, options [nop,nop,TS val 238593471 ecr 1210945], length 0
05:15:51.674759 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 321, win 16, options [nop,nop,TS val 238593471 ecr 1210945], length 0
05:15:51.777774 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 321:325, ack 1, win 5840, options [nop,nop,TS val 1211048 ecr 238593471], length 4
05:15:51.777874 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 325, win 16, options [nop,nop,TS val 238593497 ecr 1211048], length 0
05:15:52.182918 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 325:341, ack 1, win 5840, options [nop,nop,TS val 1211453 ecr 238593497], length 16
05:15:52.182970 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 341, win 0, options [nop,nop,TS val 238593599 ecr 1211453], length 0
05:15:52.183055 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 341, win 16, options [nop,nop,TS val 238593599 ecr 1211453], length 0
05:15:52.592759 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 341:357, ack 1, win 5840, options [nop,nop,TS val 1211863 ecr 238593599], length 16
05:15:52.592813 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 357, win 0, options [nop,nop,TS val 238593701 ecr 1211863], length 0
05:15:52.592871 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 357, win 16, options [nop,nop,TS val 238593701 ecr 1211863], length 0
05:15:52.695160 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 357:361, ack 1, win 5840, options [nop,nop,TS val 1211965 ecr 238593701], length 4
05:15:52.695276 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 361, win 16, options [nop,nop,TS val 238593727 ecr 1211965], length 0
05:15:53.099612 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 361:377, ack 1, win 5840, options [nop,nop,TS val 1212370 ecr 238593727], length 16
05:15:53.099641 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 377, win 0, options [nop,nop,TS val 238593828 ecr 1212370], length 0
05:15:53.099671 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 377, win 16, options [nop,nop,TS val 238593828 ecr 1212370], length 0
05:15:53.505028 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 377:393, ack 1, win 5840, options [nop,nop,TS val 1212775 ecr 238593828], length 16
05:15:53.505081 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 393, win 0, options [nop,nop,TS val 238593929 ecr 1212775], length 0
05:15:53.505138 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 393, win 16, options [nop,nop,TS val 238593929 ecr 1212775], length 0
05:15:53.605923 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [P.], seq 393:397, ack 1, win 5840, options [nop,nop,TS val 1212876 ecr 238593929], length 4

这是显然的,这是因为收发两端巨大的缓存大小差异造成的,即便是host2发送端进程退出了,在退出前已经有大量数据pending到了TCP的发送缓冲区里面而脱离已经被销毁的进程了,FIN包当然是排在了缓冲区的末尾了。

TCP的状态机运行在缓存的上层,即只要把FIN包pending排队,就切换到了FIN_WAIT1,而不是说实际发送了FIN包才切换。

因此,我们可有的等了,数据传输依然在正常有序进行,针对小包的ACK源源不断从host1回来,这进一步促进host2发送未竟的数据包,直到所有缓冲区的数据全部发送完毕…

不管怎样,总是有个头儿,只要有结束,就不需要担心。我们可以简单得出一个结论:

  • 如果主动断开端调用了close关掉了进程,它会进入FIN_WAIT1状态,如果接收端的接收窗口呈现打开状态,此时它的TCP发送队列中的数据包还是会像正常一样发往接收端,直到发送完,最后发送FIN包,收到FIN包ACK后进入FIN_WAIT2。

现在,我们进行实验的下一步,把host1上的接收进程nc的接收逻辑彻底憋死。很简单,host1上执行下面的命令即可:

killall -STOP nc

进程并没有退出,只是暂停了,nc进程上下文的recv不再执行,然而软中断上下文的TCP协议的处理依然在进行。

这个时候,抓包就会发现只剩下指数时间退避的零窗口探测包了:

# 注意观察探测包发送时间的间隔
05:15:56.444570 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1215715 ecr 238594487], length 0
05:15:56.444602 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238594664 ecr 1214601], length 0
05:15:57.757217 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1217027 ecr 238594664], length 0
05:15:57.757248 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238594992 ecr 1214601], length 0
05:16:00.283259 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1219552 ecr 238594992], length 0
05:16:00.283483 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238595624 ecr 1214601], length 0
05:16:05.234277 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1224503 ecr 238595624], length 0
05:16:05.234305 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238596861 ecr 1214601], length 0
05:16:15.032486 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1234301 ecr 238596861], length 0
05:16:15.032532 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238599311 ecr 1214601], length 0
05:16:34.629137 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1253794 ecr 238599311], length 0
05:16:34.629164 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238604210 ecr 1214601], length 0
05:17:13.757815 IP 1.1.1.2.39318 > 1.1.1.1.1234: Flags [.], ack 1, win 5840, options [nop,nop,TS val 1292784 ecr 238604210], length 0
05:17:13.757863 IP 1.1.1.1.1234 > 1.1.1.2.39318: Flags [.], ack 465, win 0, options [nop,nop,TS val 238613992 ecr 1214601], length 0

这个实验的现象和实验1的现象,仅有一个区别,那就是实验1是阻塞了ACK,而本实验则是FIN根本就还没有发送出去就进入了FIN_WAIT1,且针对RTO指数时间退避发送的零窗口探测的ACK持续到来,简单总结就是:
实验1没有ACK到来,实验2有ACK到来。

在实验结果之前,我们来看一段摘录,来自RFC 1122:https://tools.ietf.org/html/rfc1122#page-92

4.2.2.17 Probing Zero Windows: RFC-793 Section 3.7, page 42
.
  Probing of zero (offered) windows MUST be supported.
.
  A TCP MAY keep its offered receive window closed
   indefinitely. As long as the receiving TCP continues to
  send acknowledgments in response to the probe segments, the
  sending TCP MUST allow the connection to stay open.

紧接着后面是一段注解:

DISCUSSION:
  It is extremely important to remember that ACK
  (acknowledgment) segments that contain no data are not
  reliably transmitted by TCP. If zero window probing is
  not supported, a connection may hang forever when an
  ACK segment that re-opens the window is lost.
.
  The delay in opening a zero window generally occurs
  when the receiving application stops taking data from
  its TCP. For example, consider a printer daemon
  application, stopped because the printer ran out of
  paper.

只要有ACK到来,连接就要保持,这会带来什么问题呢?确实会带来问题,但是在正视这些问题之前,Linux内核协议栈的实现者,也保持了缄默,我们来看一段实验主机host1和host2所用的标准内核主线版本3.10的内核源码,来自tcp_probe_timer函数内部的注释以及一小段代码:

    /* *WARNING* RFC 1122 forbids this
     *
     * It doesn't AFAIK, because we kill the retransmit timer -AK
     *
     * FIXME: We ought not to do it, Solaris 2.5 actually has fixing
     * this behaviour in Solaris down as a bug fix. [AC]
     *
     * Let me to explain. icsk_probes_out is zeroed by incoming ACKs
     * even if they advertise zero window. Hence, connection is killed only
     * if we received no ACKs for normal connection timeout. It is not killed
     * only because window stays zero for some time, window may be zero
     * until armageddon and even later. We are in full accordance
     * with RFCs, only probe timer combines both retransmission timeout
     * and probe timeout in one bottle.             --ANK
     */
     ...
        max_probes = sysctl_tcp_retries2;

    if (sock_flag(sk, SOCK_DEAD)) { // 如果是orphan连接的话
        const int alive = ((icsk->icsk_rto << icsk->icsk_backoff) < TCP_RTO_MAX);
        // 即获取tcp_orphan_retries参数,有微调,请详审。本实验参数默认值取0!
        max_probes = tcp_orphan_retries(sk, alive);

        if (tcp_out_of_resources(sk, alive || icsk->icsk_probes_out <= max_probes))
            return;
    }
    // 只有在icsk_probes_out,即未应答的probe次数超过探测最大容忍次数后,才会出错清理连接。
    if (icsk->icsk_probes_out > max_probes) {
        tcp_write_err(sk);
    } else {
        /* Only send another probe if we didn't close things up. */
        tcp_send_probe0(sk);
    }

是的,从上面那一段注释,我们看出了抱怨,一个FIN_WAIT1的连接可能会等到世界终结日之后,然而我们却只能“in full accordance with RFCs”


这也许暗示了某种魔咒般的结果,即FIN_WAIT1将会一直持续到终结世界的大决战之日。然而非也,你会发现大概在发送了9个零窗口探测包之后,连接就消失了。netstat -st的结果中,呈现:

1 connections aborted due to timeout

看来想制造点事端,并非想象般容易!

如上所述,我展示了标准主线的Linux 3.10内核的tcp_probe_timer函数,现在的问题是,为什么下面的条件被满足了呢?

if (icsk->icsk_probes_out > max_probes) 

只有当这个条件被满足,tcp_write_err才会被调用,进而:

tcp_done(sk);
// 递增计数,即netstat -st中的那条“1 connections aborted due to timeout”
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONTIMEOUT);

按照注释和代码的确认,只要收到ACK,icsk_probes_out 字段就将被清零,这是很明确的啊,我们在tcp_ack函数中便可看到无条件清零icsk_probes_out的动作:

static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
    ...
    sk->sk_err_soft = 0;
    icsk->icsk_probes_out = 0;
    tp->rcv_tstamp = tcp_time_stamp;
    ...
}

从代码上看,只要零窗口探测持续发送,不管退避到多久(最大TCP_RTO_MAX),只要对端会有ACK回来,icsk_probes_out 就会被清零,上述的条件就不会被满足,连接就会一直在FIN_WAIT1状态,而从我们抓包看,确实是零窗口探测有去必有回的!

预期会永远僵在FIN_WAIT1状态的连接在一段时间后竟然销毁了。没有符合预期,到底发生了呢?


如果我们看高版本4.14版的Linux内核,同样是tcp_probe_timer函数,我们会看到一些不一样的代码和注释:

static void tcp_probe_timer(struct sock *sk)
{
    ...
    /* RFC 1122 4.2.2.17 requires the sender to stay open indefinitely as
     * long as the receiver continues to respond probes. We support this by
     * default and reset icsk_probes_out with incoming ACKs. But if the
     * socket is orphaned or the user specifies TCP_USER_TIMEOUT, we
     * kill the socket when the retry count and the time exceeds the
     * corresponding system limit. We also implement similar policy when
     * we use RTO to probe window in tcp_retransmit_timer().
     */
    start_ts = tcp_skb_timestamp(tcp_send_head(sk));
    if (!start_ts)
        tcp_send_head(sk)->skb_mstamp = tp->tcp_mstamp;
    else if (icsk->icsk_user_timeout &&
         (s32)(tcp_time_stamp(tp) - start_ts) >
         jiffies_to_msecs(icsk->icsk_user_timeout))
        goto abort; 

    max_probes = sock_net(sk)->ipv4.sysctl_tcp_retries2;
    if (sock_flag(sk, SOCK_DEAD)) {
        const bool alive = inet_csk_rto_backoff(icsk, TCP_RTO_MAX) < TCP_RTO_MAX;

        max_probes = tcp_orphan_retries(sk, alive);
        // 如果处在FIN_WAIT1的连接持续时间超过了TCP_RTO_MAX(这是前提)
        // 如果退避发送探测的次数已经超过了配置参数指定的次数(这是附加条件)
        if (!alive && icsk->icsk_backoff >= max_probes)
            goto abort; // 注意这个goto!直接销毁连接。
        if (tcp_out_of_resources(sk, true))
            return;
    }

    if (icsk->icsk_probes_out > max_probes) {
abort:      tcp_write_err(sk);
    } else {
        /* Only send another probe if we didn't close things up. */
        tcp_send_probe0(sk);
    }
}

我们来看这段代码的注释,RFC1122的要求:

RFC 1122 4.2.2.17 requires the sender to stay open indefinitely as
long as the receiver continues to respond probes. We support this by
default and reset icsk_probes_out with incoming ACKs.

然后我们接着看这段注释,有一个But转折:

But if the socket is orphaned or the user specifies TCP_USER_TIMEOUT, we
kill the socket when the retry count and the time exceeds the corresponding system limit.

看起来,这段注释是符合我们实验的结论的!然而我们实验的是3.10内核,而这个却是4.X的内核啊!即Linux在高版本内核上确实进行了优化,这是针对资源利用的优化,并且避免了有针对性的DDoS。


答案揭晓了。

*我们实验所使用的内核版本不是社区主线版本,而是Redhat的版本!***Redhat显然会事先回移上游的patch,我们来确认一下我们所所用的实验版本3.10.0-862.2.3.el7.x86_64的tcp_probe_timer的源码。

为此,我们到下面的地址去下载Redhat(Centos…)专门的源码,我们看看它和社区同版本源码是不是在关于probe处理上有所不同:
http://vault.centos.org/7.5.1804/updates/Source/SPackages/

这里写图片描述

使用下面的命令解压:

rpm2cpio ../kernel-3.10.0-862.2.3.el7.src.rpm | cpio -idmv
xz linux-3.10.0-862.2.3.el7.tar.xz -d
tar xvf linux-3.10.0-862.2.3.el7.tar 

查看net/ipv4/tcp_timer.c文件,找到tcp_probe_timer函数:
这里写图片描述

看来是Redhat移植了4.X的patch,导致了源码的逻辑和社区版本的出现差异,这也就解释了实验现象!


那么这个针对orphan connection的patch最初是来自何方呢?我们不得不去patchwork去溯源,以便得到更深入的Why。

在maillist,我找到了下面的链接:
http://lists.openwall.net/netdev/2014/09/23/8

Date: Mon, 22 Sep 2014 20:52:13 -0700
From: Yuchung Cheng ycheng@...gle.com
To: davem@…emloft.net
Cc: edumazet@…gle.com, andrey.dmitrov@…etlabs.ru,
  ncardwell@…gle.com, netdev@…r.kernel.org,
  Yuchung Cheng ycheng@...gle.com
Subject: [PATCH net-next] tcp: abort orphan sockets stalling on zero window probes

摘录一段描述吧:

Currently we have two different policies for orphan sockets
that repeatedly stall on zero window ACKs. If a socket gets
a zero window ACK when it is transmitting data, the RTO is
used to probe the window. The socket is aborted after roughly
tcp_orphan_retries() retries (as in tcp_write_timeout()).
.
But if the socket was idle when it received the zero window ACK,
and later wants to send more data, we use the probe timer to
probe the window. If the receiver always returns zero window ACKs,
icsk_probes keeps getting reset in tcp_ack() and the orphan socket
can stall forever until the system reaches the orphan limit (as
commented in tcp_probe_timer()). This opens up a simple attack
to create lots of hanging orphan sockets to burn the memory
and the CPU, as demonstrated in the recent netdev post “TCP
connection will hang in FIN_WAIT1 after closing if zero window is
advertised.” http://www.spinics.net/lists/netdev/msg296539.html

该链接最后面给出了patch:

...
+   max_probes = sysctl_tcp_retries2;
    if (sock_flag(sk, SOCK_DEAD)) {
        const int alive = inet_csk_rto_backoff(icsk, TCP_RTO_MAX) < TCP_RTO_MAX;

        max_probes = tcp_orphan_retries(sk, alive);
-
+       if (!alive && icsk->icsk_backoff >= max_probes)
+           goto abort;
        if (tcp_out_of_resources(sk, alive || icsk->icsk_probes_out <= max_probes))
            return;
    }

    if (icsk->icsk_probes_out > max_probes) {
-       tcp_write_err(sk);
+abort:     tcp_write_err(sk);
    } else {
...

简单说一下这个patch的意义。

在实验2中,我用kill -STOP信号故意憋死了nc接收进程,以重现现象,然而事实上在现实中,存在下面两种不太友善情况:

  • 接收端进程出现异常,或者接收端内核存在缺陷,导致进程挂死而软中断上下文的协议栈处理正常运行;
  • 接收端就是一个恶意的DDoS进程,故意不接收数据以诱导发送端在FIN_WAIT2状态(甚至ESTAB状态)发送数据不成后发送零窗口探测而不休止。

无论哪种情况,最主动断开的发送端来讲,其后果都是消耗大量的资源,而orphan连接则占着茅坑不拉屎。这比较悲哀。


现在给出本文的第三个结论:

  • 如果主动断开端调用了close关掉了进程,它会进入FIN_WAIT1状态,如果接收端的接收窗口呈现关闭状态(零窗口),此时它会不断发送零窗口探测包。发送多少次呢?有两种实现:
    1. 低版本内核(至少社区3.10及以下):永久尝试,如果探测ACK每次都返回,则没完没了。
    2. 高版本内核(至少社区4.6及以上):限制尝试tcp_orphan_retries次,不管是否收到探测ACK。

当然,其实还有关于非探测包的重传限制,比如关于TCP_USER_TIMEOUT这个socket option的限制:

else if (icsk->icsk_user_timeout &&
     (s32)(tcp_time_stamp(tp) - start_ts) >
     jiffies_to_msecs(icsk->icsk_user_timeout))
    goto abort;

包括关于Keepalive的点点滴滴,本文就不多说了。


在此,先有个必要的总结。我老是说在学习网络协议的时候读码无益并不是说不要去阅读解析Linux内核源码,而是一定要先有实验设计的能力重现问题,然后再去核对RFC或者其它的协议标准,最后再去核对源码到底是怎么实现的,这样才能一气呵成。否则将有可能陷入深渊。

以本文为例,我假设你手头有3.10的源码,当你面对“FIN_WAIT1状态的TCP连接在持续退避的零窗口探测期间并不会如预期那般永久持续下去”这个问题的时候,你读源码是没有任何用的,因为这个时候你只能静静地看着那些代码,然后纠结自己是不是哪里理解错了,很多人甚至很难能想到去对比不同版本的代码,因为版本太多了。

源码只是一种实现的方式,而已,真正重要的是协议的标准以及标准是实现的建议,此外,各个发行版厂商完全有自主的权力对社区源码做任何的定制和重构,不光是Redhat,即便你去看OpenWRT的代码,也是一样,你会发现很多不一样的东西。

我并不赞同几乎每一个程序员都拥护的那种任何情况下源码至上,the whole world is cheap,show me the code的观点,当一个逻辑流程摆在那里没有源码的时候,当然那绝对是源码至上,否则就是纸上谈兵,逻辑至少要跑起来,而只有源码编译后才能跑起来,流程图和设计图是无法运行的,这个时候,你需要放弃讨论,潜心编码。然而,当一个网络协议已经被以各种方式实现了而你只是为了排查一个问题或者确认一个逻辑的时候,代码就退居二三线了,这时候,请“show me the standard!”


本文原本是想解释完FIN_WAIT1能持续多久就结束的,但是这样显得有点遗憾,因为我想本文的这个FIN_WAIT1的论题可以引出一个更大的论题,如果不继续说一说,那便是不负责任的。

是什么的?嗯,是TCP假连接的问题。那么何谓TCP假连接?

所谓的TCP假连接就是TCP的一端已经逃逸出了TCP状态机,而另一端却不知道的连接。

我们再看完美的TCP标准RFC793上的TCP状态图:
这里写图片描述

除了TIME_WAIT到CLOSED这唯一的出口,你是找不到其它出口的,也就是说,一个TCP端一旦发起了建立连接请求,暂不考虑同时打开同时关闭的情况,就一定要到其中一方的TIME_WAIT超时而结束

然而,TCP的缺陷在于,TCP是一个端到端的协议,在协议层面上所有的端到端协议是需要底层的传送协议作为其支撑的,一旦底层永久崩坏,端到端协议将会面临状态机僵住的场景,而状态机僵住意味着对资源的永久消耗,因为连接再也释放不掉了!

随便举一个例子,在两端ESTAB状态的时候,把IP动态路由协议停掉并把把网线剪断,那么TCP两端将永远处在ESTAB状态,直到机器重启。为了解决这个问题,TCP引入了Keepalive机制,一旦超过一定时间没有互通有无,那么就会主动销毁这个连接,事实上,按照纯粹的TCP状态机而言,Keepalive机制是一种对TCP协议的污染。

是不是Keepalive就能完全避免假连接,死连接存在了呢?非也,Keepalive只是一种用户态按照自己的业务逻辑去检测并避免假连接的手段,而我们仔细观察TCP状态机,很多的步骤远不是用户态进程可是touch的,比如本文讲的FIN_WAIT1,一旦连接成为orphan的,将没有任何进程与之关联,虽然用户态设置的Keepalive也可以继续起作用,但万一用户态没有设置Keepalive呢??这时怎么办?

我们执行下面的命令:

[root@localhost ~]# sysctl -a|grep retries
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_retries1 = 3
net.ipv4.tcp_retries2 = 15
net.ipv4.tcp_syn_retries = 6
net.ipv4.tcp_synack_retries = 5
net.ipv6.idgen_retries = 3

嗯,这些就是避免TCP协议本身的状态机转换僵死所引入的控制层Keepalive机制,详细情况就自己去查阅Linux内核文档吧。

在具体实现上,防止状态机僵死的方法分为两类:

  • ESTABLISHED防止僵死的方法:使用用户进程设置的Keepalive机制
  • 非ESTABLISHED防止僵死的方法:使用各种retries内核参数设置的timeout机制

这里写图片描述
听说温州老板要来,公司楼下专门驻场高定皮鞋衬衫西裤,完全是为了迎接温州老板,不试穿,无样品,完全量身定做,皇家版。皮鞋可以下雨穿。

  • 49
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
该文件共分12个压缩包,必须下载到同一个文件夹后解压才可以用哦~~ 简介: 《TCP/IP详解,卷1:协议》是一本完整而详细的TCP/IP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作。 本书适合作为计算机专业学生学习网络的教材和教师参考书。也适用于研究网络的技术人员。 目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 分层 1 1.3 TCP/IP的分层 4 1.4 互联网的地址 5 1.5 域名系统 6 1.6 封装 6 1.7 分用 8 1.8 客户-服务器模型 8 1.9 端口号 9 1.10 标准化过程 10 1.11 RFC 10 1.12 标准的简单服务 11 1.13 互联网 12 1.14 实现 12 1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口 20 2.8 最大传输单元MTU 21 2.9 路径MTU 21 2.10 串行线路吞吐量计算 21 2.11 小结 22 第3章 IP:网际协议 24 3.1 引言 24 3.2 IP首部 24 3.3 IP路由选择 27 3.4 子网寻址 30 3.5 子网掩码 32 3.6 特殊情况的IP地址 33 3.7 一个子网的例子 33 3.8 ifconfig命令 35 3.9 netstat命令 36 3.10 IP的未来 36 3.11 小结 37 第4章 ARP:地址解析协议 38 4.1 引言 38 4.2 一个例子 38 4.3 ARP高速缓存 40 4.4 ARP的分组格式 40 4.5 ARP举例 41 4.5.1 一般的例子 41 4.5.2 对不存在主机的ARP请求 42 4.5.3 ARP高速缓存超时设置 43 4.6 ARP代理 43 4.7 免费ARP 45 4.8 arp命令 45 4.9 小结 46 第5章 RARP:逆地址解析协议 47 5.1 引言 47 5.2 RARP的分组格式 47 5.3 RARP举例 47 5.4 RARP服务器的设计 48 5.4.1 作为用户进程的RARP服务器 49 5.4.2 每个网络有多个RARP服务器 49 5.5 小结 49 第6章 ICMP:Internet控制报文协议 50 6.1 引言 50 6.2 ICMP报文的类型 50 6.3 ICMP地址掩码请求与应答 52 6.4 ICMP时间戳请求与应答 53 6.4.1 举例 54 6.4.2 另一种方法 55 6.5 ICMP端口不可达差错 56 6.6 ICMP报文的4.4BSD处理 59 6.7 小结 60 第7章 Ping程序 61 7.1 引言 61 7.2 Ping程序 61 7.2.1 LAN输出 62 7.2.2 WAN输出 63 7.2.3 线路SLIP链接 64 7.2.4 拨号SLIP链路 65 7.3 IP记录路由选项 65 7.3.1 通常的例子 66 7.3.2 异常的输出 68 7.4 IP时间戳选项 69 7.5 小结 70 第8章 Traceroute程序 71 8.1 引言 71 8.2 Traceroute 程序的操作 71 8.3 局域网输出 72 8.4 广域网输出 75 8.5 IP源站选路选项 76 8.5.1 宽松的源站选路的traceroute 程序示例 78 8.5.2 严格的源站选路的traceroute 程序示例 79 8.5.3 宽松的源站选路traceroute程序 的往返路由 80 8.6 小结 81 第9章 IP选路 83 9.1 引言 83 9.2 选路的原理 84 9.2.1 简单路由表 84 9.2.2 初始化路由表 86 9.2.3 较复杂的路由表 87 9.2.4 没有到达目的地的路由 87 9.3 ICMP主机与网络不可达差错 88 9.4 转发或不转发 89 9.5 ICMP重定向差错 89 9.5.1 一个例子 90 9.5.2 更多的细节 91 9.6 ICMP路由器发现报文 92 9.6.1 路由器操作 93 9.6.2 主机操作 93 9.6.3 实现 93 9.7 小结 94 第10章 动态选路协议 95 10.1 引言 95 10.2 动态选路 95 10.3 Unix选路守护程序 96 10.4 RIP:选路信息协议 96 10.4.1 报文格式 96 10.4.2 正常运行 97 10.4.3 度量 98 10.4.4 问题 98 10.4.5 举例 98 10.4.6 另一个例子 100 10.5 RIP版本2 102 10.6 OSPF:开放最短路径优先 102 10.7 BGP:边界网关协议 103 10.8 CIDR:无类型域间选路 104 10.9 小结 105 第11章 UDP:用户数据报协议 107 11.1 引言 107 11.2 UDP首部 107 11.3 UDP检验和 108 11.3.1 tcpdump输出 109 11.3.2 一些统计结果 109 11.4 一个简单的例子 110 11.5 IP分片 111 11.6 ICMP不可达差错(需要分片) 113 11.7 用Traceroute确定路径MTU 114 11.8 采用UDP的路径MTU发现 116 11.9 UDP和ARP之间的交互作用 118 11.10 最大UDP数据报长度 119 11.11 ICMP源站抑制差错 120 11.12 UDP服务器的设计 122 11.12.1 客户IP地址及端口号 122 11.12.2 目标IP地址 122 11.12.3 UDP输入队列 122 11.12.4 限制本地IP地址 124 11.12.5 限制远端IP地址 125 11.12.6 每个端口有多个接收者 125 11.13 小结 126 第12章 广播和多播 128 12.1 引言 128 12.2 广播 129 12.2.1 受限的广播 129 12.2.2 指向网络的广播 129 12.2.3 指向子网的广播 129 12.2.4 指向所有子网的广播 130 12.3 广播的例子 130 12.4 多播 132 12.4.1 多播组地址 133 12.4.2 多播组地址到以太网地址的转换 133 12.4.3 FDDI和令牌环网络中的多播 134 12.5 小结 134 第13章 IGMP:Internet组管理协议 136 13.1 引言 136 13.2 IGMP报文 136 13.3 IGMP协议 136 13.3.1 加入一个多播组 136 13.3.2 IGMP报告和查询 137 13.3.3 实现细节 137 13.3.4 生存时间字段 138 13.3.5 所有主机组 138 13.4 一个例子 138 13.5 小结 141 第14章 DNS:域名系统 142 14.1 引言 142 14.2 DNS基础 142 14.3 DNS的报文格式 144 14.3.1 DNS查询报文中的问题部分 146 14.3.2 DNS响应报文中的资源记录部分 147 14.4 一个简单的例子 147 14.5 指针查询 150 14.5.1 举例 151 14.5.2 主机名检查 151 14.6 资源记录 152 14.7 高速缓存 153 14.8 用UDP还是用TCP 156 14.9 另一个例子 156 14.10 小结 157 第15章 TFTP:简单文件传送协议 159 15.1 引言 159 15.2 协议 159 15.3 一个例子 160 15.4 安全性 161 15.5 小结 162 第16章 BOOTP: 引导程序协议 163 16.1 引言 163 16.2 BOOTP的分组格式 163 16.3 一个例子 164 16.4 BOOTP服务器的设计 165 16.5 BOOTP穿越路由器 167 16.6 特定厂商信息 167 16.7 小结 168 第17章 TCP:传输控制协议 170 17.1 引言 170 17.2 TCP的服务 170 17.3 TCP的首部 171 17.4 小结 173 第18章 TCP连接的建立与终止 174 18.1 引言 174 18.2 连接的建立与终止 174 18.2.1 tcpdump的输出 174 18.2.2 时间系列 175 18.2.3 建立连接协议 175 18.2.4 连接终止协议 177 18.2.5 正常的tcpdump输出 177 18.3 连接建立的超时 178 18.3.1 第一次超时时间 178 18.3.2 服务类型字段 179 18.4 最大报文段长度 179 18.5 TCP的半关闭 180 18.6 TCP状态变迁图 182 18.6.1 2MSL等待状态 183 18.6.2 平静时间的概念 186 18.6.3 FIN_WAIT_2状态 186 18.7 复位报文段 186 18.7.1 到不存在的端口的连接请求 187 18.7.2 异常终止一个连接 187 18.7.3 检测半打开连接 188 18.8 同时打开 189 18.9 同时关闭 191 18.10 TCP选项 191 18.11 TCP服务器的设计 192 18.11.1 TCP服务器端口号 193 18.11.2 限定的本地IP地址 194 18.11.3 限定的远端IP地址 195 18.11.4 呼入连接请求队列 195 18.12 小结 197 第19章 TCP的交互数据流 200 19.1 引言 200 19.2 交互式输入 200 19.3 经受时延的确认 201 19.4 Nagle算法 203 19.4.1 关闭Nagle算法 204 19.4.2 一个例子 205 19.5 窗口大小通告 207 19.6 小结 208 第20章 TCP的成块数据流 209 20.1 引言 209 20.2 正常数据流 209 20.3 滑动窗口 212 20.4 窗口大小 214 20.5 PUSH标志 215 20.6 慢启动 216 20.7 成块数据的吞吐量 218 20.7.1 带宽时延乘积 220 20.7.2 拥塞 220 20.8 紧急方式 221 20.9 小结 224 第21章 TCP的超时与重传 226 21.1 引言 226 21.2 超时与重传的简单例子 226 21.3 往返时间测量 227 21.4 往返时间RTT的例子 229 21.4.1 往返时间RTT的测量 229 21.4.2 RTT估计器的计算 231 21.4.3 慢启动 233 21.5 拥塞举例 233 21.6 拥塞避免算法 235 21.7 快速重传与快速恢复算法 236 21.8 拥塞举例(续) 237 21.9 按每条路由进行度量 240 21.10 ICMP的差错 240 21.11 重新分组 243 21.12 小结 243 第22章 TCP的坚持定时器 245 22.1 引言 245 22.2 一个例子 245 22.3 糊涂窗口综合症 246 22.4 小结 250 第23章 TCP的保活定时器 251 23.1 引言 251 23.2 描述 252 23.3 保活举例 253 23.3.1 另一端崩溃 253 23.3.2 另一端崩溃并重新启动 254 23.3.3 另一端不可达 254 23.4 小结 255 第24章 TCP的未来和性能 256 24.1 引言 256 24.2 路径MTU发现 256 24.2.1 一个例子 257 24.2.2 大分组还是小分组 258 24.3 长肥管道 259 24.4 窗口扩大选项 262 24.5 时间戳选项 263 24.6 PAWS:防止回绕的序号 265 24.7 T/TCP:为事务用的TCP扩展 265 24.8 TCP的性能 267 24.9 小结 268 第25章 SNMP:简单网络管理协议 270 25.1 引言 270 25.2 协议 270 25.3 管理信息结构 272 25.4 对象标识符 274 25.5 管理信息库介绍 274 25.6 实例标识 276 25.6.1 简单变量 276 25.6.2 表格 276 25.6.3 字典式排序 277 25.7 一些简单的例子 277 25.7.1 简单变量 278 25.7.2 get-next操作 278 25.7.3 表格的访问 279 25.8 管理信息库(续) 279 25.8.1 system组 279 25.8.2 interface组 280 25.8.3 at组 281 25.8.4 ip组 282 25.8.5 icmp组 285 25.8.6 tcp组 285 25.9 其他一些例子 288 25.9.1 接口MTU 288 25.9.2 路由表 288 25.10 trap 290 25.11 ASN.1和BER 291 25.12 SNMPv2 292 25.13 小结 292 第26章 Telnet和Rlogin:远程登录 293 26.1 引言 293 26.2 Rlogin协议 294 26.2.1 应用进程的启动 295 26.2.2 流量控制 295 26.2.3 客户的中断键 296 26.2.4 窗口大小的改变 296 26.2.5 服务器到客户的命令 296 26.2.6 客户到服务器的命令 297 26.2.7 客户的转义符 298 26.3 Rlogin的例子 298 26.3.1 初始的客户-服务器协议 298 26.3.2 客户中断键 299 26.4 Telnet协议 302 26.4.1 NVT ASCII 302 26.4.2 Telnet命令 302 26.4.3 选项协商 303 26.4.4 子选项协商 304 26.4.5 半双工、一次一字符、一次 一行或行方式 304 26.4.6 同步信号 306 26.4.7 客户的转义符 306 26.5 Telnet举例 306 26.5.1 单字符方式 306 26.5.2 行方式 310 26.5.3 一次一行方式(准行方式) 312 26.5.4 行方式:客户中断键 313 26.6 小结 314 第27章 FTP:文件传送协议 316 27.1 引言 316 27.2 FTP协议 316 27.2.1 数据表示 316 27.2.2 FTP命令 318 27.2.3 FTP应答 319 27.2.4 连接管理 320 27.3 FTP的例子 321 27.3.1 连接管理:临时数据端口 321 27.3.2 连接管理:默认数据端口 323 27.3.3 文本文件传输:NVT ASCII 表示还是图像表示 325 27.3.4 异常中止一个文件的传输: Telnet同步信号 326 27.3.5 匿名FTP 329 27.3.6 来自一个未知IP地址的匿名FTP 330 27.4 小结 331 第28章 SMTP:简单邮件传送协议 332 28.1 引言 332 28.2 SMTP协议 332 28.2.1 简单例子 332 28.2.2 SMTP命令 334 28.2.3 信封、首部和正文 335 28.2.4 中继代理 335 28.2.5 NVT ASCII 337 28.2.6 重试间隔 337 28.3 SMTP的例子 337 28.3.1 MX记录:主机非直接连到 Internet 337 28.3.2 MX记录:主机出故障 339 28.3.3 VRFY和EXPN命令 340 28.4 SMTP的未来 340 28.4.1 信封的变化:扩充的SMTP 341 28.4.2 首部变化:非ASCII字符 342 28.4.3 正文变化:通用Internet邮件 扩充 343 28.5 小结 346 第29章 网络文件系统 347 29.1 引言 347 29.2 Sun远程过程调用 347 29.3 XDR:外部数据表示 349 29.4 端口映射器 349 29.5 NFS协议 351 29.5.1 文件句柄 353 29.5.2 安装协议 353 29.5.3 NFS过程 354 29.5.4 UDP还是TCP 355 29.5.5 TCP上的NFS 355 29.6 NFS实例 356 29.6.1 简单的例子:读一个文件 356 29.6.2 简单的例子:创建一个目录 357 29.6.3 无状态 358 29.6.4 例子:服务器崩溃 358 29.6.5 等幂过程 360 29.7 第3版的NFS 360 29.8 小结 361 第30章 其他的TCP/IP应用程序 363 30.1 引言 363 30.2 Finger协议 363 30.3 Whois协议 364 30.4 Archie、WAIS、Gopher、Veronica 和WWW 366 30.4.1 Archie 366 30.4.2 WAIS 366 30.4.3 Gopher 366 30.4.4 Veronica 366 30.4.5 万维网WWW 367 30.5 X窗口系统 367 30.5.1 Xscope程序 368 30.5.2 LBX: 低带宽X 370 30.6 小结 370 附录A tcpdump程序 371 附录B 计算机时钟 376 附录C sock程序 378 附录D 部分习题的解答 381 附录E 配置选项 395 附录F 可以免费获得的源代码 406 参考文献 409 缩略语 420 目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 1.9.2 包含数据的mbuf 12 1.9.3 添加IP和UDP首部 13 1.9.4 IP输出 14 1.9.5 以太网输出 14 1.9.6 UDP输出小结 14 1.10 输入处理 15 1.10.1 以太网输入 15 1.10.2 IP输入 15 1.10.3 UDP输入 16 1.10.4 进程输入 17 1.11 网络实现概述(续) 17 1.12 中断级别与并发 18 1.13 源代码组织 20 1.14 测试网络 21 1.15 小结 22 第2章 mbuf:存储器缓存 24 2.1 引言 24 2.2 代码介绍 27 2.2.1 全局变量 27 2.2.2 统计 28 2.2.3 内核统计 28 2.3 mbuf的定义 29 2.4 mbuf结构 29 2.5 简单的mbuf宏和函数 31 2.5.1 m_get函数 32 2.5.2 MGET宏 32 2.5.3 m_retry函数 33 2.5.4 mbuf锁 34 2.6 m_devget和m_pullup函数 34 2.6.1 m_devget函数 34 2.6.2 mtod和dtom宏 36 2.6.3 m_pullup函数和连续的协议首部 36 2.6.4 m_pullup和IP的分片与重组 37 2.6.5 TCP重组避免调用m_pullup 39 2.6.6 m_pullup使用总结 40 2.7 mbuf宏和函数的小结 40 2.8 Net/3联网数据结构小结 42 2.9 m_copy和簇引用计数 43 2.10 其他选择 47 2.11 小结 47 第3章 接口层 49 3.1 引言 49 3.2 代码介绍 49 3.2.1 全局变量 49 3.2.2 SNMP变量 50 3.3 ifnet结构 51 3.4 ifaddr结构 57 3.5 sockaddr结构 58 3.6 ifnet与ifaddr的专用化 59 3.7 网络初始化概述 60 3.8 以太网初始化 61 3.9 SLIP初始化 64 3.10 环回初始化 65 3.11 if_attach函数 66 3.12 ifinit函数 72 3.13 小结 73 第4章 接口:以太网 74 4.1 引言 74 4.2 代码介绍 75 4.2.1 全局变量 75 4.2.2 统计量 75 4.2.3 SNMP变量 76 4.3 以太网接口 77 4.3.1 leintr函数 79 4.3.2 leread函数 79 4.3.3 ether_input函数 81 4.3.4 ether_output函数 84 4.3.5 lestart函数 87 4.4 ioctl系统调用 89 4.4.1 ifioctl函数 90 4.4.2 ifconf函数 91 4.4.3 举例 94 4.4.4 通用接口ioctl命令 95 4.4.5 if_down和if_up函数 96 4.4.6 以太网、SLIP和环回 97 4.5 小结 98 第5章 接口:SLIP和环回 100 5.1 引言 100 5.2 代码介绍 100 5.2.1 全局变量 100 5.2.2 统计量 101 5.3 SLIP接口 101 5.3.1 SLIP线路规程:SLIPDISC 101 5.3.2 SLIP初始化:slopen和slinit 103 5.3.3 SLIP输入处理:slinput 105 5.3.4 SLIP输出处理:sloutput 109 5.3.5 slstart函数 111 5.3.6 SLIP分组丢失 116 5.3.7 SLIP性能考虑 117 5.3.8 slclose函数 117 5.3.9 sltioctl函数 118 5.4 环回接口 119 5.5 小结 121 第6章 IP编址 123 6.1 引言 123 6.1.1 IP地址 123 6.1.2 IP地址的印刷规定 123 6.1.3 主机和路由器 124 6.2 代码介绍 125 6.3 接口和地址小结 125 6.4 sockaddr_in结构 126 6.5 in_ifaddr结构 127 6.6 地址指派 128 6.6.1 ifioctl函数 130 6.6.2 in_control函数 130 6.6.3 前提条件:SIOCSIFADDR、 SIOCSIFNETMASK和 SIOCSIFDSTADDR 132 6.6.4 地址指派:SIOCSIFADDR 133 6.6.5 in_ifinit函数 133 6.6.6 网络掩码指派:SIOCSIFNETMASK 136 6.6.7 目的地址指派:SIOCSIFDSTADDR 137 6.6.8 获取接口信息 137 6.6.9 每个接口多个IP地址 138 6.6.10 附加IP地址:SIOCAIFADDR 139 6.6.11 删除IP地址:SIOCDIFADDR 140 6.7 接口ioctl处理 141 6.7.1 leioctl函数 141 6.7.2 slioctl函数 142 6.7.3 loioctl函数 143 6.8 Internet实用函数 144 6.9 ifnet实用函数 144 6.10 小结 145 第7章 域和协议 146 7.1 引言 146 7.2 代码介绍 146 7.2.1 全局变量 147 7.2.2 统计量 147 7.3 domain结构 147 7.4 protosw结构 148 7.5 IP 的domain和protosw结构 150 7.6 pffindproto和pffindtype函数 155 7.7 pfctlinput函数 157 7.8 IP初始化 157 7.8.1 Internet传输分用 157 7.8.2 ip_init函数 158 7.9 sysctl系统调用 159 7.10 小结 161 第8章 IP:网际协议 162 8.1 引言 162 8.2 代码介绍 163 8.2.1 全局变量 163 8.2.2 统计量 163 8.2.3 SNMP变量 164 8.3 IP分组 165 8.4 输入处理:ipintr函数 167 8.4.1 ipintr概观 167 8.4.2 验证 168 8.4.3 转发或不转发 171 8.4.4 重装和分用 173 8.5 转发:ip_forward函数 174 8.6 输出处理:ip_output函数 180 8.6.1 首部初始化 181 8.6.2 路由选择 182 8.6.3 源地址选择和分片 184 8.7 Internet检验和:in_cksum函数 186 8.8 setsockopt和getsockopt系统调用 190 8.8.1 PRCO_SETOPT的处理 192 8.8.2 PRCO_GETOPT的处理 193 8.9 ip_sysctl函数 193 8.10 小结 194 第9章 IP选项处理 196 9.1 引言 196 9.2 代码介绍 196 9.2.1 全局变量 196 9.2.2 统计量 197 9.3 选项格式 197 9.4 ip_dooptions函数 198 9.5 记录路由选项 200 9.6 源站和记录路由选项 202 9.6.1 save_rte函数 205 9.6.2 ip_srcroute函数 206 9.7 时间戳选项 207 9.8 ip_insertoptions函数 210 9.9 ip_pcbopts函数 214 9.10 一些限制 217 9.11 小结 217 第10章 IP的分片与重装 218 10.1 引言 218 10.2 代码介绍 219 10.2.1 全局变量 220 10.2.2 统计量 220 10.3 分片 220 10.4 ip_optcopy函数 223 10.5 重装 224 10.6 ip_reass函数 227 10.7 ip_slowtimo函数 237 10.8 小结 238 第11章 ICMP:Internet控制报文协议 239 11.1 引言 239 11.2 代码介绍 242 11.2.1 全局变量 242 11.2.2 统计量 242 11.2.3 SNMP变量 243 11.3 icmp结构 244 11.4 ICMP 的protosw结构 245 11.5 输入处理:icmp_input函数 246 11.6 差错处理 249 11.7 请求处理 251 11.7.1 回显询问:ICMP_ECHO和 ICMP_ECHOREPLY 252 11.7.2 时间戳询问:ICMP_TSTAMP和 ICMP_TSTAMPREPLY 253 11.7.3 地址掩码询问:ICMP_MASKREQ和 ICMP_MASKREPLY 253 11.7.4 信息询问:ICMP_IREQ和ICMP_ IREQREPLY 255 11.7.5 路由器发现:ICMP_ROUTERADVERT 和ICMP_ROUTERSOLICIT 255 11.8 重定向处理 255 11.9 回答处理 257 11.10 输出处理 257 11.11 icmp_error函数 258 11.12 icmp_reflect函数 261 11.13 icmp_send函数 265 11.14 icmp_sysctl函数 266 11.15 小结 266 第12章 IP多播 268 12.1 引言 268 12.2 代码介绍 269 12.2.1 全局变量 270 12.2.2 统计量 270 12.3 以太网多播地址 270 12.4 ether_multi结构 271 12.5 以太网多播接收 273 12.6 in_multi结构 273 12.7 ip_moptions结构 275 12.8 多播的插口选项 276 12.9 多播的TTL值 277 12.9.1 MBONE 278 12.9.2 扩展环搜索 278 12.10 ip_setmoptions函数 278 12.10.1 选择一个明确的多播接口:IP_ MULTICAST_IF 280 12.10.2 选择明确的多播TTL: IP_ MULTICAST_TTL 281 12.10.3 选择多播环回:IP_MULTICAST_ LOOP 281 12.11 加入一个IP多播组 282 12.11.1 in_addmulti函数 285 12.11.2 slioctl和loioctl函数:SIOCADDMULTI和SIOCDELMULTI 287 12.11.3 leioctl函数:SIOCADDMULTI和 SIOCDELMULTI 288 12.11.4 ether_addmulti函数 288 12.12 离开一个IP多播组 291 12.12.1 in_delmulti函数 292 12.12.2 ether_delmulti函数 293 12.13 ip_getmoptions函数 295 12.14 多播输入处理:ipintr函数 296 12.15 多播输出处理:ip_output函数 298 12.16 性能的考虑 301 12.17 小结 301 第13章 IGMP:Internet组管理协议 303 13.1 引言 303 13.2 代码介绍 304 13.2.1 全局变量 304 13.2.2 统计量 304 13.2.3 SNMP变量 305 13.3 igmp结构 305 13.4 IGMP的protosw的结构 306 13.5 加入一个组:igmp_joingroup函数 306 13.6 igmp_fasttimo函数 308 13.7 输入处理:igmp_input函数 311 13.7.1 成员关系查询:IGMP_HOST_ MEMBERSHIP_QUERY 312 13.7.2 成员关系报告:IGMP_HOST_ MEMBERSHIP_REPORT 313 13.8 离开一个组:igmp_leavegroup函数 314 13.9 小结 315 第14章 IP多播选路 316 14.1 引言 316 14.2 代码介绍 316 14.2.1 全局变量 316 14.2.2 统计量 317 14.2.3 SNMP变量 317 14.3 多播输出处理(续) 317 14.4 mrouted守护程序 318 14.5 虚拟接口 321 14.5.1 虚拟接口表 322 14.5.2 add_vif函数 324 14.5.3 del_vif函数 326 14.6 IGMP(续) 327 14.6.1 add_lgrp函数 328 14.6.2 del_lgrp函数 329 14.6.3 grplst_member函数 330 14.7 多播选路 331 14.7.1 多播选路表 334 14.7.2 del_mrt函数 335 14.7.3 add_mrt函数 336 14.7.4 mrtfind函数 337 14.8 多播转发:ip_mforward函数 338 14.8.1 phyint_send函数 343 14.8.2 tunnel_send函数 344 14.9 清理:ip_mrouter_done函数 345 14.10 小结 346 第15章 插口层 348 15.1 引言 348 15.2 代码介绍 349 15.3 socket结构 349 15.4 系统调用 354 15.4.1 举例 355 15.4.2 系统调用小结 355 15.5 进程、描述符和插口 357 15.6 socket系统调用 358 15.6.1 socreate函数 359 15.6.2 超级用户特权 361 15.7 getsock和sockargs函数 361 15.8 bind系统调用 363 15.9 listen系统调用 364 15.10 tsleep和wakeup函数 365 15.11 accept系统调用 366 15.12 sonewconn和soisconnected 函数 369 15.13 connect系统调用 372 15.13.1 soconnect函数 374 15.13.2 切断无连接插口和外部地址的 关联 375 15.14 shutdown系统调用 375 15.15 close系统调用 377 15.15.1 soo_close函数 377 15.15.2 soclose函数 378 15.16 小结 380 第16章 插口I/O 381 16.1 引言 381 16.2 代码介绍 381 16.3 插口缓存 381 16.4 write、writev、sendto和sendmsg 系统调用 384 16.5 sendmsg系统调用 387 16.6 sendit函数 388 16.6.1 uiomove函数 389 16.6.2 举例 390 16.6.3 sendit代码 391 16.7 sosend函数 392 16.7.1 可靠的协议缓存 393 16.7.2 不可靠的协议缓存 393 16.7.3 sosend函数小结 401 16.7.4 性能问题 401 16.8 read、readv、recvfrom和recvmsg 系统调用 401 16.9 recvmsg系统调用 402 16.10 recvit函数 403 16.11 soreceive函数 405 16.11.1 带外数据 406 16.11.2 举例 406 16.11.3 其他的接收操作选项 407 16.11.4 接收缓存的组织:报文边界 407 16.11.5 接收缓存的组织:没有报文边界 408 16.11.6 控制信息和带外数据 409 16.12 soreceive代码 410 16.13 select系统调用 421 16.13.1 selscan函数 425 16.13.2 soo_select函数 425 16.13.3 selrecord函数 427 16.13.4 selwakeup函数 428 16.14 小结 429 第17章 插口选项 431 17.1 引言 431 17.2 代码介绍 431 17.3 setsockopt系统调用 432 17.4 getsockopt系统调用 437 17.5 fcntl和ioctl系统调用 440 17.5.1 fcntl代码 441 17.5.2 ioctl代码 443 17.6 getsockname系统调用 444 17.7 getpeername系统调用 445 17.8 小结 447 第18章 Radix树路由表 448 18.1 引言 448 18.2 路由表结构 448 18.3 选路插口 456 18.4 代码介绍 456 18.4.1 全局变量 458 18.4.2 统计量 458 18.4.3 SNMP变量 459 18.5 Radix结点数据结构 460 18.6 选路结构 463 18.7 初始化:route_init和rtable_init 函数 465 18.8 初始化:rn_init和rn_inithead 函数 468 18.9 重复键和掩码列表 471 18.10 rn_match函数 473 18.11 rn_search函数 480 18.12 小结 481 第19章 选路请求和选路消息 482 19.1 引言 482 19.2 rtalloc和rtalloc1函数 482 19.3 宏RTFREE和rtfree函数 484 19.4 rtrequest函数 486 19.5 rt_setgate函数 491 19.6 rtinit函数 493 19.7 rtredirect函数 495 19.8 选路消息的结构 498 19.9 rt_missmsg函数 501 19.10 rt_ifmsg函数 503 19.11 rt_newaddrmsg函数 504 19.12 rt_msg1函数 505 19.13 rt_msg2函数 507 19.14 sysctl_rtable函数 510 19.15 sysctl_dumpentry函数 514 19.16 sysctl_iflist函数 515 19.17 小结 517 第20章 选路插口 518 20.1 引言 518 20.2 routedomain和protosw结构 518 20.3 选路控制块 519 20.4 raw_init函数 520 20.5 route_output函数 520 20.6 rt_xaddrs函数 530 20.7 rt_setmetrics函数 531 20.8 raw_input函数 532 20.9 route_usrreq函数 534 20.10 raw_usrreq函数 535 20.11 raw_attach、raw_detach和raw_disconnect函数 539 20.12 小结 540 第21章 ARP:地址解析协议 542 21.1 介绍 542 21.2 ARP和路由表 542 21.3 代码介绍 544 21.3.1 全局变量 544 21.3.2 统计量 544 21.3.3 SNMP变量 546 21.4 ARP结构 546 21.5 arpwhohas函数 548 21.6 arprequest函数 548 21.7 arpintr函数 551 21.8 in_arpinput函数 552 21.9 ARP定时器函数 557 21.9.1 arptimer函数 557 21.9.2 arptfree函数 557 21.10 arpresolve函数 558 21.11 arplookup函数 562 21.12 代理ARP 563 21.13 arp_rtrequest函数 564 21.14 ARP和多播 569 21.15 小结 570 第22章 协议控制块 572 22.1 引言 572 22.2 代码介绍 573 22.2.1 全局变量 574 22.2.2 统计量 574 22.3 inpcb的结构 574 22.4 in_pcballoc和in_pcbdetach函数 575 22.5 绑定、连接和分用 577 22.6 in_pcblookup函数 581 22.7 in_pcbbind函数 584 22.8 in_pcbconnect函数 589 22.9 in_pcbdisconnect函数 594 22.10 in_setsockaddr和in_setpeeraddr 函数 595 22.11 in_pcbnotify、in_rtchange和in_losing函数 595 22.11.1 in_rtchange函数 598 22.11.2 重定向和原始插口 599 22.11.3 ICMP差错和UDP插口 600 22.11.4 in_losing函数 601 22.12 实现求精 602 22.13 小结 602 第23章 UDP:用户数据报协议 605 23.1 引言 605 23.2 代码介绍 605 23.2.1 全局变量 606 23.2.2 统计量 606 23.2.3 SNMP变量 607 23.3 UDP 的protosw结构 607 23.4 UDP的首部 608 23.5 udp_init函数 609 23.6 udp_output函数 609 23.6.1 在前面加上IP/UDP首部和mbuf簇 612 23.6.2 UDP检验和计算和伪首部 612 23.7 udp_input函数 616 23.7.1 对收到的UDP数据报的一般确认 616 23.7.2 分用单播数据报 619 23.7.3 分用多播和广播数据报 622 23.7.4 连接上的UDP插口和多接口主机 625 23.8 udp_saveopt函数 625 23.9 udp_ctlinput函数 627 23.10 udp_usrreq函数 628 23.11 udp_sysctl函数 633 23.12 实现求精 633 23.12.1 UDP PCB高速缓存 633 23.12.2 UDP检验和 634 23.13 小结 635 第24章 TCP:传输控制协议 636 24.1 引言 636 24.2 代码介绍 636 24.2.1 全局变量 636 24.2.2 统计量 637 24.2.3 SNMP变量 640 24.3 TCP 的protosw结构 641 24.4 TCP的首部 641 24.5 TCP的控制块 643 24.6 TCP状态变迁图 645 24.7 TCP的序号 646 24.8 tcp_init函数 650 24.9 小结 652 第25章 TCP的定时器 654 25.1 引言 654 25.2 代码介绍 655 25.3 tcp_canceltimers函数 657 25.4 tcp_fasttimo函数 657 25.5 tcp_slowtimo函数 658 25.6 tcp_timers函数 659 25.6.1 FIN_WAIT_2和2MSL定时器 660 25.6.2 持续定时器 662 25.6.3 连接建立定时器和保活定时器 662 25.7 重传定时器的计算 665 25.8 tcp_newtcpcb算法 666 25.9 tcp_setpersist函数 668 25.10 tcp_xmit_timer函数 669 25.11 重传超时:tcp_timers函数 673 25.11.1 慢起动和避免拥塞 675 25.11.2 精确性 677 25.12 一个RTT的例子 677 25.13 小结 679 第26章 TCP输出 680 26.1 引言 680 26.2 tcp_output概述 680 26.3 决定是否应发送一个报文段 682 26.4 TCP选项 691 26.5 窗口大小选项 692 26.6 时间戳选项 692 26.6.1 哪个时间戳需要回显,RFC1323 算法 694 26.6.2 哪个时间戳需要回显,正确的 算法 695 26.6.3 时间戳与延迟ACK 695 26.7 发送一个报文段 696 26.8 tcp_template函数 707 26.9 tcp_respond函数 708 26.10 小结 710 第27章 TCP的函数 712 27.1 引言 712 27.2 tcp_drain函数 712 27.3 tcp_drop函数 712 27.4 tcp_close函数 713 27.4.1 路由特性 713 27.4.2 资源释放 716 27.5 tcp_mss函数 717 27.6 tcp_ctlinput函数 722 27.7 tcp_notify函数 723 27.8 tcp_quench函数 724 27.9 TCP_REASS宏和tcp_reass函数 724 27.9.1 TCP_REASS宏 725 27.9.2 tcp_reass函数 727 27.10 tcp_trace函数 732 27.11 小结 736 第28章 TCP的输入 737 28.1 引言 737 28.2 预处理 739 28.3 tcp_dooptions函数 745 28.4 首部预测 747 28.5 TCP输入:缓慢的执行路径 752 28.6 完成被动打开或主动打开 752 28.6.1 完成被动打开 753 28.6.2 完成主动打开 756 28.7 PAWS:防止序号回绕 760 28.8 裁剪报文段使数据在窗口内 762 28.9 自连接和同时打开 768 28.10 记录时间戳 770 28.11 RST处理 770 28.12 小结 772 第29章 TCP的输入(续) 773 29.1 引言 773 29.2 ACK处理概述 773 29.3 完成被动打开和同时打开 774 29.4 快速重传和快速恢复的算法 775 29.5 ACK处理 778 29.6 更新窗口信息 784 29.7 紧急方式处理 786 29.8 tcp_pulloutofband函数 788 29.9 处理已接收的数据 789 29.10 FIN处理 791 29.11 最后的处理 793 29.12 实现求精 795 29.13 首部压缩 795 29.13.1 引言 796 29.13.2 首部字段的压缩 799 29.13.3 特殊情况 801 29.13.4 实例 802 29.13.5 配置 803 29.14 小结 803 第30章 TCP的用户需求 805 30.1 引言 805 30.2 tcp_usrreq函数 805 30.3 tcp_attach函数 814 30.4 tcp_disconnect函数 815 30.5 tcp_usrclosed函数 816 30.6 tcp_ctloutput函数 817 30.7 小结 820 第31章 BPF:BSD 分组过滤程序 821 31.1 引言 821 31.2 代码介绍 821 31.2.1 全局变量 821 31.2.2 统计量 822 31.3 bpf_if结构 822 31.4 bpf_d结构 825 31.4.1 bpfopen函数 826 31.4.2 bpfioctl函数 827 31.4.3 bpf_setif函数 830 31.4.4 bpf_attachd函数 831 31.5 BPF的输入 832 31.5.1 bpf_tap函数 832 31.5.2 catchpacket函数 833 31.5.3 bpfread函数 835 31.6 BPF的输出 837 31.7 小结 838 第32章 原始IP 839 32.1 引言 839 32.2 代码介绍 839 32.2.1 全局变量 839 32.2.2 统计量 840 32.3 原始 IP的protosw结构 840 32.4 rip_init函数 842 32.5 rip_input函数 842 32.6 rip_output函数 844 32.7 rip_usrreq函数 846 32.8 rip_ctloutput函数 850 32.9 小结 852 结束语 853 附录A 部分习题的解答 854 附录B 源代码的获取 872 附录C RFC 1122 的有关内容 874 参考文献 895 目 录 译者序 前言 第一部分 TCP事务协议 第1章 T/TCP概述 1 1.1 概述 1 1.2 UDP上的客户-服务器 1 1.3 TCP上的客户-服务器 6 1.4 T/TCP上的客户-服务器 12 1.5 测试网络 15 1.6 时间测量程序 15 1.7 应用 17 1.8 历史 19 1.9 实现 20 1.10 小结 21 第2章 T/TCP协议 23 2.1 概述 23 2.2 T/TCP中的新TCP选项 23 2.3 T/TCP实现所需变量 25 2.4 状态变迁图 27 2.5 T/TCP的扩展状态 28 2.6 小结 30 第3章 T/TCP使用举例 31 3.1 概述 31 3.2 客户重新启动 31 3.3 常规的T/TCP事务 33 3.4 服务器收到过时的重复SYN 34 3.5 服务器重启动 35 3.6 请求或应答超出报文段最大长度MSS 36 3.7 向后兼容性 39 3.8 小结 41 第4章 T/TCP协议(续) 43 4.1 概述 43 4.2 客户的端口号和TIME_WAIT状态 43 4.3 设置TIME_WAIT状态的目的 45 4.4 TIME_WAIT状态的截断 48 4.5 利用TAO跳过三次握手 51 4.6 小结 55 第5章 T/TCP协议的实现:插口层 56 5.1 概述 56 5.2 常量 56 5.3 sosend函数 56 5.4 小结 58 第6章 T/TCP的实现:路由表 59 6.1 概述 59 6.2 代码介绍 59 6.3 radix_node_head结构 60 6.4 rtentry结构 61 6.5 rt_metrics结构 61 6.6 in_inithead函数 61 6.7 in_addroute函数 62 6.8 in_matroute函数 63 6.9 in_clsroute函数 63 6.10 in_rtqtimo函数 64 6.11 in_rtqkill函数 66 6.12 小结 69 第7章 T/TCP实现:协议控制块 70 7.1 概述 70 7.2 in_pcbladdr函数 71 7.3 in_pcbconnect函数 71 7.4 小结 72 第8章 T/TCP实现: TCP概要 73 8.1 概述 73 8.2 代码介绍 73 8.3 TCP的protosw结构 74 8.4 TCP控制块 74 8.5 tcp_init函数 75 8.6 tcp_slowtimo函数 75 8.7 小结 76 第9章 T/TCP实现:TCP输出 77 9.1 概述 77 9.2 tcp_output函数 77 9.2.1 新的自动变量 77 9.2.2 增加隐藏的状态标志 77 9.2.3 在SYN_SENT状态不要重传SYN 78 9.2.4 发送器的糊涂窗口避免机制 78 9.2.5 有RST或SYN标志时强制发送报文段 79 9.2.6 发送MSS选项 80 9.2.7 是否发送时间戳选项 80 9.2.8 发送T/TCP的CC选项 80 9.2.9 根据TCP选项调整数据长度 83 9.3 小结 83 第10章 T/TCP实现:TCP函数 84 10.1 概述 84 10.2 tcp_newtcpcb函数 84 10.3 tcp_rtlookup函数 85 10.4 tcp_gettaocache函数 86 10.5 重传超时间隔的计算 86 10.6 tcp_close函数 89 10.7 tcp_msssend函数 90 10.8 tcp_mssrcvd函数 91 10.9 tcp_dooptions函数 96 10.10 tcp_reass函数 98 10.11 小结 99 第11章 T/TCP实现:TCP输入 101 11.1 概述 101 11.2 预处理 103 11.3 首部预测 104 11.4 被动打开的启动 105 11.5 主动打开的启动 108 11.6 PAWS:防止序号重复 114 11.7 ACK处理 115 11.8 完成被动打开和同时打开 115 11.9 ACK处理(续) 116 11.10 FIN处理 118 11.11 小结 119 第12章 T/TCP实现:TCP用户请求 120 12.1 概述 120 12.2 PRU_CONNECT请求 120 12.3 tcp_connect函数 120 12.4 PRU_SEND和PRU_SEND_EOF请求 124 12.5 tcp_usrclosed函数 125 12.6 tcp_sysctl函数 126 12.7 T/TCP的前景 126 12.8 小结 127 第二部分 TCP的其他应用 第13章 HTTP:超文本传送协议 129 13.1 概述 129 13.2 HTTP和HTML概述 130 13.3 HTTP 132 13.3.1 报文类型:请求与响应 132 13.3.2 首部字段 133 13.3.3 响应代码 133 13.3.4 各种报文头举例 134 13.3.5 例子:客户程序缓存 135 13.3.6 例子:服务器重定向 136 13.4 一个例子 136 13.5 HTTP的统计资料 138 13.6 性能问题 139 13.7 小结 141 第14章 在HTTP服务器上找到的分组 142 14.1 概述 142 14.2 多个HTTP服务器 144 14.3 客户端SYN的到达间隔时间 145 14.4 RTT的测量 149 14.5 用listen设置入连接队列的容量 150 14.6 客户端的SYN选项 154 14.7 客户端的SYN重传 156 14.8 域名 157 14.9 超时的持续探测 157 14.10 T/TCP路由表大小的模拟 160 14.11 mbuf的交互 162 14.12 TCP的PCB高速缓存和首部预测 163 14.13 小结 165 第15章 NNTP:网络新闻传送协议 166 15.1 概述 166 15.2 NNTP 167 15.3 一个简单的新闻客户 170 15.4 一个复杂的新闻客户 171 15.5 NNTP的统计资料 172 15.6 小结 173 第三部分 Unix域协议 第16章 Unix域协议:概述 175 16.1 概述 175 16.2 用途 176 16.3 性能 177 16.4 编码举例 177 16.5 小结 179 第17章 Unix域协议:实现 180 17.1 概述 180 17.2 代码介绍 180 17.3 Unix domain和protosw结构 181 17.4 Unix域插口地址结构 182 17.5 Unix域协议控制块 183 17.6 uipc_usrreq函数 185 17.7 PRU_ATTACH请求和unp_attach函数 186 17.8 PRU_DETACH请求和unp_detach函数 187 17.9 PRU_BIND请求和unp_bind函数 189 17.10 PRU_CONNECT请求和unp_connect 函数 191 17.11 PRU_CONNECT2请求和unp_connect2 函数 195 17.12 socketpair系统调用 198 17.13 pipe系统调用 202 17.14 PRU_ACCEPT请求 203 17.15 PRU_DISCONNECT请求和 unp_disconnect函数 204 17.16 PRU_SHUTDOWN请求和unp_shutdown 函数 205 17.17 PRU_ABORT请求和unp_drop函数 206 17.18 其他各种请求 207 17.19 小结 209 第18章 Unix域协议:I/O和描述符的传递 210 18.1 概述 210 18.2 PRU_SEND和PRU_RCVD请求 210 18.3 描述符的传递 214 18.4 unp_internalize函数 218 18.5 unp_externalize函数 220 18.6 unp_discard函数 221 18.7 unp_dispose函数 222 18.8 unp_scan函数 222 18.9 unp_gc函数 223 18.10 unp_mark函数 230 18.11 性能(再讨论) 231 18.12 小结 231 附录A 测量网络时间 232 附录B 编写T/TCP应用程序 242 参考文献 246 缩略语 251
TCP,UDP服务端口,HMODULE hIpDLL = LoadLibrary( "iphlpapi.dll"); if ( !hIpDLL) return; PMIB_TCPTABLE_OWNER_PID pTcpTable(NULL); DWORD dwSize(0); PGet_Extended_TcpTable pGetExtendedTcpTable = NULL; pGetExtendedTcpTable = (PGet_Extended_TcpTable) GetProcAddress(hIpDLL, "GetExtendedTcpTable"); if(pGetExtendedTcpTable==NULL) { FreeLibrary(hIpDLL); return; } if(pGetExtendedTcpTable(pTcpTable, &dwSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == ERROR_INSUFFICIENT_BUFFER) pTcpTable = (MIB_TCPTABLE_OWNER_PID *)new char[dwSize];//重新分配缓冲区 if (pGetExtendedTcpTable(pTcpTable, &dwSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != NO_ERROR) { delete pTcpTable; return; } CString sTemp; m_PortList.DeleteAllItems(); int nNum = (int)pTcpTable->dwNumEntries; //TCP连接的数目 // nNum = 0; for (int i = 0; itable[i].dwLocalAddr))); sTemp.Format("%u",htons((WORD)pTcpTable->table[i].dwLocalPort)); m_PortList.SetItemText(i,4,sTemp); m_PortList.SetItemBgColor(i,4,RGB(210,0,0)); m_PortList.SetItemText(i,5,FormatNumToIpv4(htonl(pTcpTable->table[i].dwRemoteAddr))); sTemp.Format("%u",htons((WORD) pTcpTable->table[i].dwRemotePort)); m_PortList.SetItemText(i,6,sTemp); sTemp.Format("%u",pTcpTable->table[i].dwOwningPid); m_PortList.SetItemText(i,8,sTemp); //ProcessPidToName(pTcpTable->table[i].dwOwningPid,i); ProcessPidToNameX(pTcpTable->table[i].dwOwningPid,i); switch (pTcpTable->table[i].dwState) { case MIB_TCP_STATE_CLOSED: m_PortList.SetItemText(i,2,"CLOSED"); break; case MIB_TCP_STATE_LISTEN: m_PortList.SetItemText(i,2,"LISTEN"); break; case MIB_TCP_STATE_SYN_SENT: m_PortList.SetItemText(i,2,"SYN
FIN_WAIT1 是 TCP 连接的一种状态,它表示连接关闭的阶段之一。当一方发送了 FIN(终止连接)报文后,进入 FIN_WAIT1 状态,等待对方的确认。 在 FIN_WAIT1 状态下,发送方仍然可以接收对方发送的数据。它等待对方发送 ACK(确认)报文作为对 FIN 报文的确认。如果接收到对方的 ACK 报文,连接将进入 FIN_WAIT2 状态。如果在一定时间内没有收到对方的 ACK 报文,或者收到了对方的 RST(复位)报文,则连接会直接关闭。 FIN_WAIT1 状态通常是一个短暂的状态,在正常情况下不会停留太久。如果连接长时间停留在 FIN_WAIT1 状态,可能是由于以下原因之一: 1. 对方没有发送 ACK 报文:对方可能由于某种原因未正确处理并发送 ACK 报文,导致连接无法继续进入 FIN_WAIT2 状态。可以通过网络抓包或日志来确认是否有 ACK 报文被丢失或未发送。 2. 对方在接收到 FIN 报文后长时间未响应:如果对方在接收到 FIN 报文后长时间没有响应,可能是由于对方应用程序的问题,导致无法及时发送 ACK 报文。 请注意,如果应用程序频繁地打开和关闭连接,并且使用相同的本地 IP 地址和端口号,可能会导致连接在 TIME_WAIT 状态下保持较长时间,进而导致 FIN_WAIT1 状态持续时间延长。在这种情况下,可以尝试使用不同的本地 IP 地址和端口号来避免连接复用。 如果长时间停留在 FIN_WAIT1 状态引起了问题,可以考虑调整操作系统的参数或检查应用程序的代码逻辑,以确保连接能够正常关闭。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值