研究網路卡地址註冊時的一點思考

咖啡拿鐵發表於2019-04-30

我曾經寫過一篇和本文標題類似的文章《研究優雅停機時的一點思考》,上文和本文都有一個共同點:網路卡地址註冊和優雅停機都是一個很小的知識點,但是背後牽扯到的知識點卻是龐大的體系,我在寫這類文章前基本也和大多數讀者一樣,處於“知道有這麼個東西,但不瞭解細節”的階段,但一旦深挖,會感受到其中的奇妙,並有機會接觸到很多平時不太關注的知識點。

另外,我還想介紹一個叫做”元閱讀“的技巧,可能這個詞是我自己造的,也有人稱之為”超視角閱讀“。其內涵指的是,普通讀者從我的文章中學到的是某個知識點,而元閱讀者從我的文章中可能會額外關注,我是如何掌握某個知識點的,在一個知識點的學習過程中我關注了哪些相關的知識點,又是如何將它們聯絡在一起,最終形成一個體系的。這篇文章就是一個典型的例子,我會對一些點進行發散,大家可以嘗試著跟我一起來思考”網路卡地址註冊“這個問題。

1 如何選擇合適的網路卡地址

可能相當一部分人還不知道我這篇文章到底要講什麼,我說個場景,大家應該就明晰了。在分散式服務呼叫過程中,以 Dubbo 為例,服務提供者往往需要將自身的 IP 地址上報給註冊中心,供消費者去發現。在大多數情況下 Dubbo 都可以正常工作,但如果你留意過 Dubbo 的 github issue,其實有不少人反饋:Dubbo Provider 註冊了錯誤的 IP。如果你能立刻聯想到:多網路卡、內外網地址共存、VPN、虛擬網路卡等關鍵詞,那我建議你一定要繼續將本文看下去,因為我也想到了這些,它們都是本文所要探討的東西!那麼“如何選擇合適的網路卡地址”呢,Dubbo 現有的邏輯到底算不算完備?我們不急著回答它,而是帶著這些問題一起進行研究,相信到文末,其中答案,各位看官自有評說。

2 Dubbo 是怎麼做的

Dubbo 獲取網路卡地址的邏輯在各個版本中也是千迴百轉,走過彎路,也做過優化,我們用最新的 2.7.2-SNAPSHOT 版本來介紹,在看以下原始碼時,大家可以懷著質疑的心態去閱讀,在 dubbo github 的 master 分支可以獲取原始碼。獲取 localhost 的邏輯位於 org.apache.dubbo.common.utils.NetUtils#getLocalAddress0() 之中

private static InetAddress getLocalAddress0() {
    InetAddress localAddress = null;
    // 首先嚐試獲取 /etc/hosts 中 hostname 對應的 IP
    localAddress = InetAddress.getLocalHost();
    Optional<InetAddress> addressOp = toValidAddress(localAddress);
    if (addressOp.isPresent()) {
        return addressOp.get();
    }

    // 沒有找到適合註冊的 IP,則開始輪詢網路卡
    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
    if (null == interfaces) {
        return localAddress;
    }
    while (interfaces.hasMoreElements()) {
        NetworkInterface network = interfaces.nextElement();
        Enumeration<InetAddress> addresses = network.getInetAddresses();
        while (addresses.hasMoreElements()) {
            // 返回第一個匹配的適合註冊的 IP
            Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
            if (addressOp.isPresent()) {
                return addressOp.get();
            }
        }
    }
    return localAddress;
}
複製程式碼

Dubbo 這段選取本地地址的邏輯大致分成了兩步

  1. 先去 /etc/hosts 檔案中找 hostname 對應的 IP 地址,找到則返回;找不到則轉 2
  2. 輪詢網路卡,尋找合適的 IP 地址,找到則返回;找不到返回 null,再 getLocalAddress0 外側還有一段邏輯,如果返回 null,則註冊 127.0.0.1 這個本地迴環地址

首先強調下,這段邏輯並沒有太大的問題,先別急著挑刺,讓我們來分析下其中的一些細節,並進行驗證。

2.1 嘗試獲取 hostname 對映 IP

Dubbo 首先選取的是 hostname 對應的 IP,在原始碼中對應的 InetAddress.getLocalHost();*nix 系統實際部署 Dubbo 應用時,可以首先使用 hostname 命令獲取主機名

xujingfengdeMacBook-Pro:~ xujingfeng$ hostname
xujingfengdeMacBook-Pro.local
複製程式碼

