【轉載】IPv4 也是可以訪問 IPv6 服務的

原文鏈接:https://ms2008.github.io/2018/12/10/ipv6-bindv6only/

原文:https://ms2008.github.io/2018/12/10/ipv6-bindv6only/

起因

對於 Golang 的 net.Listen() 函數,如果你不強行指定 IPv4 或 IPv6 的話,在雙棧系統上默認只會監聽 IPv6 地址。比如,用 Golang 實現一個 HTTP 服務非常簡單:

package main

import (
	"net/http"
)

type helloHandler struct{}

func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, world!"))
}

func main() {
	http.Handle("/", &helloHandler{})
	http.ListenAndServe(":6666", nil)
}

啓動後,通過 netstat 查看可以確定服務只有在 IPv6 上監聽:

 

這時候,如果 curl 的是服務的 IPv4 地址,其實也是可以看到預期輸出的:

$ curl -i -s http://192.168.3.10:6666
HTTP/1.1 200 OK
Date: Sat, 08 Dec 2018 06:33:54 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8

Hello, world!

之所以會有這樣的行爲,是因爲在 linux 上有個內核參數 net.ipv6.bindv6only 默認爲關閉狀態,這樣 IPv6 的 socket 也就可以解析映射到同一個網卡的 IPv4 請求了。這樣的話,如果我們的服務需要同時提供 IPv4 和 IPv6 的訪問能力,只需要監聽一個 IPv6 的 socket 即可。

我這裏並不希望 IPv4 可以訪問 IPv6 的服務,所以我把 net.ipv6.bindv6only 置爲了 1:

$ cat /proc/sys/net/ipv6/bindv6only
1

遺憾的是,開啓了這個參數後,似乎並沒有效果,curl 依然可以使用 IPv4 的地址來訪問。

溯源

怎麼解釋這個問題?既然是看起來內核參數沒有生效,問題多半是發生在 syscall 的調用上,祭出 strace 大殺器:

可以看到在 220 行 Golang 把準備 `listen` 的 socket 選項置爲了 0。顯然這個是 Golang 自身的行爲

通過追蹤 Golang 的源碼,問題最終定位在了這裏 src/net/ipsock_posix.go :

func favoriteAddrFamily(network string, laddr, raddr sockaddr, mode string) (family int, ipv6only bool) {
    switch network[len(network)-1] {
    case '4':
        return syscall.AF_INET, false
    case '6':
        return syscall.AF_INET6, true
    }

    if mode == "listen" && (laddr == nil || laddr.isWildcard()) {
        if supportsIPv4map() || !supportsIPv4() {
            return syscall.AF_INET6, false
        }
        if laddr == nil {
            return syscall.AF_INET, false
        }
        return laddr.family(), false
    }

    if (laddr == nil || laddr.family() == syscall.AF_INET) &&
        (raddr == nil || raddr.family() == syscall.AF_INET) {
        return syscall.AF_INET, false
    }
    return syscall.AF_INET6, false
}

原來 Golang 自己定義了 IPV6_V6ONLY 這個行爲,至於這麼做的原因在官方 Github 也有一些討論:net: Listen is unfriendly to multiple address families, endpoints and subflows

既然是這樣,那如何解決這個問題?

  • 自己定製需要的 net.Listen()
  • listen 完整的 IPv4 和 IPv6 地址

插曲

如果你用 Chrome 訪問 http://localhost:6666 這樣的地址,可能會看到 ERR_UNSAFE_PORT 這樣的錯誤頁面。這個其實是因爲 Chrome 的非安全端口限制。

像這樣的端口,一共有 64 個:

1,    // tcpmux
7,    // echo
9,    // discard
11,   // systat
13,   // daytime
15,   // netstat
17,   // qotd
19,   // chargen
20,   // ftp data
21,   // ftp access
22,   // ssh
23,   // telnet
25,   // smtp
37,   // time
42,   // name
43,   // nicname
53,   // domain
77,   // priv-rjs
79,   // finger
87,   // ttylink
95,   // supdup
101,  // hostriame
102,  // iso-tsap
103,  // gppitnp
104,  // acr-nema
109,  // pop2
110,  // pop3
111,  // sunrpc
113,  // auth
115,  // sftp
117,  // uucp-path
119,  // nntp
123,  // NTP
135,  // loc-srv /epmap
139,  // netbios
143,  // imap2
179,  // BGP
389,  // ldap
465,  // smtp+ssl
512,  // print / exec
513,  // login
514,  // shell
515,  // printer
526,  // tempo
530,  // courier
531,  // chat
532,  // netnews
540,  // uucp
556,  // remotefs
563,  // nntp+ssl
587,  // stmp?
601,  // ??
636,  // ldap+ssl
993,  // ldap+ssl
995,  // pop3+ssl
2049, // nfs
3659, // apple-sasl / PasswordServer
4045, // lockd
6000, // X11
6665, // Alternate IRC [Apple addition]
6666, // Alternate IRC [Apple addition]
6667, // Standard IRC [Apple addition]
6668, // Alternate IRC [Apple addition]
6669, // Alternate IRC [Apple addition]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章