Wireshark网络包分析实战三:域名访问不通

一、Overview

我的工作内容是模拟各种现实中的故障,比如网络丢包、延迟,CPU满载,磁盘爆满等。

最近在盘点「域名访问不通」这一项功能时,发现某些情况下,没有按照预期生效。

为了讲清楚遇到的问题,我先来解释我到底是如何实现「域名访问不通」这一功能的。

其实原理很简单,那便是直接修改/etc/hosts文件。

比如你想要实现访问www.baidu.com不通的情形,你完全可以如此做:

arduino 复制代码
127.0.0.1 odin.xiaojukeji.comwww.baidu.com #chaosblade

如此一来,当你在浏览器或者终端curl访问www.baidu.com的时候,会报错。

我遇到的问题是,在某种情况下,即便我进行了上述的设置,但结果还是访问到百度的服务器,而不是我设置的127.0.0.1

下面讲一下pinggo-http两种失效的场景,失效的现象捕捉以及原因。

二、ping

2.1、问题复现

我们的目标是将www.baidu.com映射到本地local 127.0.0.1

修改 /etc/hosts文件之前,我们先在目标机器上运行ping www.baidu.com

python 复制代码
~$ ping www.baidu.com
PING www.baidu.com (110.242.68.4) 56(84) bytes of data.
64 bytes from 110.242.68.4: icmp_seq=1 ttl=55 time=3.32 ms
64 bytes from 110.242.68.4: icmp_seq=2 ttl=55 time=4.39 ms
64 bytes from 110.242.68.4: icmp_seq=3 ttl=55 time=2.39 ms
64 bytes from 110.242.68.4: icmp_seq=4 ttl=55 time=3.66 ms

