1.背景
??????? 在运行JBoss程序时,出现如下错误。
Caused by: java.net.UnknownHostException: bogon: bogon: 未知的名称或服务
at java.net.InetAddress.getLocalHost(InetAddress.java:1506)
at org.jboss.ws.common.management.AbstractServerConfig.setWebServiceHost(AbstractServerConfig.java:133)
at org.jboss.as.webservices.config.ServerConfigImpl.setWebServiceHost(ServerConfigImpl.java:98)
at org.jboss.as.webservices.dmr.WSSubsystemAdd.createServerConfig(WSSubsystemAdd.java:103)
... 12 more
Caused by: java.net.UnknownHostException: bogon: 未知的名称或服务
at java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:929)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1324)
at java.net.InetAddress.getLocalHost(InetAddress.java:1501)
... 15 more
????????最终定位原因是因为无法通过主机名,解析出IP地址所致,将/etc/hosts中增加了主机名与IP地址的映射后问题解决。但是发现解决的太容易,有些问题和原理没有想透彻。
??????? bogon是什么含义?在哪里定义的?
????????java的java.net.InetAddress.getLocalHost为何与bogon扯上关系?
2.分析过程
??????? 在分析之前先了解一下Linux命令 ipcalc。ipcalc是RedHat提供的一个开源软件,其提供了一种计算主机IP信息的简单方法。比如可以通过IP地址与子网掩码计算出网络地址、广播地址等等,也可以通过IP地址反向获取主机名。
[yeqiyu@CentOS7-229 ~]$ ipcalc --help
Usage: ipcalc [OPTION...]
-c, --check Validate IP address for specified address family
-4, --ipv4 IPv4 address family (default)
-6, --ipv6 IPv6 address family
-b, --broadcast Display calculated broadcast address
-h, --hostname Show hostname determined via DNS
-m, --netmask Display default netmask for IP (class A, B, or C)
-n, --network Display network address
-p, --prefix Display network prefix
-s, --silent Don't ever display error messages
Help options:
-?, --help Show this help message
--usage Display brief usage message
??????? 在不同状态使用ipcalc命令,查看效果。可以看到,已经出现了HOSTNAME=bogon关键信息。
#hosts中未进行主机名与IP的映射
[yeqiyu@CentOS7-229 ~]$ ipcalc -h 192.168.130.229
HOSTNAME=bogon
#hosts中进行了主机名与IP的映射
[yeqiyu@CentOS7-229 ~]$ ipcalc -h 192.168.130.229
HOSTNAME=vcentos7-229
????????因此,可以推测,当本地DNS未设置时,会使用公共DNS,但公共DNS无法将IP地址反向解析为主机名时,会使用默认值bogon。我们换个方式佐证一下。
??????? 更换同版本的Linux环境
[yeqiyu@s168 ~]$ nslookup 192.168.129.168
168.129.168.192.in-addr.arpa name = bogon.
Authoritative answers can be found from:
??????? 更换Windows机器查看现象。DNS服务商将IP反向解析为了bogon。
C:\Users\YQY>nslookup 192.168.40.105
服务器: public1.114dns.com
Address: 114.114.114.114
名称: bogon
Address: 192.168.40.105
C:\Users\YQY>nslookup 202.106.0.20
服务器: public1.114dns.com
Address: 114.114.114.114
名称: gjjline.bta.net.cn
Address: 202.106.0.20
??????? 通过不同操作系统、不同工具、不同IP验证,基本可以得出与IP地址的反向解析有关。当目标IP是私有地址或者无法被DNS反向解析时,都会将IP解析为bogon。
3.Bogon是什么
????????Bogon IP Addresses是一组IP地址,不被互联网地址分配机构(IANA)和RIR(区域互联网注册)分配给任何实体,这个未分配的地址空间称为虚假空间。bogon还包括保留的私有地址和链路本地地址范围(Martian Packets)。
??????? IPv4 Bobon范围
Netblock | Description |
---|
0.0.0.0/8 | "This" network | 10.0.0.0/8 | Private-use networks | 100.64.0.0/10 | Carrier-grade NAT | 127.0.0.0/8 | Loopback | 127.0.53.53 | Name collision occurrence | 169.254.0.0/16 | Link local | 172.16.0.0/12 | Private-use networks | 192.0.0.0/24 | IETF protocol assignments | 192.0.2.0/24 | TEST-NET-1 | 192.168.0.0/16 | Private-use networks | 198.18.0.0/15 | Network interconnect device benchmark testing | 198.51.100.0/24 | TEST-NET-2 | 203.0.113.0/24 | TEST-NET-3 | 224.0.0.0/4 | Multicast | 240.0.0.0/4 | Reserved for future use | 255.255.255.255/32 | Limited broadcast |
4.JAVA解析过程分析
??????? 上述过程分析了在操作系统层面,什么情况会返回bogon,那么,JAVA层面是如何实现解析的呢?
??????? 分析JDK源码,找到类InetAddress,查看方法 getLocalHost(),可以看到其通过impl.getLocalHostName()获取了主机名,然后再通过主机名获取IP地址。根据错误提示,程序已经获得了主机名(bogon),因此关键点要找到getLocalHostName的具体实现。
/**
* Returns the address of the local host. This is achieved by retrieving
* the name of the host from the system, then resolving that name into
* an {@code InetAddress}.
*
* <P>Note: The resolved address may be cached for a short period of time.
* </P>
*
* <p>If there is a security manager, its
* {@code checkConnect} method is called
* with the local host name and {@code -1}
* as its arguments to see if the operation is allowed.
* If the operation is not allowed, an InetAddress representing
* the loopback address is returned.
*
* @return the address of the local host.
*
* @exception UnknownHostException if the local host name could not
* be resolved into an address.
*
* @see SecurityManager#checkConnect
* @see java.net.InetAddress#getByName(java.lang.String)
*/
public static InetAddress getLocalHost() throws UnknownHostException {
SecurityManager security = System.getSecurityManager();
try {
String local = impl.getLocalHostName();
if (security != null) {
security.checkConnect(local, -1);
}
if (local.equals("localhost")) {
return impl.loopbackAddress();
}
InetAddress ret = null;
synchronized (cacheLock) {
long now = System.currentTimeMillis();
if (cachedLocalHost != null) {
if ((now - cacheTime) < maxCacheTime) // Less than 5s old?
ret = cachedLocalHost;
else
cachedLocalHost = null;
}
// we are calling getAddressesFromNameService directly
// to avoid getting localHost from cache
if (ret == null) {
InetAddress[] localAddrs;
try {
localAddrs =
InetAddress.getAddressesFromNameService(local, null);
} catch (UnknownHostException uhe) {
// Rethrow with a more informative error message.
UnknownHostException uhe2 =
new UnknownHostException(local + ": " +
uhe.getMessage());
uhe2.initCause(uhe);
throw uhe2;
}
cachedLocalHost = localAddrs[0];
cacheTime = now;
ret = localAddrs[0];
}
}
return ret;
} catch (java.lang.SecurityException e) {
return impl.loopbackAddress();
}
}
getLocalHostName实现了接口InetAddressImpl的getLocalHostName方法,而Inet4AddressImpl又是InetAddressImpl的一个实现,可以找到该方法。
public native String getLocalHostName() throws UnknownHostException;
关键native表示该方法由C语言实现,找到源码。
JNIEXPORT jstring JNICALL
Java_java_net_Inet4AddressImpl_getLocalHostName(JNIEnv *env, jobject this) {
char hostname[NI_MAXHOST+1];
hostname[0] = '\0';
if (JVM_GetHostName(hostname, sizeof(hostname))) {//如果获取失败,则设置默认值localhost
/* Something went wrong, maybe networking is not setup? */
strcpy(hostname, "localhost");
} else {
struct addrinfo hints, *res;
int error;
hostname[NI_MAXHOST] = '\0'; //设置默认结束符
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME; //这个参数很重要,要求返回主机规范名称,非别名
hints.ai_family = AF_INET;
error = getaddrinfo(hostname, NULL, &hints, &res); //这里hostname会重新改写
if (error == 0) {/* host is known to name service */
getnameinfo(res->ai_addr,
res->ai_addrlen,
hostname,
NI_MAXHOST,
NULL,
0,
NI_NAMEREQD);
/* if getnameinfo fails hostname is still the value
from gethostname */
freeaddrinfo(res);
}
}
return (*env)->NewStringUTF(env, hostname);
}
????????可以看到关键函数JVM_GetHostName和getaddrinfo,对变量hostname做了操作。参照JDK的源码,写一个Demo,来看下在哪里发生了变化。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#define NI_MAXHOST 1025
int main() {
char hostname[NI_MAXHOST+1] = {0};
gethostname(hostname, NI_MAXHOST);
printf("hostname_1=%s\n", hostname);
struct addrinfo hints, *res;
int error;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
error = getaddrinfo(hostname, NULL, &hints, &res);
printf("hostname_2=%s\n", hostname);
if (error == 0) {/* host is known to name service */
getnameinfo(res->ai_addr,
res->ai_addrlen,
hostname,
NI_MAXHOST,
NULL,
0,
NI_NAMEREQD);
/* if getnameinfo fails hostname is still the value
from gethostname */
freeaddrinfo(res);
}
printf("hostname_3=%s\n", hostname);
}
编译
gcc gethostname_demo.c -o gethostname_demo
执行
[root@CentOS7-229 HostnameDemo_lib]# ./gethostname_demo
hostname_1=CentOS7-229
hostname_2=CentOS7-229
hostname_3=bogon
可以看到,经过getaddrinfo函数之后,hostname变成了bogon
此时,我们修改host文件,补充主机名到IP的解析,然后再用demo查看,可以获取主机名了。
[root@CentOS7-229 HostnameDemo_lib]# cat /etc/hosts
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
192.168.130.229 CentOS7-229
[root@CentOS7-229 HostnameDemo_lib]# ./gethostname_demo
hostname_1=CentOS7-229
hostname_2=CentOS7-229
hostname_3=CentOS7-229
?????? 关键点在于getnameinfo函数,如果在没有进行主机名和IP映射时,getnameinfo做了哪些事情?
????????
#include <netdb.h>
#include <sys/socket.h>
int getnameinfo(const struct sockaddr* restrict addr, socklen_t addrlen,
char* restrict host, socklen_t hostlen,
char* restrict serv, socklen_t servlen, int flags);
???是getaddrinfo的互补函数,以一个套接字地址为参数,返回描述其中的主机的一个字符串和描述其中的服务的另一个字符串。那么问题来了,明明已经通过gethostname和getaddrinfo函数拿到了主机名,又要再通过getnameinfo重新走一遍hosts、reslov.conf,将bogon ip反向解析为bogon名称,这么做的原因是什么呢?
?当前的能力还无法理解,暂且记录下来,后续再补充。
5.jboss与bogon
????????当jboss.bind.address=127.0.0.1或者192.168.130.229时,虽然会出现如下警告,但并不会出现ERROR级别错误。
21:59:44,867 WARN [com.arjuna.ats.arjuna] (MSC service thread 1-8) ARJUNA012210: Unable to use InetAddress.getLocalHost() to resolve address.
??????? 在JBOSS启动时,jboss.bind.address=0.0.0.0,且不在hosts中映射,两个条件同时都满足,才会出现UnknownHostException: bogon: bogon: 未知的名称或服务。由JAVA实现的JBOSS又是怎么和bogon关联起来的呢? ????
??????? 这需要研究一下jboss.bind.address的实现机制。初步推测是如果设置具体IP地址,则直接监听这个指定的IP即可。如果设置为0.0.0.0,则需要监听本机上的所有IP,所以会调用getlocalhost的方法,来获取本机所有IP,于是便出现了步骤4的现象。
6.总结
??????? 借着这个问题,重新温习了Linux下DNS解析过程,将JAVA DNS内部实现原理进行了基本的学习,在Linux下的C开发与使用有了进一步了解。虽留有遗憾,仍然收获满满。
7.参考
ipcalc命令 – 简单的IP地址计算器 – Linux命令大全(手册)
Bogon IP addresses, ipv4 and ipv6 bogon IP Ranges
networking - Java DNS resolution hangs forever - Stack Overflow
getnameinfo(3) - Linux manual page
getaddrinfo(3) - Linux manual page
https://www.ibm.com/docs/en/zos/2.3.0?topic=functions-getnameinfo-get-name-information
Jdk8 DNS解析 - Loull - 博客园
|