首页 > 编程语言 >【Java】InetAddress.isReachable()失效的底层原因探究

【Java】InetAddress.isReachable()失效的底层原因探究

时间:2024-06-13 21:32:20浏览次数:14  
标签:Java socket ip ping InetAddress 权限 isReachable 连通性

文章目录

背景

在某些场景下,我们可能需要在Java中判断到某个主机的网络是否连通,比如我们的系统中可能有业务需要录入一些主机信息,此时为了更好的用户体验,我们可能会在前端页面上提供一个拨测按钮,让用户可以在输入主机地址之后进行连通性检验,来判断我们的系统和目标主机是否网络可达,同时也能一定程度上保证用户输入的主机地址有效性。

这只讨论使用最简单且通用的方式判断,如果我们清楚目标主机/应用有其他可用的连通性测试接口,那么使用这种更准确的接口当然是更好的解决方案。比如若要测数据库连通性,我们可以尝试建立一个数据库连接来判断,这样更加准确

提到这种简单的连通性测试,有计算机基础的同学肯定会想到ping命令,如果我们借助ping命令来检测目的主机的网络连通性,则Java代码可以这样写:

public static void main(String[] args) throws Exception {
    String ip = "192.168.121.136";
    linuxPing(ip);
}

/**
 * Linux平台下的Ping,使用Runtime来执行命令。Windows中ping参数不太一样,需要对应调整
 */
public static void linuxPing(String ip) throws IOException, InterruptedException {
    Process exec = Runtime.getRuntime().exec("ping -c 4 " + ip);
    BufferedReader reader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
        //解析每行输出,判断最终结果
    }
}

上述实现方式确实也是一种方案,但需要考虑几个跨平台带来的问题:

  1. Windows和Linux下的ping命令选项参数不一致,如指定次数时Windows中用-n选项,而Linux中用-c选项。需要判断平台,并使用对应的ping命令
  2. IPv6和IPv4使用的ping命令选项可能不一致(如Linux中ping命令在使用IPv6地址时应该为ping -6 xxx,Windows中可不指定),因此在实现时需要区分不同类型的地址
  3. 不同平台下ping命令的输出格式也不一定相同,如果使用命令的输出来解析结果,可能需要适配不同的格式

当然,这种常用功能想必早已经有开源成熟的解决方案,我们自己去实现属实有些许费力不讨好的意味。这时我们会发现,Java中早就给我们提供了InetAddress.isReachable()方法来完成这个任务,于是我们自信地写出以下代码:

public static void main(String[] args) throws Exception {
    String ip;
    if (args != null && args.length > 0) {
        ip = args[0];
    } else {
        ip = "192.168.121.136";
    }
    InetAddress address = null;
    try {
        address = InetAddress.getByName(ip);
        if (address.isReachable(5 * 1000)) {
            System.out.println(ip + " is reachable");
        } else {
            System.out.println(ip + " is not reachable");
        }
    } catch (IOException e) {
        System.out.println("exception: " + e.getMessage());
    }
}

上述代码使用了Java标准库方法,跨平台、IPv4/IPv6区分等问题我们就通通不用考虑了,只需要把异常处理一下即可。但代码上线时,问题又双叒叕来了——明明自测还好好的,怎么上线全部连通性测试都失败了??

现象

当我们使用上述代码进行测试时,会发现一种奇怪的现象——使用ping命令能ping通,但用InetAddress.isReachable()却老是不行

p1

查阅网上的资料,有的回答中提到了一个点,大概意思是:

使用isReachable()时,如果应用的权限不足,可能会导致isReachable()检测连通性失败,始终返回false

确实,我们的应用程序通常是不会以root等超级用户身份执行的,上图测试失败时我们就是以普通用户longqinx执行的,那换成root试试呢?

p2

果然!当我们切换到root用户时,isReachable()开始正常工作了;再次尝试用普通用户longqinx执行时,发现isReachable()又失败了…

注:上述示例中, sudo -u username command 表示以username用户身份执行command命令

问题原因

在网络上查了一圈儿资料之后,多数人都提到了权限问题和防火墙问题,但却几乎无人再去深入研究这个问题的根本原因所在。我心中的两个问题始终没有得到解答:

  1. isReachable()到底是干了啥才需要高级权限?
  2. 又是哪里和防火墙扯上了关系?

