溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

研究網(wǎng)卡地址注冊(cè)時(shí)的一點(diǎn)思考

發(fā)布時(shí)間:2020-08-11 03:01:19 來源:ITPUB博客 閱讀:173 作者:咖啡拿鐵 欄目:互聯(lián)網(wǎng)科技

本文轉(zhuǎn)載自公眾號(hào)kiritomoe,不僅僅學(xué)習(xí)結(jié)果,更多的是學(xué)習(xí)優(yōu)秀的人是如何思考。

我曾經(jīng)寫過一篇和本文標(biāo)題類似的文章《研究優(yōu)雅停機(jī)時(shí)的一點(diǎn)思考》,上文和本文都有一個(gè)共同點(diǎn):網(wǎng)卡地址注冊(cè)和優(yōu)雅停機(jī)都是一個(gè)很小的知識(shí)點(diǎn),但是背后牽扯到的知識(shí)點(diǎn)卻是龐大的體系,我在寫這類文章前基本也和大多數(shù)讀者一樣,處于“知道有這么個(gè)東西,但不了解細(xì)節(jié)”的階段,但一旦深挖,會(huì)感受到其中的奇妙,并有機(jī)會(huì)接觸到很多平時(shí)不太關(guān)注的知識(shí)點(diǎn)。

另外,我還想介紹一個(gè)叫做”元閱讀“的技巧,可能這個(gè)詞是我自己造的,也有人稱之為”超視角閱讀“。其內(nèi)涵指的是,普通讀者從我的文章中學(xué)到的是某個(gè)知識(shí)點(diǎn),而元閱讀者從我的文章中可能會(huì)額外關(guān)注,我是如何掌握某個(gè)知識(shí)點(diǎn)的,在一個(gè)知識(shí)點(diǎn)的學(xué)習(xí)過程中我關(guān)注了哪些知識(shí)點(diǎn)相關(guān)的點(diǎn),又是如何將他們聯(lián)系在一起,最終形成一個(gè)體系的。這篇文章就是一個(gè)典型的例子,我會(huì)對(duì)一些點(diǎn)進(jìn)行發(fā)散,大家可以嘗試著跟我一起來思考”網(wǎng)卡地址注冊(cè)“這個(gè)問題。

1 如何選擇合適的網(wǎng)卡地址

可能相當(dāng)一部分人還不知道我這篇文章到底要講什么,我說個(gè)場景,大家應(yīng)該就明晰了。在分布式服務(wù)調(diào)用過程中以 Dubbo 為例,服務(wù)提供者往往需要將自身的 IP 地址上報(bào)給注冊(cè)中心,供消費(fèi)者去發(fā)現(xiàn)。在大多數(shù)情況下 Dubbo 都可以正常工作,但你如果留意過 Dubbo 的 github issue,其實(shí)有不少人反饋:Dubbo Provider 注冊(cè)了錯(cuò)誤的 IP。如果你能立刻聯(lián)想到:多網(wǎng)卡、內(nèi)外網(wǎng)地址共存、VPN、虛擬網(wǎng)卡等關(guān)鍵詞,那我建議你一定要繼續(xù)將本文看下去,因?yàn)槲乙蚕氲搅诉@些,它們都是本文所要探討的東西!那么“如何選擇合適的網(wǎng)卡地址”呢?Dubbo 現(xiàn)有的邏輯到底算不算完備?是否有改進(jìn)措施?我們不急著回答它,而是帶著這些問題一起來進(jìn)行研究,相信到文末,其中答案,各位看官自有評(píng)說。

2 Dubbo 是怎么做的

