最近偶爾發(fā)現(xiàn)一個比較奇怪的現(xiàn)象,netstat 查看監(jiān)聽的服務(wù)端口時泉坐,卻只顯示了 tcp6 的監(jiān)控刚夺, 但是服務(wù)明明是可以通過 tcp4 的 ipv4 地址訪問的,那為什么沒有顯示 tcp4 的監(jiān)聽呢?
以 sshd 監(jiān)聽的 22 端口為例:
# 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 既監(jiān)聽在 ipv4 的地址,又監(jiān)聽在 ipv6 的地址啥繁。
而再看看 httpd 進(jìn)程:
# netstat -tlnp | grep :80
tcp6 0 0 :::80 :::* LISTEN 19837/httpd
卻發(fā)現(xiàn)只顯示了監(jiān)聽在 ipv6 的地址上 ,但是青抛,通過 ipv4 的地址明明是可以訪問訪問的。
下面來看下怎樣解釋這個現(xiàn)象酬核。
首先蜜另,關(guān)閉 ipv6 并且重啟 httpd:
# sysctl net.ipv6.conf.all.disable_ipv6=1
# systemctl restart httpd
現(xiàn)在适室,看下 httpd 監(jiān)聽的地址:
# netstat -tlnp | grep :80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 33697/httpd
可以看到,已經(jīng)只監(jiān)聽到 ipv4 地址了举瑰。
那為什么在 ipv6 開啟的時候捣辆,netstat 只顯示了 tcp6 的監(jiān)聽而非像 sshd 那樣既顯示 tcp 又顯示 tcp6 的監(jiān)聽呢?
我們下載 httpd 的源碼看一看此迅,在代碼 server/listen.c 的 open_listeners() 函數(shù)中汽畴, 有相關(guān)注釋:
/* 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 的請求的當(dāng) V6ONLY 沒有開啟的時候耸序,反之不然忍些; 那么 V6ONLY 是在什么時候開啟呢?
繼續(xù) follow 代碼到 make_sock() 函數(shù)坎怪,可以發(fā)現(xiàn)如下代碼:
#if APR_HAVE_IPV6
#ifdef AP_ENABLE_V4_MAPPED
int v6only_setting = 0;
#else
int v6only_setting = 1;
#endif
#endif
在這個函數(shù)中罢坝,可以看到如果監(jiān)聽的地址是 ipv6,那么會去設(shè)置 IPV6_V6ONLY 這個 socket 選項搅窿, 現(xiàn)在嘁酿,關(guān)鍵是看 AP_ENABLE_V4_MAPPED 是怎么定義的。
在 configure(注意男应,如果是直接通過代碼數(shù)獲取的闹司,可能沒有這個文件,而只有 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 中,默認(rèn)情況下薪铜,AP_ENABLE_V4_MAPPED 是 1众弓,那么 httpd 就會直接監(jiān)聽 ipv6, 因為此時 ipv6 的 socket 能夠處理 ipv4 的請求隔箍;另外谓娃,bind() 系統(tǒng)調(diào)用會對用戶空間的進(jìn)程透明處理 ipv6 沒有開啟的情況,此時會監(jiān)聽到 ipv4蜒滩。
而如果我們在編譯 httpd 的時候使用 --disable-v4-mapped 參數(shù)禁止 ipv4 mapped滨达,那么默認(rèn)情況下, httpd 會分別監(jiān)聽在 ipv4 和 ipv6俯艰,而非只監(jiān)聽 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 設(shè)置為只監(jiān)聽 ipv6 地址竹握,如下:
Listen :::80
那么画株,將可以看到 netstat 只顯示 tcp6 的監(jiān)聽:
# systemctl restart httpd
# netstat -tlnp | grep :80
tcp6 0 0 :::80 :::* LISTEN 40980/httpd
并且,你會發(fā)現(xiàn)現(xiàn)在不能通過 ipv4 地址訪問 httpd 了。
# telnet 192.168.1.100 80
Trying 192.168.1.100...
telnet: Unable to connect to remote host: Connection refused
所以谓传,netstat 只是很真實的顯示監(jiān)聽的端口而已蜈项,但是需要注意 ipv6 實際上在 Linux 上也支持 ipv4。