緊接著在 /etc/hosts 配置 IP 對映,為了驗證 Dubbo 的機制,我們隨意為 hostname 配置一個 IP 地址

127.0.0.1	localhost
1.2.3.4 xujingfengdeMacBook-Pro.local
複製程式碼

接著呼叫 NetUtils.getLocalAddress0() 進行驗證,控制檯列印如下:

xujingfengdeMacBook-Pro.local/1.2.3.4
複製程式碼

2.2 判定有效的 IP 地址

在 toValidAddress 邏輯中,Dubbo 存在以下邏輯判定一個 IP 地址是否有效

private static Optional<InetAddress> toValidAddress(InetAddress address) {
    if (address instanceof Inet6Address) {
        Inet6Address v6Address = (Inet6Address) address;
        if (isValidV6Address(v6Address)) {
            return Optional.ofNullable(normalizeV6Address(v6Address));
        }
    }
    if (isValidV4Address(address)) {
        return Optional.of(address);
    }
    return Optional.empty();
}
複製程式碼

依次校驗其符合 Ipv6 或者 Ipv4 的 IP 規範,對於 Ipv6 的地址,見如下程式碼:

static boolean isValidV6Address(Inet6Address address) {
    boolean preferIpv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");
    if (!preferIpv6) {
        return false;
    }
    try {
        return address.isReachable(100);
    } catch (IOException e) {
        // ignore
    }
    return false;
}
複製程式碼

首先獲取 java.net.preferIPv6Addresses 引數,其預設值為 false,鑑於大多數應用並沒有使用 Ipv6 地址作為理想的註冊 IP,這問題不大,緊接著通過 isReachable 判斷網路卡的連通性。例如一些網路卡可能是 VPN/虛擬網路卡的地址,如果沒有配置路由表,往往無法連通,可以將之過濾。

對於 Ipv4 的地址,見如下程式碼:

static boolean isValidV4Address(InetAddress address) {
    if (address == null || address.isLoopbackAddress()) {
        return false;
    }
    String name = address.getHostAddress();
    boolean result = (name != null
            && IP_PATTERN.matcher(name).matches()
            && !Constants.ANYHOST_VALUE.equals(name)
            && !Constants.LOCALHOST_VALUE.equals(name));
    return result;
}
複製程式碼

對比 Ipv6 的判斷,這裡我們已經發現前後不對稱的情況了

  • Ipv4 相比 Ipv6 的邏輯多了 Ipv4 格式的正則校驗、本地迴環地址校驗、ANYHOST 校驗
  • Ipv4 相比 Ipv6 的邏輯少了網路卡連通性的校驗

大家都知道,Ipv4 將 127.0.0.1 定為本地迴環地址, Ipv6 也存在迴環地址:0:0:0:0:0:0:0:1 或者表示為 ::1。改進建議也很明顯,我們放到文末統一總結。

2.3 輪詢網路卡

如果上述地址獲取為 null 則進入輪詢網路卡的邏輯(例如 hosts 未指定 hostname 的對映或者 hostname 配置成了 127.0.0.1 之類的地址便會導致獲取到空的網路卡地址),輪詢網路卡對應的原始碼是 NetworkInterface.getNetworkInterfaces() ,這裡面涉及的知識點就比較多了,支撐起了我寫這篇文章的素材,Dubbo 的邏輯並不複雜,進行簡單的校驗,返回第一個可用的 IP 即可。

性子急的讀者可能忍不住了,多網路卡!合適的網路卡可能不止一個,Dubbo 怎麼應對呢?按道理說,我們也替 Dubbo 說句公道話,客官要不你自己指定下?我們首先得對多網路卡的場景達成一致看法,才能繼續把這篇文章完成下去:我們只能儘可能過濾那些“不對”的網路卡。Dubbo 看樣子對所有網路卡是一視同仁了,那麼是不是可以嘗試優化一下其中的邏輯呢?

許多開源的服務治理框架在 stackoverflow 或者其 issue 中,註冊錯 IP 相關的問題都十分高頻,大多數都是輪詢網路卡出了問題。既然事情發展到這兒,勢必需要了解一些網路、網路卡的知識,我們才能過濾掉那些明顯不適合 RPC 服務註冊的 IP 地址了。

3 Ifconfig 介紹

我並沒有想要讓大家對後續的內容望而卻步,特地選擇了這個大家最熟悉的 Linux 命令!對於那些吐槽:“天吶,都 2019 年了,你怎麼還在用 net-tools/ifconfig,iproute2/ip 瞭解一下”的言論,請大家視而不見。無論你使用的是 mac,還是 linux,都可以使用它去 CRUD 你的網路卡配置。