Dubbo 獲取網(wǎng)卡地址的邏輯在各個(gè)版本中也是千回百轉(zhuǎn),走過彎路,也做過優(yōu)化,我們用最新的 2.7.2-SNAPSHOT 版本來介紹,在看以下源碼時(shí),大家可以懷著質(zhì)疑的心態(tài)去閱讀,在 dubbo github 的 master 分支可以獲取源碼。獲取 localhost 的邏輯位于 org.apache.dubbo.common.utils.NetUtils#getLocalAddress0() 之中

  1. private static InetAddress getLocalAddress0() {

  2.    InetAddress localAddress = null;

  3.    // 首先嘗試獲取 /etc/hosts 中 hostname 對(duì)應(yīng)的 IP

  4.    localAddress = InetAddress.getLocalHost();

  5.    Optional<InetAddress> addressOp = toValidAddress(localAddress);

  6.    if (addressOp.isPresent()) {

  7.        return addressOp.get();

  8.    }


  9.    // 沒有找到適合注冊(cè)的 IP,則開始輪詢網(wǎng)卡

  10.    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();

  11.    if (null == interfaces) {

  12.        return localAddress;

  13.    }

  14.    while (interfaces.hasMoreElements()) {

  15.        NetworkInterface network = interfaces.nextElement();

  16.        Enumeration<InetAddress> addresses = network.getInetAddresses();

  17.        while (addresses.hasMoreElements()) {

  18.            // 返回第一個(gè)匹配的適合注冊(cè)的 IP

  19.            Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());

  20.            if (addressOp.isPresent()) {

  21.                return addressOp.get();

  22.            }

  23.        }

  24.    }

  25.    return localAddress;

  26. }

Dubbo 這段獲取 localhost 的邏輯大致分成了兩步

  1. 先去 /etc/hosts 文件中找 hostname 對(duì)應(yīng)的 IP 地址,找到則返回;找不到則轉(zhuǎn) 2

  2. 輪詢網(wǎng)卡,尋找合適的 IP 地址,找到則返回;找不到返回 null,再 getLocalAddress0 外側(cè)還有一段邏輯,如果返回 null,則注冊(cè) 127.0.0.1 這個(gè)本地回環(huán)地址

首先強(qiáng)調(diào)下,這段邏輯并沒有太大的問題,先別急著挑刺,讓我們來分析下其中的一些細(xì)節(jié),并進(jìn)行驗(yàn)證。

2.1 嘗試獲取 hostname 映射 IP

Dubbo 首先選取的是 hostname 對(duì)應(yīng)的 IP,在源碼中對(duì)應(yīng)的 InetAddress.getLocalHost();*nix系統(tǒng)實(shí)際部署 Dubbo 應(yīng)用時(shí),可以首先使用 hostname 命令獲取主機(jī)名

xujingfengdeMacBook-Pro:~ xujingfeng$ hostnamexujingfengdeMacBook-Pro.local

緊接著在 /etc/hosts 配置 IP 映射,為了驗(yàn)證 Dubbo 的機(jī)制,我們隨意為 hostname 配置一個(gè) IP 地址

127.0.0.1    localhost1.2.3.4 xujingfengdeMacBook-Pro.local

接著調(diào)用 NetUtils.getLocalAddress0() 進(jìn)行驗(yàn)證,控制臺(tái)打印如下:

xujingfengdeMacBook-Pro.local/1.2.3.4

2.2 判定有效的 IP 地址

在 toValidAddress 邏輯中,Dubbo 存在以下邏輯判定一個(gè) 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();}

依次校驗(yàn)其符合 Ipv6 或者 Ipv4 的 IP 規(guī)范,對(duì)于 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 參數(shù),其默認(rèn)值為 false,鑒于大多數(shù)應(yīng)用并沒有使用 Ipv6 地址作為理想的注冊(cè) IP,這問題不大,緊接著通過 isReachable 判斷網(wǎng)卡的連通性。例如一些網(wǎng)卡可能是 VPN/虛擬網(wǎng)卡的地址,如果沒有配置路由表,往往無法連通,可以將之過濾。

對(duì)于 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;}

對(duì)比 Ipv6 的判斷,這里我們已經(jīng)發(fā)現(xiàn)前后不對(duì)稱的情況了

  • Ipv4 相比 Ipv6 的邏輯多了 Ipv4 格式的正則校驗(yàn)、本地回環(huán)地址校驗(yàn)、ANYHOST 校驗(yàn)

  • Ipv4 相比 Ipv6 的邏輯少了網(wǎng)卡連通性的校驗(yàn)

大家都知道,Ipv4 將 127.0.0.1 定為本地回環(huán)地址, Ipv6 也存在回環(huán)地址:0:0:0:0:0:0:0:1 或者表示為 ::1。改進(jìn)建議也很明顯,我們放到文末統(tǒng)一總結(jié)。

