為什麽 netstat 對某些服務只顯示了 tcp6 監聽端口
最近偶爾發現一個比較奇怪的現象,netstat 查看監聽的服務端口時,卻只顯示了 tcp6 的監控, 但是服務明明是可以通過 tcp4 的 ipv4 地址訪問的,那為什麽沒有顯示 tcp4 的監聽呢?
以 sshd 監聽的 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 既監聽在 ipv4 的地址,又監聽在 ipv6 的地址。
而再看看 httpd 進程:
1 # netstat -tlnp | grep :80 2 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 :80 2 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) and 2 * the unspecified IPv6 address (::) is next, we need to 3 * swap the order of these in the list. We always try to 4 * bind to IPv6 first, then IPv4, since an IPv6 socket 5 * might be able to receive IPv4 packets if V6ONLY is not 6 * enabled, but never the other way around. 7 * ... 省略 ... 8 */
上面提到,ipv6 實際上是可以處理 ipv4 的請求的當 V6ONLY 沒有開啟的時候,反之不然; 那麽 V6ONLY 是在什麽時候開啟呢?
繼續 follow 代碼到 make_sock() 函數,可以發現如下代碼:
1 #if APR_HAVE_IPV6 2 #ifdef AP_ENABLE_V4_MAPPED 3 int v6only_setting = 0; 4 #else 5 int v6only_setting = 1; 6 #endif 7 #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=no 11 ;; 12 *) 13 v4mapped=yes 14 ;; 15 esac 16 if ap_mpm_is_enabled winnt; then 17 v4mapped=no 18 fi 19 20 fi 21 22 23 if test $v4mapped = "yes" -a $ac_cv_define_APR_HAVE_IPV6 = "yes"; then 24 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 :80 2 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 40576/httpd 3 tcp6 0 0 :::80 :::* LISTEN 40576/httpd
而,如果在 /etc/httpd/conf/httpd.conf
中將 Listen
設置為只監聽 ipv6 地址,如下:
1 Listen :::80
那麽,將可以看到 netstat 只顯示 tcp6 的監聽:
1 # systemctl restart httpd 2 # netstat -tlnp | grep :80 3 tcp6 0 0 :::80 :::* LISTEN 40980/httpd
並且,你會發現現在不能通過 ipv4 地址訪問 httpd 了。
1 # telnet 192.168.1.100 80 2 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/
為什麽 netstat 對某些服務只顯示了 tcp6 監聽端口