You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
14 KiB
Markdown

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 答疑(二)| 第6~10讲思考题答案
你好,我是胜辉。
在上一节答疑课里我们回顾了第1到第5讲的思考题分析了每道题目的解题思路当然更重要的是对这五节课的知识点做了一次复习和补充。你看完了这些解答后的感觉如何呢跟之前你自己的思考过程和结论相比又有哪些相同和不同之处呢相信通过再一次的思考你对知识的掌握将会更加深刻。
那么这节课我们就继续讲解这些课后的思考题。先从第6讲开始。
## 06讲的答疑
### 思考题
这节课我给你介绍了利用防火墙的TTL跟原先的正常报文的TTL不一致的特点从而识别出防火墙的方法。那么假设有一天防火墙公司把这个特性也完善了我们再也不能仅凭TTL的突变而发现防火墙了你觉得还有什么办法可以识别出防火墙吗
### 答案
这是一个开放式的问题其实并没有标准答案。不过通过对这个问题的思考我们可以对网络的理解更进一步。我们先参考下面这张图复习一下用TTL判断防火墙的原理
![](https://static001.geekbang.org/resource/image/f6/56/f6a1206ea604d7f3dd88981e39366256.jpg?wh=1694x562)
> 补充上图是一个例子假设防火墙是路径上的第5跳那么防火墙自己发出的报文的TTL就比真正的发送端的TTL多了5。
然后想一想我们之所以能通过TTL定位防火墙本质原因是什么呢实际上本质原因就是这个IP报文是防火墙自己构造的而**在构造时TTL被设置为了初始值**。
那么,还有哪些元信息也是在这个构造好的报文里呢?其实这些信息也可以帮我们辅助判断防火墙。
另外在IP层除了TTL还有一个字段也值得我们注意它就是**IP ID**。这个字段占用了2个字节所以它的最大值是2^16-1也就是65535。IP ID一般是IP报文的发起端设置的每次发包递增1然后在0到65535之间循环使用。
它最主要的用途,是**接收端会根据这个ID来组装assemble同一组IP分片fragments**而中间设备交换机、路由器不会改动IP ID。并且在防火墙构造RST报文的时候IP包的TTL和ID值也是防火墙自己设置的也就有可能被我们用来发现防火墙。所以如果SYN+ACK和RST的IP ID不连续我们就可以判断出防火墙也就是下图这样
![](https://static001.geekbang.org/resource/image/51/52/51df7643ff2127011ce9e6fd124e9652.jpg?wh=1846x884)
不过我在探究这个可能性的时候又遇到了新的问题。要知道Linux系统发送SYN+ACK的时候IP ID一定是0但是我既在Wireshark里看到了这个现象也在内核代码里证实了这一点。而这就给我们利用IP ID带来了障碍**因为即使RST真的是对端发出的两个报文的IP ID也会不同。**
只有当我们收到至少三个报文也就是除了SYN+ACK和RST之外我们还需要在中间收到一些报文才可以对比RST跟中间报文的IP ID的差别然后进行判断。
所以利用IP ID的方法对于TCP握手后就发生RST的情况并不适用但对于交互过程中发生RST的情况应该是适用的。你有机会也可以在遇到RST时来抓包验证一下。
![](https://static001.geekbang.org/resource/image/16/d5/168e95c43c17fc318d72df93205231d5.jpg?wh=1821x943)
## 07讲的答疑
### 思考题
1. tcp.payload eq abc这个过滤器可以搜索到精确匹配“abc”字符串的报文。那么如果是模糊匹配比如只要包含“abc”的报文我都想搜到这个过滤器又该如何写呢
2. 在工作中你遇到过跟TCP Keep-alive相关的问题吗你是怎么解决的呢
### 答案
第一个问题,其实在上节答疑课里我也提到过,就是利用各层的 **contains** 动词来写过滤器。比如:
* 应用层可以是 `http contains "abc"`
* 传输层可以是 `tcp contains "abc"`
* 网络层可以是 `ip contains "abc"`
* 数据链路层可以是 `frame contains "abc"`
这类过滤器的使用场景一般是这样的我们知道应用层的某一个信息比如HTTP报文里面的某个uuid然后再通过这个信息去找到对应的报文。
而在这里,又可以细分为两种不同的场景。
**第一种某一个TCP连接在通信两端的报文的对应。**比如客户端发出的HTTP报文里含有某个uuid那么我们需要在服务端的抓包文件里把这个TCP流过滤出来就可以用上面说的那些过滤器。
![](https://static001.geekbang.org/resource/image/61/e6/6180fd2be0492305d612eb6e2f2fbce6.jpg?wh=2000x954)
**第二种LB前后方两个不同TCP连接的报文的对应。**比如LB会转发请求给后端多台服务器中的某一个而这个选择一般是动态的这就给我们的排查工作带来了不小的障碍。
那么这里可以参考的方法就是:在“客户端<->LB”这一侧的连接中找到HTTP报文的某个uuid然后在“LB<->多个后端IP”这一侧的抓包中搜索这个uuid就能得到同样是这个请求的相关报文了。
我们可以看看下面这个示意图:
![](https://static001.geekbang.org/resource/image/28/6d/282b9b474a7b96998c657bfdbebae46d.jpg?wh=2000x1125)
也就是说TCP流1和TCP流1`'`还有TCP流2和TCP流2`'`分别是同一个TCP流在连接两侧的不同体现。而**由于流1和流2确实是完全不同的连接无法用任何TCP报文头部的信息来进行匹配所以只能用应用层的信息来找到对应关系**。
第二个问题也是一个开放式的问题。我看到不少同学都分享了他们的经验,这也增加了我自己的见识,真是一件多赢的事。比如,**@江山如画**同学对这个问题的回复是这样的:
> 在工作中我目前还没有碰到过和TCP Keep-alive有关的问题不过我之前看一些建立网络隧道的软件里有Keep-alive这个参数查询说它和SNAT有关就让我一直很迷惑。
>  
> 今天学习了老师的课我又去深究了下。原来Virtual Tunnel会建立虚拟网卡它和物理网卡之间需要做流量的桥接进而就需要做SNAT。然后SNAT会维护一个端口映射表因为链接太多可能会占满本地端口如果没有设置Keep-alive那么在一段时间内都没有传输数据的话就会把端口转换的记录给删掉这时候通信双方想再通信就不行了。所以需要每隔一段时间发送心跳包保证SNAT端口映射表中的记录不被删掉从而保证连接存活。
这实际上就是一个很典型的场景也就是NAT和TCP Keep-alive之间的关系。NAT是维护连接跟踪表的里面的表项有一定的有效期过了有效期的表项就会被清空。而TCP Keep-alive正好可以定期去刷新这个计时器让它“永葆青春”表项不被删除这个连接就可以一直愉快地工作下去了。
类似地,**@Geek\_535c45**同学也对TCP Keep-alive问题发出了灵魂的拷问
> Chrome每隔45秒发起TCP Keep-alive包的意义是什么呢
其实答案也跟上面的解释差不多Chrome每隔45秒的TCP Keep-alive主要是起到下面这两个作用
* **避免连接被中间环节给撤销掉**。一般客户端是在内网里的出公网的时候NAT会转换为公网IP跟对端通信。而跟上面的例子类似为了保持NAT表项不被回收所以Chrome要发送保活报文。
* **避免被服务端认为是无效连接给撤销掉**。服务端一般也有idle timeout时间也就是如果一个客户端连接超过一定时间没有报文传送服务端就会取消这个连接而且这时很可能不发送FIN或者RST导致客户端对连接失效这个事情并不知情。因为服务端要面向公网上成千上万的访问者所以它的逻辑是定时清除无效连接以保证服务端的资源得到合理的使用。
## 08讲的答疑
### 思考题
1. 在LB或者网关上修改MSS虽然可以减小MSS从而达到让通信成功这个目的但是这个方案有没有什么劣势或者不足也同样需要我们认真考量呢你可以从运维和可用性的角度来思考。
2. 你有没有遇到过MTU引发的问题呢
### 答案
对于第一个问题在LB或者网关上修改MSS这当然起到了作用。从短期来看也是很不错的临时方案。但是长期来看一旦客户和自己发生人员流动又缺乏文档记录就很可能会引起问题。比如说这个配置可能会被后来的人当作无用配置而清除而到时候如果没有人了解这个上下文显然就要花很多时间去排查给双方带去的业务影响也不会小。
尽管这可能算是一个非技术问题,但是也会影响到自己团队和兄弟团队的工作体验。所以我们做运维工作,还有一个重要的原则:**优先选择简洁又容易维护的方案**,而不是复杂且不易维护的方案,避免给以后的运维工作埋下隐患。
所以后来我们选择的长期方案还是在两端修改MTU这样的话客户团队就很清楚自己机器上的配置就有责任也有条件做好维护。
第二个问题,还是**@江山如画**同学,他分享了一个典型案例:
> 之前我们手动创建了虚拟网卡和物理网卡之间做了流量的桥接发现有些报文在这二者之间转发时会被丢掉。然后通过分析我们发现虚拟网卡的MTU设置过大并且报文DF位设置为了1后来我们通过ifconfig命令把虚拟网卡的MTU改小报文就可以正常转发了。
不用我做更多解释,你应该都很清楚这个场景里的技术细节了。这也是比较常见的情况,你掌握好以后应该也有机会运用到实际工作中,也就是说,**如果遇到丢包的问题你可以看看MTU的值是否超标了**。这是一个比较合算的“投资”,就花几分钟查一下,也许真能解决一个原本要查几个小时的大问题。
## 09讲的答疑
### 思考题
你有没有在工作中遇到过TCP传输速度相关的问题呢通过这节课的学习你已经掌握了传输速度相关的不少知识你准备怎么运用这些知识来解决这个传输问题呢
### 答案
我看到留言中有一个同学的回答是这样的:
> 我们公司之前有一个场景是多个客户端连接同一个服务端如果某个客户端下载文件或上传文件占用了大量的带宽就会导致新的客户端连不上服务端。后来我们就在服务端使用tc工具做了流控根据客户端个数来均分带宽还有限制单个IP的最大使用带宽。
其实, tc的原理是调整qdisc发送缓存队列超出队列的数据就丢弃这样的话发送端就探测到了“拥塞”进而会主动降速从而也就达到了限制速度的效果。而“拥塞”这个知识点又是我们在[第11讲](https://time.geekbang.org/column/article/486281)里深入探讨过的内容。其实从第9到13讲的内容都是关于传输的所以我建议你可以把第9到13讲的内容结合起来学习应该就能更加深入和全面地理解TCP传输这个重要话题了。
## 10讲的答疑
### 思考题
1. TCP的序列号和确认号最大可以到多少
2. 接收端只确认部分数据,导致了“数据滞留”现象,这个现象背后的原因可能是什么呢?
### 答案
第一个问题呢,其实是一个简单的**协议规范的问题**。这个知识点,你很容易就可以在[RFC793](https://www.ietf.org/archive/id/draft-ietf-tcpm-rfc793bis-25.html#section-3.10.7.4-2.1.2.1.9.7.2.1)里找到。TCP的序列号和确认号的长度都是4个字节4个字节也就是32位所以最大值是2^32-1也就是4G。我也建议你记住这些小的知识点因为它是在工作中高频使用的知识点你能记住的话就不用去搜索了可以提升工作的效率。
第二个问题就比较复杂了,而且因为当时的现场没有了,所以也没有标准答案。但是这个思考的过程对我们掌握这一讲的知识是很有帮助的,而且,我们其实还是可以大致推导出当时的情景。
我直接借用[第10讲](https://time.geekbang.org/column/article/485689)里面的一张示意图:
![](https://static001.geekbang.org/resource/image/5d/e1/5daef13e8f38a14a86c45yyd28513ee1.jpg?wh=2000x872)
我也来简单描述一下这个过程。
* 在t1这个时间点B通知A“我的接收窗口是1000字节”。
* 在t2这个时间点A收到了B的通知然后发送了1000字节此时这些都属于在途字节数。
* 在t3这个时间点B收到了1000字节此时接收窗口缓冲区全部用完Wireshark据此判断出“Window Full”。然后B向A发送确认包确认了300个字节。
* 在t4这个时间点A收到B确认300个字节的确认包于是**判断出在途字节数是700字节所以A能发的最多只有300字节。**
我们再来看一下接收端的报文处理流程。如果我们把缓冲区包括Ring Buffer和Recieve Buffer比作水池那么报文进入缓冲区就相当于有水龙头往里注水应用程序到缓冲区取走数据又相当于有水龙头放出水。这两个水龙头的速度的不同就造成了缓冲区的动态调整。
![](https://static001.geekbang.org/resource/image/a4/f5/a483db4d1ffe854e660d4c4611cde0f5.jpg?wh=2000x778)
那么,发生数据“滞留”,其实就相当于水池中一直囤着水,也就是“放水速度不够”,应用程序没有把数据都及时读取走导致的。
显然,比较理想的情况是放水的速度大于等于注水的速度,这是传输速度最快的方式。
但是,我们也要认识到,操作系统设立缓冲区的一大原因,就是考虑到“注水”和“放水”的速度本来就是经常不相等的,所以用缓冲区这个“空间”,来抵消速度不同导致的“时间”上的矛盾。简单来说就是“**用空间换时间**”。而且,不仅是网络接收和发送的缓冲区如此,大部分缓冲区也都是“用空间换时间”原则的体现。
所以通过这个思考你对TCP的缓冲区等概念是否也有了新的认识了呢
好了,今天的答疑就到这里。如果你还有什么疑问,同样可以回复到留言区,我们一同进步、成长。