背景
netstat 查看监听的服务端口时,却只显示了 tcp6 的监控, 但是服务明明是可以通过 tcp4 的 ipv4 地址访问的,那为什么没有显示 tcp4 的监听呢?
- 范例一:sshd
# netstat -lnp |grep -i sshd
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1305/sshd
tcp6 0 0 :::22 :::* LISTEN 1305/sshd
- 范例二:nginx
yum install -y nginx
nginx
# netstat -lnp | grep :80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5082/nginx: master
tcp6 0 0 :::80 :::* LISTEN 5082/nginx: master
- 范例三:httpd
yum install -y httpd
httpd
# netstat -lnp |grep -i httpd
tcp6 0 0 :::80 :::* LISTEN 45950/httpd
关闭 ipv6 并且重启 httpd
# sysctl net.ipv6.conf.all.disable_ipv6=1
# systemctl restart httpd
看下 httpd 监听的地址:
netstat -tlnp | grep :80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 33697/httpd
现在已经只监听到 ipv4 地址了。
源码查看
/* 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 实际上是在当 V6ONLY 没有开启的时候,可以处理 ipv4 的请求的,反之不然;
V6ONLY 是在什么时候开启呢?
#if APR_HAVE_IPV6
#ifdef AP_ENABLE_V4_MAPPED
int v6only_setting = 0;
#else
int v6only_setting = 1;
#endif
#endif
现在,关键是看 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,那么 v6only_setting = 0, 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 了。
系统级别:内核参数bindv6only
通过查看RFC2553获得了一些信息:
RFC2553描述了IPv4映射地址和IPv6通配绑定套字的特殊行为。规格允许:
通过AF_INET6通配绑定套接字接受IPv4连接。
使用特殊形式的地址 (如 ::ffff:10.1.1.1 ) 通过AF_INET6套接字传输IPv4数据包。
通过RFC2553规定的规则,linux默认(默认bindv6only=0)所有来自IPv4地址的访问转换为IPv6地址的格式从而处理来自于IPv4的连接。
当bindv6only这个内核参数设置为0时,对所有来自于ipv4的请求都绑定到ipv6地址。简单说就是端口可以接收ipv4的包,也可以接收ipv6的包。当bindv6only这个内核参数设置为1时,对于来自ipv4的请求就打开多个端口进行监听和处理。Ipv4与ipv6所监听的端口是分开的。
echo 1 > /proc/sys/net/ipv6/bindv6only
- 当bindv6only这个内核参数设置为0时
对所有来自于ipv4的请求都绑定到ipv6地址。简单说就是端口可以接收ipv4的包,也可以接收ipv6的包。 - 当bindv6only这个内核参数设置为1时
对于来自ipv4的请求就打开多个端口进行监听和处理。Ipv4与ipv6所监听的端口是分开的。
进程级别:setsockopt
opt = 1;
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof(int))
设置socket参数项IPV6_V6ONLY,建立的socket监听就只会监听 ipv6地址的端口;