2.3 輪詢網(wǎng)卡

如果上述地址獲取為 null 則進(jìn)入輪詢網(wǎng)卡的邏輯(例如 hosts 未指定 hostname 的映射或者 hostname 配置成了 127.0.0.1 之類的地址便會(huì)導(dǎo)致獲取到空的網(wǎng)卡地址),輪詢網(wǎng)卡對(duì)應(yīng)的源碼是 NetworkInterface.getNetworkInterfaces() ,這里面涉及的知識(shí)點(diǎn)就比較多了,支撐起了我寫這篇文章的素材,Dubbo 的邏輯并不復(fù)雜,進(jìn)行簡單的校驗(yàn),返回第一個(gè)可用的 IP 即可。

性子急的讀者可能忍不住了,多網(wǎng)卡!合適的網(wǎng)卡可能不止一個(gè),Dubbo 怎么應(yīng)對(duì)呢?按道理說,我們也替 Dubbo 說句公道話,客官要不你自己指定下?我們首先對(duì)于多網(wǎng)卡的場景達(dá)成一致看法,咱們才能繼續(xù)把這篇文章完成下去:我們只能盡可能過濾那些“不對(duì)”的網(wǎng)卡。Dubbo 看樣子對(duì)所有網(wǎng)卡是一視同仁了,我們是不是可以嘗試優(yōu)化一下其中的邏輯呢?

許多開源的服務(wù)治理框架在 stackoverflow 或者其 issue 中,注冊(cè)錯(cuò) IP 相關(guān)的問題都十分高頻,大多數(shù)都是輪詢網(wǎng)卡出了問題。既然事情發(fā)展到這兒,勢必需要了解一些網(wǎng)絡(luò)、網(wǎng)卡的知識(shí),我們才能過濾掉那些明顯不適合 RPC 服務(wù)注冊(cè)的 IP 地址了。

3 Ifconfig 介紹

我并沒有想要讓大家對(duì)后續(xù)的內(nèi)容望而卻步,特地選擇了這個(gè)大家最熟悉的 Linux 命令!對(duì)于那些吐槽:“天吶,都 2019 年了,你怎么還在用 net-tools/ifconfig,iproute2/ip 了解一下”的言論,請(qǐng)大家視而不見。無論你使用的是 mac,還是 linux,都可以使用它去 CRUD 你的網(wǎng)卡配置。

3.1 常用指令

啟動(dòng)關(guān)閉指定網(wǎng)卡:

ifconfig eth0 upifconfig eth0 down

ifconfig eth0 up 為啟動(dòng)網(wǎng)卡 eth0, ifconfig eth0 down 為關(guān)閉網(wǎng)卡 eth0。ssh 登陸 linux 服務(wù)器操作的用戶要小心執(zhí)行這個(gè)操作了,千萬不要蠢哭自己。不然你下一步就需要去 google:“禁用 eth0 網(wǎng)卡后如何遠(yuǎn)程連接 Linux 服務(wù)器” 了。

為網(wǎng)卡配置和刪除IPv6地址:

ifconfig eth0 add 33ffe:3240:800:1005::2/64    #為網(wǎng)卡eth0配置IPv6地址ifconfig eth0 del 33ffe:3240:800:1005::2/64    #為網(wǎng)卡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

啟用和關(guān)閉arp協(xié)議:

ifconfig eth0 arp    #開啟網(wǎng)卡eth0 的arp協(xié)議ifconfig eth0 -arp   #關(guān)閉網(wǎng)卡eth0 的arp協(xié)議

設(shè)置最大傳輸單元:

ifconfig eth0 mtu 1500    #設(shè)置能通過的最大數(shù)據(jù)包大小為 1500 bytes

3.2 查看網(wǎng)卡信息