在不中断上述ping命令的前提下,进行域名访问不通(修改/etc/hosts文件 127.0.0.1 www.baidu.com #chaosblade)。

在修改完/etc/hosts文件后,发现ping www.baidu.com的结果未曾改变,依然是from 110.242.68.4

但此时开启另一个终端,运行ping www.baidu.com,发现是符合预期的!

python 复制代码
~$ ping www.baidu.com
PING www.baidu.com (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.014 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.007 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.011 ms
64 bytes from localhost (127.0.0.1): icmp_seq=4 ttl=64 time=0.009 ms
64 bytes from localhost (127.0.0.1): icmp_seq=5 ttl=64 time=0.007 ms
64 bytes from localhost (127.0.0.1): icmp_seq=6 ttl=64 time=0.006 ms

难道是ping的实现中有缓存?

2.2、问题排查

其实就是看ping命令的实现原理

经过源码解读,ping的实现如下:在进入不断发出icmp数据包的逻辑之前,用户指定的域名只会被解析一次。所以,在ping的过程中,即便修改hosts文件,正在运行的ping进程是没有感知的。

三、http-go

3.1、问题复现

后来我发现,go进行http请求的时候,也会发生与ping类似的情况。

比如下面的例子,进行50www.baidu.com主页的http get请求,每次请求间隔为3s。在程序运行的过程中,修改/etc/hosts文件,看是否能够正常发出请求和收到响应。

go 复制代码
# 测试代码
func TestHTTPGet(t *testing.T) {
  url := "http://www.baidu.com"
  for i := 0; i < 50; i++ {
   resp, err := http.Get(url)
   if err != nil {
    t.Error(err)
   }
   defer resp.Body.Close()
   body, err := ioutil.ReadAll(resp.Body)
   if err != nil {
    t.Error(err)
   }
   fmt.Println(string(body))
   time.Sleep(3 * time.Second)
  }
}

抓包展示1:细节

抓包展示2:统计

发现并没有因为更改了hosts文件,gohttp库便将www.baidu.comIP指向127.0.0.1

3.2、问题排查

通过抓包分析,这50http请求使用了长连接技术,因为源端口都是58443,当然这也符合HTTP 1.1 persistent connection的特性。

你可能有疑问,通过这个HTTP请求头部来看,并没有发现表示长连接 persistent connection的设置啊?

确实没有,但这不过是因为HTTP/1.1默认就是开启长连接的。

3.3、另外复现方式

上面go代码,又得运行又得抓包才能确认http请求是长连接,所以即便是修改了/etc/hosts文件,也没有进行新的dns查询,导致一直沿用首次dns查询的ip,而不是设置的127.0.0.1

有没有一种方式,不用抓包,我们也能得知dns查询只进行了一次呢?当然可以。

我们可以利用标准库 httptrace

go 复制代码
func createHTTPTraceRequest(ctx context.Context, url string) (*http.Request, error) {
  req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
  if err != nil {
   return req, err
  }

  // 增加一些行为日志打印
  trace := &httptrace.ClientTrace{
   GotConn: func(info httptrace.GotConnInfo) {   // 获取底层连接,http通过底层连接将请求发送出去
    fmt.Printf("GotConn info:%+v\n", info)
   },
   DNSStart: func(info httptrace.DNSStartInfo) { // 进行dns查询,打印之
    fmt.Printf("DNSStart info:%+v\n", info)
   },
   DNSDone: func(info httptrace.DNSDoneInfo) {   // 完成dns查询,打印之
    fmt.Printf("DNSDone info:%+v\n", info)
   },
  }
  traceCtx := httptrace.WithClientTrace(ctx, trace)
  req = req.WithContext(traceCtx)
  return req, nil
}

func TestWithTraceHTTP(t *testing.T) {
  url := "http://www.baidu.com"
  for i := 0; i < 50; i++ {
  // 构建request结构
   req, err := createHTTPTraceRequest(context.Background(), url)
   if err != nil {
    t.Error(err)
   }
   // 发送请求
   resp, err := http.DefaultClient.Do(req)
   if err != nil {
    t.Error(err)
   }
   defer resp.Body.Close()
   _, err = ioutil.ReadAll(resp.Body)
   if err != nil {
    t.Error(err)
   }
   time.Sleep(3 * time.Second)
  }
}

输出如下:

css 复制代码
DNSStart info:{Host:www.baidu.com}
DNSDone info:{Addrs:[{IP:110.242.68.3 Zone:} {IP:110.242.68.4 Zone:} {IP:2408:871a:2100:2:0:ff:b09f:237 Zone:} {IP:2408:871a:2100:3:0:ff:b025:348d Zone:}] Err:<nil> Coalesced:false}
GotConn info:{Conn:0x14000186000 Reused:false WasIdle:false IdleTime:0s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001353958s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001554083s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00088375s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00107s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001237583s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001469083s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.002459209s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00587425s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001838042s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.007866292s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00083075s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000906125s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001565917s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.002019208s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001439292s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000538625s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001538042s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001537375s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000745167s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000566916s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001680083s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001349s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001143166s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00183775s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001327459s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001370041s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001351875s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001454833s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001327166s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000833042s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001082209s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001355333s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.002008875s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001410666s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000633791s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001041583s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000719583s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000613334s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000894167s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000837292s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001611s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001473875s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000864875s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000621208s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.000998083s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001407s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00110875s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.00126275s}
GotConn info:{Conn:0x14000186000 Reused:true WasIdle:true IdleTime:3.001923709s}

仔细查看上述日志信息,你会发现如下事实:

1、50http请求只进行了一次dns查询,这是我们修改/etc/hosts文件未达预期的根本原因。

2、只有第一次发送http连接时,创建了新的底层连接,后面的请求全都是复用之前的底层连接。

何以见得?

GotConn中的Reused字段,false表示新建连接,true表示复用底层连接。

四、Summary

当操作系统收到dns查询的委托,它会首先查看待查询的域名在/etc/hosts文件中是否有相应配置,若没有则调用dns服务来进行递归查询。

修改完/etc/hosts文件之后,我们期望它能够立马生效,它也确实"立马生效了"。

但还是存在一些特殊情况,即便修改文件,文件的改动也触达不了这些场景,比如上文中讲述的pinghttp

我觉得我们可以称之为缓存问题。

啊哈,这就是计算机的世界中两大难题之一:

  • 命名
  • 缓存

哈哈

相关推荐
mysql学习中3 小时前
Linux的环境变量
linux·运维·服务器·tcp/ip·centos
江湖十年9 小时前
在 Go 中如何优雅的处理错误
后端·go
ZachOn1y11 小时前
计算机网络:计算机网络体系结构 —— 专用术语总结
网络·tcp/ip·计算机网络·考研必备
276695829213 小时前
京东e卡滑块 分析
java·javascript·python·node.js·go·滑块·京东
爱码少年14 小时前
springboot工程中使用tcp协议
spring boot·后端·tcp/ip
小堃学编程21 小时前
计算机网络(十) —— IP协议详解,理解运营商和全球网络
网络·tcp/ip·计算机网络
小鹿( ﹡ˆoˆ﹡ )1 天前
探索IP协议的神秘面纱:Python中的网络通信
python·tcp/ip·php
5967851541 天前
DotNetty ChannelRead接收数据为null
tcp/ip·c#
hgdlip1 天前
如何快速切换电脑的ip地址
网络·tcp/ip·电脑
程序员-珍1 天前
虚拟机ip突然看不了了
linux·网络·网络协议·tcp/ip·centos