3.1 常用指令

啟動關閉指定網路卡:

ifconfig eth0 up
ifconfig eth0 down
複製程式碼

ifconfig eth0 up 為啟動網路卡 eth0,ifconfig eth0 down 為關閉網路卡 eth0。ssh 登陸 linux 伺服器操作的使用者要小心執行這個操作了,千萬不要蠢哭自己。不然你下一步就需要去 google:“禁用 eth0 網路卡後如何遠端連線 Linux 伺服器” 了。

為網路卡配置和刪除IPv6地址:

ifconfig eth0 add 33ffe:3240:800:1005::2/64    #為網路卡eth0配置IPv6地址
ifconfig eth0 del 33ffe:3240:800:1005::2/64    #為網路卡eth0刪除IPv6地址
複製程式碼

用 ifconfig 修改 MAC 地址:

ifconfig eth0 hw ether 00:AA:BB:CC:dd:EE
複製程式碼

配置 IP 地址:

[root@localhost ~]# ifconfig eth0 192.168.2.10
[root@localhost ~]# ifconfig eth0 192.168.2.10 netmask 255.255.255.0
[root@localhost ~]# ifconfig eth0 192.168.2.10 netmask 255.255.255.0 broadcast 192.168.2.255
複製程式碼

啟用和關閉arp協議:

ifconfig eth0 arp    #開啟網路卡eth0 的arp協議
ifconfig eth0 -arp   #關閉網路卡eth0 的arp協議
複製程式碼

設定最大傳輸單元:

ifconfig eth0 mtu 1500    #設定能通過的最大資料包大小為 1500 bytes
複製程式碼

3.2 檢視網路卡資訊

在一臺 ubuntu 上執行 ifconfig -a

