以 sshd 监听的 22 端口为例:
# netstat -tlnp | grep :22tcp ???????0 ?????0 0.0.0.0:22 ?????????????0.0.0.0:* ??????????????LISTEN ?????1444/sshdtcp6 ??????0 ?????0 :::22 ??????????????????:::* ???????????????????LISTEN ?????1444/sshd
可以看到,netstat 显示表示 sshd 既监听在 ipv4 的地址,又监听在 ipv6 的地址。
而再看看 httpd 进程:
1 # netstat -tlnp | grep :802 tcp6 ??????0 ?????0 :::80 ??????????????????:::* ???????????????????LISTEN ?????19837/httpd
却发现只显示了监听在 ipv6 的地址上 ,但是,通过 ipv4 的地址明明是可以访问访问的。
下面来看下怎样解释这个现象。
首先,关闭 ipv6 并且重启 httpd:
1 # sysctl net.ipv6.conf.all.disable_ipv6=12 # systemctl restart httpd
现在,看下 httpd 监听的地址:
1 # netstat -tlnp | grep :802 tcp ???????0 ?????0 0.0.0.0:80 ?????????????0.0.0.0:* ??????????????LISTEN ?????33697/httpd
可以看到,已经只监听到 ipv4 地址了。
那为什么在 ipv6 开启的时候,netstat 只显示了 tcp6 的监听而非像 sshd 那样既显示 tcp 又显示 tcp6 的监听呢?
我们下载 httpd 的源码看一看,在代码 server/listen.c
的 open_listeners() 函数中, 有相关注释:
1 /* If we have the unspecified IPv4 address (0.0.0.0) and2 ?* the unspecified IPv6 address (::) is next, we need to3 ?* swap the order of these in the list. We always try to4 ?* bind to IPv6 first, then IPv4, since an IPv6 socket5 ?* might be able to receive IPv4 packets if V6ONLY is not6 ?* enabled, but never the other way around.7 ?* ... 省略 ...8 ?*/
上面提到,ipv6 实际上是可以处理 ipv4 的请求的当 V6ONLY 没有开启的时候,反之不然; 那么 V6ONLY 是在什么时候开启呢?
继续 follow 代码到 make_sock() 函数,可以发现如下代码:
1 #if APR_HAVE_IPV62 #ifdef AP_ENABLE_V4_MAPPED3 ????int v6only_setting = 0;4 #else5 ????int v6only_setting = 1;6 #endif7 #endif
在这个函数中,可以看到如果监听的地址是 ipv6,那么会去设置 IPV6_V6ONLY 这个 socket 选项, 现在,关键是看 AP_ENABLE_V4_MAPPED 是怎么定义的。
在 configure(注意,如果是直接通过代码数获取的,可能没有这个文件,而只有 configure.ac/in 文件)文件中, 可以找到:
1 # Check whether --enable-v4-mapped was given. 2 if test "${enable_v4_mapped+set}" = set; then : 3 ??enableval=$enable_v4_mapped; 4 ??v4mapped=$enableval 5 ?6 else 7 ?8 ????case $host in 9 ????*freebsd5*|*netbsd*|*openbsd*)10 ????????v4mapped=no11 ????????;;12 ????*)13 ????????v4mapped=yes14 ????????;;15 ????esac16 ????if ap_mpm_is_enabled winnt; then17 ????????????????v4mapped=no18 ????fi19 20 fi21 22 23 if test $v4mapped = "yes" -a $ac_cv_define_APR_HAVE_IPV6 = "yes"; then24 25 $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,如下所示:
1 # netstat -tlnp | grep :802 tcp ???????0 ?????0 0.0.0.0:80 ?????????????0.0.0.0:* ??????????????LISTEN ?????40576/httpd3 tcp6 ??????0 ?????0 :::80 ??????????????????:::* ???????????????????LISTEN ?????40576/httpd
而,如果在 /etc/httpd/conf/httpd.conf
中将 Listen
设置为只监听 ipv6 地址,如下:
1 Listen :::80
那么,将可以看到 netstat 只显示 tcp6 的监听:
1 # systemctl restart httpd2 # netstat -tlnp | grep :803 tcp6 ??????0 ?????0 :::80 ??????????????????:::* ???????????????????LISTEN ?????40980/httpd
并且,你会发现现在不能通过 ipv4 地址访问 httpd 了。
1 # telnet 192.168.1.100 802 Trying 192.168.1.100...3 telnet: Unable to connect to remote host: Connection refused
所以,netstat 只是很真实的显示监听的端口而已,但是需要注意 ipv6 实际上在 Linux 上也支持 ipv4。
转自:http://www.chengweiyang.cn/2017/03/05/why-netstat-not-showup-tcp4-socket/