最近偶尔发现一个比较奇怪的现象,netstat 查看监听的服务端口时,却只显示了 tcp6 的监控, 可是服务明明是能够经过 tcp4 的 ipv4 地址访问的,那为何没有显示 tcp4 的监听呢?php
以 sshd 监听的 22 端口为例:html
# netstat -tlnp | grep :22 tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1444/sshd tcp6 0 0 :::22 :::* LISTEN 1444/sshd
能够看到,netstat 显示表示 sshd 既监听在 ipv4 的地址,又监听在 ipv6 的地址。前端
而再看看 httpd 进程:linux
# netstat -tlnp | grep :80 tcp6 0 0 :::80 :::* LISTEN 19837/httpd
却发现只显示了监听在 ipv6 的地址上 ,可是,经过 ipv4 的地址明明是能够访问访问的。docker
下面来看下怎样解释这个现象。app
首先,关闭 ipv6 而且重启 httpd:ssh
# sysctl net.ipv6.conf.all.disable_ipv6=1 # systemctl restart httpd
如今,看下 httpd 监听的地址:socket
# netstat -tlnp | grep :80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 33697/httpd
能够看到,已经只监听到 ipv4 地址了。tcp
那为何在 ipv6 开启的时候,netstat 只显示了 tcp6 的监听而非像 sshd 那样既显示 tcp 又显示 tcp6 的监听呢?函数
咱们下载 httpd 的源码看一看,在代码 server/listen.c
的 open_listeners() 函数中, 有相关注释:
/* If we have the unspecified IPv4 address (0.0.0.0) and * the unspecified IPv6 address (::) is next, we need to * swap the order of these in the list. We always try to * bind to IPv6 first, then IPv4, since an IPv6 socket * might be able to receive IPv4 packets if V6ONLY is not * enabled, but never the other way around. * ... 省略 ... */
上面提到,ipv6 其实是能够处理 ipv4 的请求的当 V6ONLY 没有开启的时候,反之否则; 那么 V6ONLY 是在何时开启呢?
继续 follow 代码到 make_sock() 函数,能够发现以下代码:
#if APR_HAVE_IPV6 #ifdef AP_ENABLE_V4_MAPPED int v6only_setting = 0; #else int v6only_setting = 1; #endif #endif
在这个函数中,能够看到若是监听的地址是 ipv6,那么会去设置 IPV6_V6ONLY 这个 socket 选项, 如今,关键是看 AP_ENABLE_V4_MAPPED 是怎么定义的。
在 configure(注意,若是是直接经过代码数获取的,可能没有这个文件,而只有 configure.ac/in 文件)文件中, 能够找到:
# Check whether --enable-v4-mapped was given. if test "${enable_v4_mapped+set}" = set; then : enableval=$enable_v4_mapped; v4mapped=$enableval else case $host in *freebsd5*|*netbsd*|*openbsd*) v4mapped=no ;; *) v4mapped=yes ;; esac if ap_mpm_is_enabled winnt; then v4mapped=no fi fi if test $v4mapped = "yes" -a $ac_cv_define_APR_HAVE_IPV6 = "yes"; then $as_echo "#define AP_ENABLE_V4_MAPPED 1" >>confdefs.h
因此,在 Linux 中,默认状况下,AP_ENABLE_V4_MAPPED 是 1,那么 httpd 就会直接监听 ipv6, 由于此时 ipv6 的 socket 可以处理 ipv4 的请求;另外,bind() 系统调用会对用户空间的进程透明处理 ipv6 没有开启的状况,此时会监听到 ipv4。
而若是咱们在编译 httpd 的时候使用 --disable-v4-mapped
参数禁止 ipv4 mapped,那么默认状况下, httpd 会分别监听在 ipv4 和 ipv6,而非只监听 ipv6,以下所示:
# netstat -tlnp | grep :80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 40576/httpd tcp6 0 0 :::80 :::* LISTEN 40576/httpd
而,若是在 /etc/httpd/conf/httpd.conf
中将 Listen
设置为只监听 ipv6 地址,以下:
Listen :::80
那么,将能够看到 netstat 只显示 tcp6 的监听:
# systemctl restart httpd # netstat -tlnp | grep :80 tcp6 0 0 :::80 :::* LISTEN 40980/httpd
而且,你会发现如今不能经过 ipv4 地址访问 httpd 了。
# telnet 192.168.1.100 80 Trying 192.168.1.100... telnet: Unable to connect to remote host: Connection refused
因此,netstat 只是很真实的显示监听的端口而已,可是须要注意 ipv6 实际上在 Linux 上也支持 ipv4。