ubuntu@VM-30-130-ubuntu:~$ ifconfig -a
eth0      Link encap:Ethernet  HWaddr 52:54:00:a9:5f:ae
          inet addr:10.154.30.130  Bcast:10.154.63.255  Mask:255.255.192.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:149673 errors:0 dropped:0 overruns:0 frame:0
          TX packets:152271 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:15205083 (15.2 MB)  TX bytes:21386362 (21.3 MB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
          
docker0   Link encap:Ethernet  HWaddr 02:42:58:45:c1:15
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          UP POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
複製程式碼

為了防止黑客對我的 Linux 發起攻擊,我還是偷偷對 IP 做了一點“改造”,請不要為難一個趁著打折+組團購買廉價雲伺服器的小夥子。對於部門網路卡的詳細解讀:

eth0 表示第一塊網路卡, 其中 HWaddr 表示網路卡的實體地址,可以看到目前這個網路卡的實體地址(MAC 地址)是 02:42:38:52:70:54

inet addr 用來表示網路卡的 IP 地址,此網路卡的 IP 地址是 10.154.30.130,廣播地址, Bcast: 172.18.255.255,掩碼地址 Mask:255.255.0.0

lo 是表示主機的迴環地址,這個一般是用來測試一個網路程式,但又不想讓區域網或外網的使用者能夠檢視,只能在此臺主機上執行和檢視所用的網路介面。比如把 HTTPD 伺服器的指定到回壞地址,在瀏覽器輸入 127.0.0.1 就能看到你所架 WEB 網站了。但只是你能看得到,區域網的其它主機或使用者無從知曉。

第一行:連線型別:Ethernet(乙太網)HWaddr(硬體mac地址)

第二行:網路卡的IP地址、子網、掩碼

第三行:UP(代表網路卡開啟狀態)RUNNING(代表網路卡的網線被接上)MULTICAST(支援組播)MTU:1500(最大傳輸單元):1500位元組(ipconfig 不加 -a 則無法看到 DOWN 的網路卡)

第四、五行:接收、傳送資料包情況統計

第七行:接收、傳送資料位元組數統計資訊。

緊接著的兩個網路卡 docker0,tun0 是怎麼出來的呢?我在我的 ubuntu 上裝了 docker 和 openvpn。這兩個東西應該是日常干擾我們做服務註冊時的罪魁禍首了,當然,也有可能存在 eth1 這樣的第二塊網路卡。ifconfig -a 看到的東西就對應了 JDK 的 api :NetworkInterface.getNetworkInterfaces() 。我們簡單做個總結,大致有三個干擾因素

  • 以 docker 網橋為首的虛擬網路卡地址,畢竟這東西這麼火,怎麼也得單獨列出來吧?
  • 以 TUN/TAP 為代表的虛擬網路卡地址,多為 VPN 場景
  • 以 eth1 為代表的多網路卡場景,有錢就可以裝多網路卡了!

我們後續的篇幅將針對這些場景做分別的介紹,力求讓大家沒吃過豬肉,起碼看下豬怎麼跑的。

4 干擾因素一:Docker 網橋

熟悉 docker 的朋友應該知道 docker 會預設建立一個 docker0 的網橋,供容器例項連線。如果嫌預設的網橋不夠直觀,我們可以使用 bridge 模式自定義建立一個新的網橋:

ubuntu@VM-30-130-ubuntu:~$ docker network create kirito-bridge
a38696dbbe58aa916894c674052c4aa6ab32266dcf6d8111fb794b8a344aa0d9
ubuntu@VM-30-130-ubuntu:~$ ifconfig -a
br-a38696dbbe58 Link encap:Ethernet  HWaddr 02:42:6e:aa:fd:0c
          inet addr:172.19.0.1  Bcast:172.19.255.255  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
複製程式碼

使用 docker network 指令建立網橋之後,自動建立了對應的網路卡,我只給出了 ifconfig -a 的增量返回部分,可以看出多了一個 br-a38696dbbe58 的網路卡。

我有意區分了“網橋”和“網路卡”,可以使用 bridge-utils/brctl 來檢視網橋資訊:

ubuntu@VM-30-130-ubuntu:~$ sudo brctl show
bridge name	bridge id		STP enabled	interfaces
br-a38696dbbe58		8000.02426eaafd0c	no
docker0		8000.02425845c215	no
複製程式碼

網橋是一個虛擬裝置,這個裝置只有 brctl show 能看到,網橋建立之後,會自動建立一個同名的網路卡,並將這個網路卡加入網橋。

5 干擾因素二:TUN/TAP 虛擬網路裝置

平時我們所說的虛擬網路卡、虛擬機器,大致都跟 TUN/TAP 有關。我的讀者大多數是 Java 從業者,相信我下面的內容並沒有太超綱,不要被陌生的名詞唬住。對於被唬住的讀者,也可以直接跳過 5.1~5.3,直接看 5.4 的實戰。

5.1 真實網路卡工作原理

1918847-496d0e96c237f25a

上圖中的 eth0 表示我們主機已有的真實的網路卡介面 (interface)。

網路卡介面 eth0 所代表的真實網路卡通過網線(wire)和外部網路相連,該物理網路卡收到的資料包會經由介面 eth0 傳遞給核心的網路協議棧(Network Stack)。然後協議棧對這些資料包進行進一步的處理。

對於一些錯誤的資料包,協議棧可以選擇丟棄;對於不屬於本機的資料包,協議棧可以選擇轉發;而對於確實是傳遞給本機的資料包,而且該資料包確實被上層的應用所需要,協議棧會通過 Socket API 告知上層正在等待的應用程式。

5.2 TUN 工作原理

1918847-85ea08bc89d9427e

我們知道,普通的網路卡是通過網線來收發資料包的話,而 TUN 裝置比較特殊,它通過一個檔案收發資料包。

如上圖所示,tunX 和上面的 eth0 在邏輯上面是等價的, tunX 也代表了一個網路介面,雖然這個介面是系統通過軟體所模擬出來的.

網路卡介面 tunX 所代表的虛擬網路卡通過檔案 /dev/tunX 與我們的應用程式(App) 相連,應用程式每次使用 write 之類的系統呼叫將資料寫入該檔案,這些資料會以網路層資料包的形式,通過該虛擬網路卡,經由網路介面 tunX 傳遞給網路協議棧,同時該應用程式也可以通過 read 之類的系統呼叫,經由檔案 /dev/tunX 讀取到協議棧向 tunX 傳遞的所有資料包。

此外,協議棧可以像操縱普通網路卡一樣來操縱 tunX 所代表的虛擬網路卡。比如說,給 tunX 設定 IP 地址,設定路由,總之,在協議棧看來,tunX 所代表的網路卡和其他普通的網路卡區別不大,當然,硬要說區別,那還是有的,那就是 tunX 裝置不存在 MAC 地址,這個很好理解,tunX 只模擬到了網路層,要 **MAC **地址沒有任何意義。當然,如果是 tapX 的話,在協議棧的眼中,tapX 和真實網路卡沒有任何區別。

是不是有些懵了?我是誰,為什麼我要在這篇文章裡面學習 TUN!因為我們常用的 VPN 基本就是基於 TUN/TAP 搭建的,如果我們使用 TUN 裝置搭建一個基於 UDPVPN ,那麼整個處理過程可能是這幅樣子:

1918847-ac4155ec7e9489b2

5.3 TAP 工作原理

TAP 裝置與 TUN 裝置工作方式完全相同,區別在於:

  1. TUN 裝置是一個三層裝置,它只模擬到了 IP 層,即網路層 我們可以通過 /dev/tunX 檔案收發 IP 層資料包,它無法與物理網路卡做 bridge,但是可以通過三層交換(如 ip_forward)與物理網路卡連通。可以使用ifconfig之類的命令給該裝置設定 IP 地址。
  2. TAP 裝置是一個二層裝置,它比 TUN 更加深入,通過 /dev/tapX 檔案可以收發 MAC 層資料包,即資料鏈路層,擁有 MAC 層功能,可以與物理網路卡做 bridge,支援 MAC 層廣播。同樣的,我們也可以通過ifconfig之類的命令給該裝置設定 IP 地址,你如果願意,我們可以給它設定 MAC 地址。

關於文章中出現的二層,三層,我這裡說明一下,第一層是物理層,第二層是資料鏈路層,第三層是網路層,第四層是傳輸層。

5.4 openvpn 實戰

openvpn 是 Linux 上一款開源的 vpn 工具,我們通過它來複現出影響我們做網路卡選擇的場景。

安裝 openvpn

sudo apt-get install openvpn
複製程式碼

安裝一個 TUN 裝置:

ubuntu@VM-30-130-ubuntu:~$ sudo openvpn --mktun --dev tun0
Mon Apr 29 22:23:31 2019 TUN/TAP device tun0 opened
Mon Apr 29 22:23:31 2019 Persist state set to: ON
複製程式碼

安裝一個 TAP 裝置:

ubuntu@VM-30-130-ubuntu:~$ sudo openvpn --mktun --dev tap0
Mon Apr 29 22:24:36 2019 TUN/TAP device tap0 opened
Mon Apr 29 22:24:36 2019 Persist state set to: ON
複製程式碼

執行 ifconfig -a 檢視網路卡,只給出增量的部分:

tap0      Link encap:Ethernet  HWaddr 7a:a2:a8:f1:6b:df
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          inet addr:10.154.30.131  P-t-P:10.154.30.131  Mask:255.255.255.255
          UP POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
複製程式碼

這樣就解釋了文章一開始為什麼會有 tun0 這樣的網路卡了。這裡讀者可能會有疑惑,使用 ifconfig 不是也可以建立 tap 和 tun 網路卡嗎?當然啦,openvpn 是一個 vpn 工具,只能建立名為 tunX/tapX 的網路卡,其遵守著一定的規範,ifconfig 可以隨意建立,但沒人認那些隨意建立的網路卡。

6 干擾因素三:多網路卡

image-20190429223515625

這個沒有太多好說的,有多張真實的網路卡,從普哥那兒搞到如上的 IP 資訊。

7 MAC 下的差異

雖然 ifconfig 等指令是 *nux 通用的,但是其展示資訊,網路卡相關的屬性和命名都有較大的差異。例如這是我 MAC 下執行 ifconfig -a 的返回:

xujingfengdeMacBook-Pro:dubbo-in-action xujingfeng$ ifconfig -a
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
	inet 127.0.0.1 netmask 0xff000000
	inet6 ::1 prefixlen 128
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
	nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
XHC0: flags=0<> mtu 0
XHC20: flags=0<> mtu 0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	ether 88:e9:fe:88:a0:76
	inet6 fe80::1cab:f689:60d1:bacb%en0 prefixlen 64 secured scopeid 0x6
	inet 30.130.11.242 netmask 0xffffff80 broadcast 30.130.11.255
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active
p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304
	ether 0a:e9:fe:88:a0:76
	media: autoselect
	status: inactive
awdl0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1484
	ether 66:d2:8c:8c:dd:85
	inet6 fe80::64d2:8cff:fe8c:dd85%awdl0 prefixlen 64 scopeid 0x8
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active
en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
	options=60<TSO4,TSO6>
	ether aa:00:d0:13:0e:01
	media: autoselect <full-duplex>
	status: inactive
en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
	options=60<TSO4,TSO6>
	ether aa:00:d0:13:0e:00
	media: autoselect <full-duplex>
	status: inactive
bridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	options=63<RXCSUM,TXCSUM,TSO4,TSO6>
	ether aa:00:d0:13:0e:01
	Configuration:
		id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
		maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
		root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
		ipfilter disabled flags 0x2
	member: en1 flags=3<LEARNING,DISCOVER>
	        ifmaxaddr 0 port 9 priority 0 path cost 0
	member: en2 flags=3<LEARNING,DISCOVER>
	        ifmaxaddr 0 port 10 priority 0 path cost 0
	nd6 options=201<PERFORMNUD,DAD>
	media: <unknown type>
	status: inactive
utun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000
	inet6 fe80::3fe0:3e8b:384:9968%utun0 prefixlen 64 scopeid 0xc
	nd6 options=201<PERFORMNUD,DAD>
utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1380
	inet6 fe80::7894:3abc:5abd:457d%utun1 prefixlen 64 scopeid 0xd
	nd6 options=201<PERFORMNUD,DAD>  
複製程式碼

內容很多,我挑幾點差異簡述下:

  • 內容展示形式不一樣,沒有 Linux 下的接收、傳送資料位元組數等統計資訊

  • 真實網路卡的命名不一樣:eth0 -> en0

  • 虛擬網路卡的命名格式不一樣:tun/tap -> utun

對於這些常見網路卡命名的解讀,我摘抄一部分來自 stackoverflow 的回答:

In arbitrary order of my familarity / widespread relevance:

lo0 is loopback.

en0 at one point "ethernet", now is WiFi (and I have no idea what extra en1 or en2 are used for).

fw0 is the FireWire network interface.

stf0 is an IPv6 to IPv4 tunnel interface to support the transition from IPv4 to the IPv6 standard.

gif0 is a more generic tunneling interface [46]-to-[46].

awdl0 is Apple Wireless Direct Link

p2p0 is related to AWDL features. Either as an old version, or virtual interface with different semantics than awdl.

the "Network" panel in System Preferences to see what network devices "exist" or "can exist" with current configuration.

many VPNs will add additional devices, often "utun#" or "utap#" following TUN/TAP (L3/L2)virtual networking devices.

use netstat -nr to see how traffic is currently routed via network devices according to destination.

interface naming conventions started in BSD were retained in OS X / macOS, and now there also additions.

8 Dubbo 改進建議

我們進行了以上探索,算是對網路卡有一點了解了。回過頭來看看 Dubbo 獲取網路卡的邏輯,是否可以做出改進呢?

Dubbo Action 1:

保持 Ipv4 和 Ipv6 的一致性校驗。為 Ipv4 增加連通性校驗;為 Ipv6 增加 LoopBack 和 ANYHOST 等校驗。

Dubbo Action 2:

NetworkInterface network = interfaces.nextElement();
if (network.isLoopback() || network.isVirtual() || !network.isUp()) {
    continue;
}
複製程式碼

JDK 提供了以上的 API,我們可以利用起來,過濾一部分一定不正確的網路卡。

Dubbo Action 3:

我們本文花了較多的篇幅介紹了 docker 和 TUN/TAP 兩種場景導致的虛擬網路卡的問題,算是較為常見的一個影響因素,雖然他們的命名具有固定性,如 docker0、tunX、tapX,但我覺得通過網路卡名稱的判斷方式去過濾註冊 IP 有一些 hack,所以不建議 dubbo contributor 提出相應的 pr 去增加這些 hack 判斷,儘管可能會對判斷有所幫助。

對於真實多網路卡、內外網 IP 共存的場景,不能僅僅是框架側在做努力,使用者也需要做一些事,就像愛情一樣,我可以主動一點,但你也得反饋,才能發展出故事。

Dubbo User Action 1:

可以配置 /etc/hosts 檔案,將 hostname 對應的 IP 顯示配置進去。

Dubbo User Action 2:

可以使用啟動引數去顯示指定註冊的 IP:

-DDUBBO_IP_TO_REGISTRY=1.2.3.4
複製程式碼

也可以指定 Dubbo 服務繫結在哪塊網路卡上:

-DDUBBO_IP_TO_BIND=1.2.3.4
複製程式碼

9 參考文章

TUN/TAP 裝置淺析

what-are-en0-en1-p2p-and-so-on-that-are-displayed-after-executing-ifconfig

歡迎關注我的微信公眾號:「Kirito的技術分享」,關於文章的任何疑問都會得到回覆,帶來更多 Java 相關的技術分享。

關注微信公眾號

相關文章