美文网首页
一次网络连通性监控误报问题诊断

一次网络连通性监控误报问题诊断

作者: 艺超51iwowo | 来源:发表于2022-07-01 07:33 被阅读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服务的,通常就是发起服务器什么,就会返回什么。但是由于存在安全隐患,该端口通常都是关闭的。


    image.png

    既然TCP端口7不通,而且Ping也不通,那究竟如何判断网络是正常的呢? 还是要求助源码。
    github上找了一下InetAddress的实现(https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/solaris/native/java/net/Inet4AddressImpl.c

    image.png

    仿佛发现了新大陆一样,发现即使是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了。


    image.png

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

    关于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程序运行,


    image.png

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

    验证2:使用Root账户运行

    image.png

    在这种情况下,使用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
    
    image.png

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

    监控服务器验证

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

    抓取icmp包

    命令

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

    可以Ping通的情况下,应该包含request请求和reply响应。


    image.png

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

    抓取tcp端口7的包

    命令

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

    执行结果(右上角是tcpdump的抓包结果,右下角是wireshark的结果)


    image.png

    说明:

    • 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. 对于源码问题,一定要找到问题根源,并加以验证。

    相关文章

      网友评论

          本文标题:一次网络连通性监控误报问题诊断

          本文链接:https://www.haomeiwen.com/subject/lnzqbrtx.html