在一臺(tái) ubuntu 上執(zhí)行 ifconfig-a

  1. ubuntu@VM-30-130-ubuntu:~$ ifconfig -a

  2. eth0      Link encap:Ethernet  HWaddr 52:54:00:a9:5f:ae

  3.          inet addr:10.154.30.130  Bcast:10.154.63.255  Mask:255.255.192.0

  4.          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

  5.          RX packets:149673 errors:0 dropped:0 overruns:0 frame:0

  6.          TX packets:152271 errors:0 dropped:0 overruns:0 carrier:0

  7.          collisions:0 txqueuelen:1000

  8.          RX bytes:15205083 (15.2 MB)  TX bytes:21386362 (21.3 MB)


  9. lo        Link encap:Local Loopback

  10.          inet addr:127.0.0.1  Mask:255.0.0.0

  11.          UP LOOPBACK RUNNING  MTU:65536  Metric:1

  12.          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

  13.          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

  14.          collisions:0 txqueuelen:1

  15.          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


  16. docker0   Link encap:Ethernet  HWaddr 02:42:58:45:c1:15

  17.          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0

  18.          UP BROADCAST MULTICAST  MTU:1500  Metric:1

  19.          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

  20.          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

  21.          collisions:0 txqueuelen:0

  22.          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


  23. tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00

  24.          UP POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1

  25.          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

  26.          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

  27.          collisions:0 txqueuelen:100

  28.          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

為了防止黑客對(duì)我的 Linux 發(fā)起攻擊,我還是偷偷對(duì) IP 做了一點(diǎn)“改造”,請(qǐng)不要為難一個(gè)趁著打折+組團(tuán)購買廉價(jià)云服務(wù)器的小伙子。對(duì)于部門網(wǎng)卡的詳細(xì)解讀:

eth0 表示第一塊網(wǎng)卡, 其中 HWaddr 表示網(wǎng)卡的物理地址,可以看到目前這個(gè)網(wǎng)卡的物理地址(MAC 地址)是 02:42:38:52:70:54

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

lo 是表示主機(jī)的回環(huán)地址,這個(gè)一般是用來測試一個(gè)網(wǎng)絡(luò)程序,但又不想讓局域網(wǎng)或外網(wǎng)的用戶能夠查看,只能在此臺(tái)主機(jī)上運(yùn)行和查看所用的網(wǎng)絡(luò)接口。比如把 HTTPD 服務(wù)器的指定到回壞地址,在瀏覽器輸入 127.0.0.1 就能看到你所架 WEB 網(wǎng)站了。但只是你能看得到,局域網(wǎng)的其它主機(jī)或用戶無從知曉。

第一行:連接類型:Ethernet(以太網(wǎng))HWaddr(硬件mac地址)

第二行:網(wǎng)卡的IP地址、子網(wǎng)、掩碼

第三行:UP(代表網(wǎng)卡開啟狀態(tài))RUNNING(代表網(wǎng)卡的網(wǎng)線被接上)MULTICAST(支持組播)MTU:1500(最大傳輸單元):1500字節(jié)(ipconfig 不加 -a 則無法看到 DOWN 的網(wǎng)卡)

第四、五行:接收、發(fā)送數(shù)據(jù)包情況統(tǒng)計(jì)

第七行:接收、發(fā)送數(shù)據(jù)字節(jié)數(shù)統(tǒng)計(jì)信息。

緊接著的兩個(gè)網(wǎng)卡 docker0,tun0 是怎么出來的呢?我在我的 ubuntu 上裝了 docker 和 openvpn。這兩個(gè)東西應(yīng)該是日常干擾我們做服務(wù)注冊(cè)時(shí)的罪魁禍?zhǔn)琢耍?dāng)然,也有可能存在 eth2 這樣的第二塊網(wǎng)卡。ifconfig -a 看到的東西就對(duì)應(yīng)了 JDK 的 api : NetworkInterface.getNetworkInterfaces() 。我們簡單做個(gè)總結(jié),大致有三個(gè)干擾因素

  • 以 docker 網(wǎng)橋?yàn)槭椎奶摂M網(wǎng)卡地址,畢竟這東西這么火,怎么也得單獨(dú)列出來吧?

  • 以 TUN/TAP 為代表的虛擬網(wǎng)卡地址,多為 VPN 場景

  • 以 eth2 為代表的多網(wǎng)卡場景,有錢就可以裝多網(wǎng)卡了!

我們后續(xù)的篇幅將針對(duì)這些場景做分別的介紹,力求讓大家沒吃過豬肉,起碼看下豬怎么跑的。

4 干擾因素一:Docker 網(wǎng)橋