源代码才是最终的答案!于是我开始跟踪InetAddress.isReachable()的调用链,发现其最终调用了一个名为java.net.Inet4AddressImpl#isReachable0的Native方法

继续跟进源码,最终在OpenJDK的源码中找到了这个isReachable0的实现逻辑:

源码位置:https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libnet/Inet4AddressImpl.c

/*
 * Class:     java_net_Inet4AddressImpl
 * Method:    isReachable0
 * Signature: ([bI[bI)Z
 */
JNIEXPORT jboolean JNICALL
Java_java_net_Inet4AddressImpl_isReachable0(JNIEnv *env, jobject this,
                                            jbyteArray addrArray, jint timeout,
                                            jbyteArray ifArray, jint ttl)
{
    //.....省略......

    // Let's try to create a RAW socket to send ICMP packets.
    // This usually requires "root" privileges, so it's likely to fail.
    fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (fd == -1) {
        return tcp_ping4(env, &sa, netif, timeout, ttl);
    } else {
        // It didn't fail, so we can use ICMP_ECHO requests.
        return ping4(env, fd, &sa, netif, timeout, ttl);
    }
}

/**
 * ping implementation using tcp port 7 (echo)
 */
static jboolean
tcp_ping4(JNIEnv *env, SOCKETADDRESS *sa, SOCKETADDRESS *netif, jint timeout, jint ttl);

/**
 * ping implementation.
 * Send an ICMP_ECHO_REQUEST packet every second until either the timeout
 * expires or an answer is received.
 * Returns true if an ECHO_REPLY is received, false otherwise.
 */
static jboolean
ping4(JNIEnv *env, jint fd, SOCKETADDRESS *sa, SOCKETADDRESS *netif, jint timeout, jint ttl)

看到上述代码就十分明了了,注释也十分清晰。isReachable0这个Native方法会先尝试创建一个发送ICMP包的SOCK_RAW类型的socket,而创建这种socket需要用户有足够的权限,比如root权限,否则创建socket就会失败(返回-1)

若用户有足够的权限,则socket创建成功,此时通过ping4()函数来进行连通性测试,有兴趣的读者可以看这个函数的实现,其本质上就是socket编程发送ICMP包来检测,原理和ping命令类似

而当用户权限不足创建socket失败后,源码中开始走另一条路,即调用tcp_ping4()函数来进行连通性测试。此函数本质上是建立一个socket连接到目标主机上的echo服务,若echo服务有回应则认为是连通的。这里建立的socket是SOCK_STREAM类型,即普通的面向连接的socket,普通用户就有权限创建

echo服务是一种特殊服务,工作在端口 7 上,它只是简单地将收到的东西返回给发送者,通过这个特性就能判断目的主机网络是否连通。echo协议可参考RFC 862 - Echo Protocol (ietf.org)

说到这里,我们再回头看一开始的测试代码。既然在程序权限不足的情况下底层实现会通过echo协议来判断连通性,那为什么这个测试代码还是失败了呢?有经验的同学可能已经猜到了,正是防火墙在作祟
p3

从上图可以看出,我们使用普通用户权限也能正常进行连通性测试了,但前提是目标主机开放了echo服务所在的7端口

总结

通过上文的测试和分析,可以得出下述结论(Linux平台下):

  1. Java中InetAddress.isReachable()方法底层有两种机制来判断连通性,一种是使用ICMP报文,在程序有足够权限时使用;另一种则是使用echo协议,在程序权限不足以建立原生socket发送ICMP报文时使用。
  2. 当程序权限不足时会使用echo协议来进行连通性测试,此时需要目标主机端口 7 为开放状态才有意义。端口 7 未开放时也会测试失败

基于上述结论,这里给出使用InetAddress.isReachable()时的一些注意事项:

  1. 判断程序上线时是否有足够的权限,若是以普通用户权限运行则不建议使用InetAddress.isReachable()进行连通性测试
  2. 若权限不足但又非要使用InetAddress.isReachable()来进行连通性测试,则需要考虑被测试的目标主机是否会正常开放端口 7。若能保证被测的目标主机端口 7 都是开放的,那也可以使用此方法
  3. 默认情况下,笔者测试的CentOS7和Ubuntu 22.04系统中端口 7 都是默认关闭状态,使用时需要谨慎

