一次网络连通性问题诊断

3 篇文章 1 订阅
1 篇文章 0 订阅

背景

业务存在一个监控系统,需要监控一些设备的网络是否正常。不过最近发现经常会发生断网误报情况,所以深入探究一下问题原因。

排查过程

阶段一:一直困扰在Ping的请求是否通的误区

开始的时候,粗略看了一下源代码,再加上平时一直使用Ping作为网络探测的首要方式,所以一直认为只有Ping通,才认为网络是通的。

if (!PingUtil.ping(ip)) {
    return DISCONNECT;
}

但是事实并非如此,发现有一些主机,从监控主机ping依然是不通的,但是监控平台显示网络状态是正常的。

阶段二:仔细阅读代码,判断问题

这就说明判断对方主机是否断网的策略应该不止是Ping方式。
进入PingUtil.cmdPing源码,可以看到包含两种策

  • 策略一:使用JDK InetAddress的isReachable探测。
  • 策略二:如果策略一失败,则尝试使用Ping方法探测。
public static boolean ping(String ipAddress) {
    try {
        InetAddress inet = InetAddress.getByName(ipAddress);
        boolean isReachable = inet.isReachable(1000);
        if (isReachable) {
            return true;
        }
    } catch (IOException e) {
        logger.error("ping reachable error,ip:" + ipAddress, e);
    }

    String osName = System.getProperty("os.name");
    String cmd = null;
    int pingTimes = 4;
    if (osName.contains(WINDOWS)) {
        cmd = "ping -n " + pingTimes + " " + ipAddress;
    } else {
        cmd = "ping -c " + pingTimes + " " + ipAddress;
    }
    Process process = null;
    try {
        process = Runtime.getRuntime().exec(cmd);
    } catch (IOException e) {
        logger.error("ping exec error,ip:" + ipAddress, e);
    }
    if (process == null) {
        return false;
    }
    int connected = 0;
    StringBuilder content=new StringBuilder();
    try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
        String line = null;
        while ((line = in.readLine()) != null) {
            content.append(line).append(";");
            if (line.contains("ttl=") || line.contains("TTL=")) {
                connected++;
            }
        }
    } catch (IOException e) {
        logger.error("ping read error,ip:" + ipAddress, e);
    }

    return connected > 0;
}

回到源码,只能说明策略一在起作用,也就是InetAddress.isReachable
看一下代码的JDK注释

/**
     * Test whether that address is reachable. Best effort is made by the
     * implementation to try to reach the host, but firewalls and server
     * configuration may block requests resulting in a unreachable status
     * while some specific ports may be accessible.
     * A typical implementation will use ICMP ECHO REQUESTs if the
     * privilege can be obtained, otherwise it will try to establish
     * a TCP connection on port 7 (Echo) of the destination host.
     * <p>
     * The timeout value, in milliseconds, indicates the maximum amount of time
     * the try should take. If the operation times out before getting an
     * answer, the host is deemed unreachable. A negative value will result
     * in an IllegalArgumentException being thrown.
     *
     * @param   timeout the time, in milliseconds, before the call aborts
     * @return a {@code boolean} indicating if the address is reachable.
     * @throws IOException if a network error occurs
     * @throws  IllegalArgumentException if {@code timeout} is negative.
     * @since 1.5
     */
public boolean isReachable(int timeout) throws IOException {
    return isReachable(null, 0 , timeout);
}

有几个点要注意

  • 典型的实现是使用ICMP协议,不过这要求有Root权限,有资料显示是因为ICMP需要使用Raw Socket。
  • 如果没有权限,那么就会尝试使用TCP 7端口同远程host建立连接。

对于我们常规的java应用,都不是以Root权限运行的,所以依赖于同远程host建立TCP连接的方式。到这里,立马去机器上执行

telnet xx.xx.xx.xx 7

很遗憾,直接得到了Conenction Refused。

这里也补充一下端口7的说明,端口7是echo服务的,通常就是发起服务器什么,就会返回什么。但是由于存在安全隐患,该端口通常都是关闭的。
在这里插入图片描述