熟悉 docker 的朋友應(yīng)該知道 docker 會(huì)默認(rèn)創(chuàng)建一個(gè) docker0 的網(wǎng)橋,供容器實(shí)例連接。如果嫌默認(rèn)的網(wǎng)橋不夠直觀,我們可以使用 bridge 模式自定義創(chuàng)建一個(gè)新的網(wǎng)橋:

ubuntu@VM-30-130-ubuntu:~$ docker network create kirito-bridgea38696dbbe58aa916894c674052c4aa6ab32266dcf6d8111fb794b8a344aa0d9ubuntu@VM-30-130-ubuntu:~$ ifconfig -abr-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 指令創(chuàng)建網(wǎng)橋之后,自動(dòng)創(chuàng)建了對(duì)應(yīng)的網(wǎng)卡,我只給出了 ifconfig-a 的增量返回部分,可以看出多了一個(gè) br-a38696dbbe58 的網(wǎng)卡。

我有意區(qū)分了“網(wǎng)橋”和“網(wǎng)卡”,可以使用 bridge-utils/brctl 來查看網(wǎng)橋信息:

ubuntu@VM-30-130-ubuntu:~$ sudo brctl showbridge name    bridge id       STP enabled interfacesbr-a38696dbbe58        8000.02426eaafd0c   nodocker0        8000.02425845c215   no

網(wǎng)橋是一個(gè)虛擬設(shè)備,這個(gè)設(shè)備只有 brctl show 能看到,網(wǎng)橋創(chuàng)建之后,會(huì)自動(dòng)創(chuàng)建一個(gè)同名的網(wǎng)卡,并將這個(gè)網(wǎng)卡加入網(wǎng)橋。

5 干擾因素二:TUN/TAP 虛擬網(wǎng)絡(luò)設(shè)備

平時(shí)我們所說的虛擬網(wǎng)卡、虛擬機(jī),大致都跟 TUN/TAP 有關(guān)。我的讀者大多數(shù)是 Java 從業(yè)者,相信我下面的內(nèi)容并沒有太超綱,不要被陌生的名詞唬住。對(duì)于被唬住的讀者,也可以直接跳過 5.1~5.3,直接看 5.4 的實(shí)戰(zhàn)。

5.1 真實(shí)網(wǎng)卡工作原理

研究網(wǎng)卡地址注冊(cè)時(shí)的一點(diǎn)思考

上圖中的 eth0 表示我們主機(jī)已有的真實(shí)的網(wǎng)卡接口 (interface)。

網(wǎng)卡接口 eth0 所代表的真實(shí)網(wǎng)卡通過網(wǎng)線(wire)和外部網(wǎng)絡(luò)相連,該物理網(wǎng)卡收到的數(shù)據(jù)包會(huì)經(jīng)由接口 eth0 傳遞給內(nèi)核的網(wǎng)絡(luò)協(xié)議棧(Network Stack)。然后協(xié)議棧對(duì)這些數(shù)據(jù)包進(jìn)行進(jìn)一步的處理。

對(duì)于一些錯(cuò)誤的數(shù)據(jù)包,協(xié)議??梢赃x擇丟棄;對(duì)于不屬于本機(jī)的數(shù)據(jù)包,協(xié)議??梢赃x擇轉(zhuǎn)發(fā);而對(duì)于確實(shí)是傳遞給本機(jī)的數(shù)據(jù)包,而且該數(shù)據(jù)包確實(shí)被上層的應(yīng)用所需要,協(xié)議棧會(huì)通過 Socket API 告知上層正在等待的應(yīng)用程序。

5.2 TUN 工作原理

研究網(wǎng)卡地址注冊(cè)時(shí)的一點(diǎn)思考

我們知道,普通的網(wǎng)卡是通過網(wǎng)線來收發(fā)數(shù)據(jù)包的話,而 TUN 設(shè)備比較特殊,它通過一個(gè)文件收發(fā)數(shù)據(jù)包。

如上圖所示,tunX 和上面的 eth0 在邏輯上面是等價(jià)的, tunX 也代表了一個(gè)網(wǎng)絡(luò)接口,雖然這個(gè)接口是系統(tǒng)通過軟件所模擬出來的.