标签:Java,socket,ip,ping,InetAddress,权限,isReachable,连通性
From: https://blog.csdn.net/Poker_Facex/article/details/139664440

相关文章

  • Java技术书籍大全
    目录入门书籍基础书籍多线程与并发网络编程数据结构语言基础进阶性能优化响应式编程JVM虚拟机代码&设计优化设计模式框架与中间件数据库缓存与NoSQL消息队列ORM框架Spring家族高并发分布式搜索引擎大数据架构分布式架构微服务架构架构方法论JVM周边语言项目管理&领导力&流程项目管......
  • 需求虽小但是问题很多,浅谈JavaScript导出excel文件
    最近我在进行一些前端小开发,遇到了一个小需求:我想要将数据导出到Excel文件,并希望能够封装成一个函数来实现。这个函数需要接收一个二维数组作为参数,数组的第一行是表头。在导出的过程中,要能够确保避免出现中文乱码的情况。另外,考虑到数组中可能包含回车、逗号、换行符等......
  • JAVA面向对象模型练习题3
    题目要求:        需求:在实际开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:ArraysUtils:        ①我们知道数组对象直接输出的时候是输出对象的地址的,而项目中很多地方都需要返回数组的内容,请在ArraysUtils中提供一个工具类......
  • java:【@Import】和【ImportSelector】的简单示例
    #代码结构#项目【myBeanBranch】【pom.xml】<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.15.RELEASE</version></dependency><dependency>......
  • java:【@ComponentScan】和【@SpringBootApplication】扫包范围的冲突
    #代码结构如下:注意【com.chz.myBean.branch】和【com.chz.myBean.main】这两个包是没有生重叠的。主程序【MyBeanTest1、MyBeanTest2、MyBeanTest3】这两个类是在包【com.chz.myBean.main】下#示例代码【pom.xml】<dependency><groupId>org.springframework.bo......
  • 【Java中常用的设计模式总结】
    文章目录概要1、单例模式(SingletonPattern)2、工厂模式(FactoryPattern)3、建造者模式(BuilderPattern)4、原型模式(PrototypePattern)5、适配器模式(AdapterPattern)6、桥接模式(BridgePattern)7、组合模式(CompositePattern)8、装饰器模式(DecoratorPattern)9、外观模式(Facade......
  • 【java计算机毕设】图书管理系统javaweb java MySQL springboot vue html maven送文档
    1项目功能【java计算机专业学长毕业设计分享】智慧图书管理系统JavaSpringBootvueHTMLMySQL前后端分离2项目介绍系统功能:智慧图书管理系统包括管理员和用户两种角色。管理员的功能包括在个人中心修改个人信息和密码,管理员功能模块管理管理员。基础数据管理模......
  • JavaScript 编程语言【数据类型】映射|集合|WeakMap and WeakSet
    文章目录MapandSet(映射和集合)MapMap迭代Object.entries:从对象创建Map]Object.fromEntries:从Map创建对象SetSet迭代(iteration)总结✅任务过滤数组中的唯一元素过滤字谜(anagrams)迭代键WeakMapandWeakSet(弱映射和弱集合)WeakMap使用案例:额外的数据使用案例:缓存Weak......
  • Java面试:Redis如何保证数据一致性?
    Redis是一个内存数据结构存储系统,广泛用于缓存、会话管理等场景。尽管Redis本身不是传统的关系型数据库,它仍然提供了一些机制来保证数据一致性。以下是Redis保证数据一致性的一些方法和机制:1.事务机制(Transactions)Redis支持事务,通过MULTI、EXEC、DISCARD、WATCH等命令实......
  • Java面试:final关键字有什么特点?
    final关键字在Java中有多种用途和特点,它可以用在类、方法和变量的声明中。以下是final关键字在不同上下文中的特点和用途:1.final类特点:当一个类被声明为final时,这个类不能被继承。不能创建这个类的子类,任何试图继承这个类的行为都会导致编译错误。示例:publicfinalc......