为什么 localhost 解析为 ::1 而不是 127.0.0.1

jan*_*jan 10 networking hosts ipv6

有了getent hosts localhost,我才得到::1,虽然我期待127.0.0.1。我禁用了 IPv6,所以得到::1更令人惊讶。更令人困惑的是,当 I 时ping localhost,ping 会发送到127.0.0.1哪个有效。有人可以解释一下吗?

~: getent hosts localhost
::1             localhost

~: grep 'hosts:' /etc/nsswitch.conf 
hosts: files mymachines myhostname resolve [!UNAVAIL=return] dns

~: cat /etc/sysctl.d/disable_ipv6.conf 
net.ipv6.conf.all.disable_ipv6=1

~: ping ::1
connect: Network is unreachable

~: ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.022 ms

~: ping localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.015 ms
Run Code Online (Sandbox Code Playgroud)

编辑:localhost我的/etc/hosts.

小智 4

找到这个并不容易(但很有趣:))。

简短回答

gethostbyname2() 使用 __lookup_name(),具有一些环回('lo')接口的硬编码值。当您为“getent ports”命令指定“localhost”时,它最终会在尝试 IPv4 之前使用 IPv6 的默认值,因此最终会得到 ::1。您可以更改 getent 的代码以获得 127.0.0.1,如下所示:

  1. 从github下载getent源码
  2. 在getent.c 下的hosts_keys() 中注释掉以下行(#329): //else if ((host = gethostbyname2 (key[i], AF_INET6)) == NULL)
  3. 从源代码编译并运行:

结果:

$make clean && make && ./getent hosts localhost
rm -f *.o
rm -f getent
gcc -g -Wall -std=gnu99 -w -c getent.c -o getent.o
gcc  getent.o -Wall -lm -o getent
127.0.0.1       localhost
Run Code Online (Sandbox Code Playgroud)

更多细节

getent 工具使用musl 库定义和实现的函数。当我们运行命令时

$getent hosts localhost
Run Code Online (Sandbox Code Playgroud)

该工具调用getent.c 下的hosts_keys() 函数来解析提供的密钥。该函数尝试通过 4 种方法进行解析:

  1. IPv6 的 gethostbyaddr(在此实例中失败)。
  2. IPv4 的 gethostbyaddr(在此实例中失败)。
  3. IPv6 的 gethostbyname2(由于硬编码值,对于 localhost 总是成功)。
  4. IPv4 的 gethostbyname2(由于在#3 上成功,所以不尝试)。

所有musl功能都在/src/network/下实现,参见这里。gethostbyname2()(在 gethostbyname2.c 中实现)调用 gethostbyname2_r()(在 gethostbyname2_r.c 中实现),后者调用 __lookup_name()(在lookup_name.c 中)。__lookup_name() 再次作为如何解析主机名的几个选项,第一个是 name_from_null (在同一文件中):

static int name_from_null(struct address buf[static 2], const char *name, int family, int flags)
{
    int cnt = 0;
    if (name) return 0;
    if (flags & AI_PASSIVE) {
            if (family != AF_INET6)
                    buf[cnt++] = (struct address){ .family = AF_INET };
            if (family != AF_INET)
                    buf[cnt++] = (struct address){ .family = AF_INET6 };
    } else {
            if (family != AF_INET6)
                    buf[cnt++] = (struct address){ .family = AF_INET, .addr = { 127,0,0,1 } };
            if (family != AF_INET)
                    buf[cnt++] = (struct address){ .family = AF_INET6, .addr = { [15] = 1 } };
    }
    return cnt;
}
Run Code Online (Sandbox Code Playgroud)

最后,我们可以看到,当 family == AF_INET6 时,我们将得到硬编码值::1。由于 getent 在 IPv4 之前尝试 IPv6,因此这将是返回值。正如我上面所示,在 getent 中强制解析为 IPv4 将导致上面函数中的硬编码 127.0.0.1 值。

如果您希望更改功能以返回 localhost 的 IPv4 地址,最好的办法是提交/请求修复 getent 以首先搜索 IPv4。

希望这可以帮助!