網(wǎng)卡接口 tunX 所代表的虛擬網(wǎng)卡通過文件 /dev/tunX 與我們的應(yīng)用程序(App) 相連,應(yīng)用程序每次使用 write 之類的系統(tǒng)調(diào)用將數(shù)據(jù)寫入該文件,這些數(shù)據(jù)會(huì)以網(wǎng)絡(luò)層數(shù)據(jù)包的形式,通過該虛擬網(wǎng)卡,經(jīng)由網(wǎng)絡(luò)接口 tunX 傳遞給網(wǎng)絡(luò)協(xié)議棧,同時(shí)該應(yīng)用程序也可以通過 read 之類的系統(tǒng)調(diào)用,經(jīng)由文件 /dev/tunX 讀取到協(xié)議棧向 tunX 傳遞的所有數(shù)據(jù)包。

此外,協(xié)議??梢韵癫倏v普通網(wǎng)卡一樣來操縱 tunX 所代表的虛擬網(wǎng)卡。比如說,給 tunX 設(shè)定 IP 地址,設(shè)置路由,總之,在協(xié)議??磥?,tunX 所代表的網(wǎng)卡和其他普通的網(wǎng)卡區(qū)別不大,當(dāng)然,硬要說區(qū)別,那還是有的,那就是 tunX 設(shè)備不存在 MAC 地址,這個(gè)很好理解,tunX 只模擬到了網(wǎng)絡(luò)層,要 MAC *地址沒有任何意義。當(dāng)然,如果是 *tapX 的話,在協(xié)議棧的眼中,tapX 和真實(shí)網(wǎng)卡沒有任何區(qū)別。

是不是有些懵了?我是誰,為什么我要在這篇文章里面學(xué)習(xí) TUN!因?yàn)槲覀兂S玫?VPN 基本就是基于 TUN/TAP 搭建的,如果我們使用 TUN 設(shè)備搭建一個(gè)基于 UDPVPN ,那么整個(gè)處理過程可能是這幅樣子:

研究網(wǎng)卡地址注冊(cè)時(shí)的一點(diǎn)思考

5.3 TAP 工作原理

TAP 設(shè)備與 TUN 設(shè)備工作方式完全相同,區(qū)別在于:

  1. TUN 設(shè)備是一個(gè)三層設(shè)備,它只模擬到了 IP 層,即網(wǎng)絡(luò)層 我們可以通過 /dev/tunX 文件收發(fā) IP 層數(shù)據(jù)包,它無法與物理網(wǎng)卡做 bridge,但是可以通過三層交換(如 ip_forward)與物理網(wǎng)卡連通??梢允褂?nbsp;ifconfig之類的命令給該設(shè)備設(shè)定 IP 地址。

  2. TAP 設(shè)備是一個(gè)二層設(shè)備,它比 TUN 更加深入,通過 /dev/tapX 文件可以收發(fā) MAC 層數(shù)據(jù)包,即數(shù)據(jù)鏈路層,擁有 MAC 層功能,可以與物理網(wǎng)卡做 bridge,支持 MAC 層廣播。同樣的,我們也可以通過 ifconfig之類的命令給該設(shè)備設(shè)定 IP 地址,你如果愿意,我們可以給它設(shè)定 MAC 地址。

關(guān)于文章中出現(xiàn)的二層,三層,我這里說明一下,第一層是物理層,第二層是數(shù)據(jù)鏈路層,第三層是網(wǎng)絡(luò)層,第四層是傳輸層。

5.4 openvpn 實(shí)戰(zhàn)

openvpn 是 Linux 上一款開源的 vpn 工具,我們通過它來復(fù)現(xiàn)出影響我們做網(wǎng)卡選擇的場景。

安裝 openvpn

sudo apt-get install openvpn

安裝一個(gè) TUN 設(shè)備:

ubuntu@VM-30-130-ubuntu:~$ sudo openvpn --mktun --dev tun0Mon Apr 29 22:23:31 2019 TUN/TAP device tun0 openedMon Apr 29 22:23:31 2019 Persist state set to: ON

安裝一個(gè) TAP 設(shè)備:

ubuntu@VM-30-130-ubuntu:~$ sudo openvpn --mktun --dev tap0Mon Apr 29 22:24:36 2019 TUN/TAP device tap0 openedMon Apr 29 22:24:36 2019 Persist state set to: ON