既然TCP端口7不通,而且Ping也不通,那究竟如何判断网络是正常的呢? 还是要求助源码。
github上找了一下InetAddress的实现(https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/solaris/native/java/net/Inet4AddressImpl.c
在这里插入图片描述
发现了新大陆一样,发现即使是connection refused,也可以认为网络是可达的。
对connnection refused可以解释为

  1. 发送一个TCP SYN packet 给远端机器。
  2. 然后收到一个 TCP RST packet 响应。

也就是说请求可以到达远端机器,至于远端给的是可以连接的响应,还是拒绝的响应,至少可以证明链路是通的。
这里有一点需要注意,有一些特殊情况下,链路中间的防火墙会拦截TCP SYN packet,然后给一个TCP RST响应。所以InetAddress.isReachable通常是局域网内的判断。
这里以百度的一个ip为例,发现的确向对方TCP端口7发送了请求,但是并未收到响应,所以此时根据InetAddress.isReachable得到的结果就是false了。
在这里插入图片描述

阶段三:问题验证,事实说话

关于InetAddress.isReachable方法,还是需要验证一下。

本地验证

就将源码单独运行一下,本地开启wireshark,抓包验证。

public static void main(String[] args) throws IOException, InterruptedException {
    while (true) {
        InetAddress inet = InetAddress.getByName("192.168.2.116");
        boolean isReachable = inet.isReachable(1000);
        System.out.println(isReachable);
        Thread.sleep(3000);
    }
}
验证1:非root账号运行

直接使用Idea Run程序运行,
在这里插入图片描述

注意右下角,可以看到如前面阶段二所述,是给远端 TCP 7发送建连接请求,并且也收到了一个RST响应,而且判断结果是true。

验证2:使用Root账户运行

在这里插入图片描述

在这种情况下,使用Mac Root账号执行,然后抓包后,发现是使用的ICMP协议了。

验证3:使用非Root账户运行,并且模拟端口7不可达。
linux环境下,可以使用iptables做一些路由。如果是Mac电脑,需要使用pfctl工具(Mac下使用pfctl)。
修改pfctl,将发送给远端端口7的包drop掉。

block drop out proto tcp from any to 192.168.2.116 port 7

在这里插入图片描述

可以看到此时的InetAddress.isReachable返回了false。

监控服务器验证

linux服务器上可以借助tcpdump进行抓包。

抓取icmp包

命令

sudo tcpdump -c 5 -nn -i 网卡 icmp

可以Ping通的情况下,应该包含request请求和reply响应。
在这里插入图片描述

如果无法ping通的话,只包含了request请求。

抓取tcp端口7的包

命令

sudo tcpdump -nn tcp port 7 |grep '目标ip'

执行结果(右上角是tcpdump的抓包结果,右下角是wireshark的结果)
在这里插入图片描述

说明:

  • Flag说明
    | | 标志类型 | 描述 |
    | — | — | — |
    | S | SYN | Connection Start |
    | F | FIN | Connection Finish |
    | P | PUSH | Data push |
    | R | RST | Connection reset |
    | . | ACK | Acknowledgment |

可以看到通过tcp端口7发送SYN包,收到一个RST包。这样按照JDK说明,是可以判定网络可达的。

阶段四: 优化断网监控策略

Action 1:

从监控集群到目标主机的链路来看,依赖tcp和icmp两种协议的数据包,所以需要保障双向(从目标主机到监控集群、从监控集群到目标主机)的路由策略,不能拦截数据包。这一步需要网络运维同学配合完成。

Action 2:

原有的监控策略是依赖InetAddress.isReachable的,也就是从监控集群发出Ping请求或者是请求tcp 7端口。两种方式都属于监控集群主动监控。 在当前情况下,由于程序是非Root启动,所以会首先依赖tcp 7端口的响应。但是当偶尔发生丢包或者超时,就会被误认为断网。
所以增加一种校验策略,由于目标主机是会定期上传心跳,而上传心跳也就意味着设备可以通过网络访问到日志平台,可以基于心跳时间判断网络是否正常。

调整后的策略

  1. 判断最后一次心跳是否在允许的时间区间内。
  2. 如果心跳不正常,使用InetAddress.isReachable进行判断。
  3. 如果InetAddress.isReachable不通,尝试直接执行Ping命令。

只有当上述三种策略都不通的情况下,则认定目标主机已经断网。

总结

  1. 运维人员依赖监控告警,如果频繁的误报,会影响大家的判断,所以要尽可能减少误报,增加准确率。当然依赖心跳会引入告警及时性的问题。
  2. 网络是非常复杂的,在判定条件上,可以综合多种方式一起判断。同时要善于使用抓包工具进行验证。
  3. 对于源码问题,一定要找到问题根源,并加以验证。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

51iwowo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值