執(zhí)行 ifconfig-a 查看網(wǎng)卡,只給出增量的部分:

  1. tap0      Link encap:Ethernet  HWaddr 7a:a2:a8:f1:6b:df

  2.          BROADCAST MULTICAST  MTU:1500  Metric:1

  3.          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

  4.          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

  5.          collisions:0 txqueuelen:100

  6.          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


  7. tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00

  8.          inet addr:10.154.30.131  P-t-P:10.154.30.131  Mask:255.255.255.255

  9.          UP POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1

  10.          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

  11.          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

  12.          collisions:0 txqueuelen:100

  13.          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

這樣就解釋了文章一開始為什么會(huì)有 tun0 這樣的網(wǎng)卡了。這里讀者可能會(huì)有疑惑,使用 ifconfig 不是也可以創(chuàng)建 tap 和 tun 網(wǎng)卡嗎?當(dāng)然啦,openvpn 是一個(gè) vpn 工具,只能創(chuàng)建名為 tunX/tapX 的網(wǎng)卡,其遵守著一定的規(guī)范,ifconfig 可以隨意創(chuàng)建,但沒人認(rèn)那些隨意創(chuàng)建的網(wǎng)卡。

6 干擾因素三:多網(wǎng)卡

研究網(wǎng)卡地址注冊(cè)時(shí)的一點(diǎn)思考

這個(gè)沒有太多好說的,有多張真實(shí)的網(wǎng)卡,從普哥那兒搞到如上的 IP 信息。

7 MAC 下的差異

雖然 ifconfig 等指令是 *nux 通用的,但是其展示信息,網(wǎng)卡相關(guān)的屬性和命名都有較大的差異。例如這是我 MAC 下執(zhí)行 ifconfig-a 的返回:

xujingfengdeMacBook-Pro:dubbo-in-action xujingfeng$ ifconfig -alo0: 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 1280stf0: flags=0<> mtu 1280XHC0: flags=0<> mtu 0XHC20: flags=0<> mtu 0en0: 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: activep2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304    ether 0a:e9:fe:88:a0:76    media: autoselect    status: inactiveawdl0: 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: activeen1: 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: inactiveen2: 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: inactivebridge0: 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: inactiveutun0: 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>

內(nèi)容很多,我挑幾點(diǎn)差異簡述下:


  • 內(nèi)容展示形式不一樣,沒有 Linux 下的接收、發(fā)送數(shù)據(jù)字節(jié)數(shù)等統(tǒng)計(jì)信息



  • 真實(shí)網(wǎng)卡的命名不一樣:eth0 -> en0



  • 虛擬網(wǎng)卡的命名格式不一樣:tun/tap -> utun


對(duì)于這些常見網(wǎng)卡命名的解讀,我摘抄一部分來自 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 改進(jìn)建議

我們進(jìn)行了以上探索,算是對(duì)網(wǎng)卡有了一點(diǎn)了解了。回過頭來看看 Dubbo 的獲取網(wǎng)卡的邏輯,是否可以做出改進(jìn)呢?

Dubbo Action 1:

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

Dubbo Action 2:

NetworkInterface network = interfaces.nextElement();if (network.isLoopback() || network.isVirtual() || !network.isUp()) {    continue;}

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

Dubbo Action 3:

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

對(duì)于真實(shí)多網(wǎng)卡、內(nèi)外網(wǎng) IP 共存的場景,不能僅僅是框架側(cè)在做努力,用戶也需要做一些事,就像愛情一樣,我可以主動(dòng)一點(diǎn),但你也得反饋,才能發(fā)展出故事。

Dubbo User Action 1:

可以配置 /etc/hosts 文件,將 hostname 對(duì)應(yīng)的 IP 顯示配置進(jìn)去。

Dubbo User Action 2:

可以使用啟動(dòng)參數(shù)去顯示指定注冊(cè)的 IP:

-DDUBBO_IP_TO_REGISTRY=1.2.3.4

也可以指定 Dubbo 服務(wù)綁定在哪塊網(wǎng)卡上:

-DDUBBO_IP_TO_BIND=1.2.3.4

9 參考文章

TUN/TAP 設(shè)備淺析

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI