This is an automated email from the ASF dual-hosted git repository. kirito pushed a commit to branch asf-site in repository https://gitbox.apache.org/repos/asf/incubator-dubbo-website.git
The following commit(s) were added to refs/heads/asf-site by this push: new 574428a new acticle: study dubbo network interfaces seletion (#363) 574428a is described below commit 574428a9b9f4aa23bfc214db60f7739e89a35f9b Author: xujingfeng <xujingfen...@gmail.com> AuthorDate: Thu May 23 17:50:24 2019 +0800 new acticle: study dubbo network interfaces seletion (#363) * new acticle: study dubbo network interfaces seletion * fix some type error --- blog/zh-cn/dubbo-network-interfaces.md | 526 +++++++++++++++++++++++++++++++++ site_config/blog.js | 7 + 2 files changed, 533 insertions(+) diff --git a/blog/zh-cn/dubbo-network-interfaces.md b/blog/zh-cn/dubbo-network-interfaces.md new file mode 100644 index 0000000..16dc1f3 --- /dev/null +++ b/blog/zh-cn/dubbo-network-interfaces.md @@ -0,0 +1,526 @@ +--- +title: 研究 Dubbo 网卡地址注册时的一点思考 +keywords: 网卡地址注册 +description: 研究 Dubbo 网卡地址注册时的一点思考 +--- + +# 研究 Dubbo 网卡地址注册时的一点思考 + +## 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()` 之中 + +```java +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` 命令获取主机名 + +```shell +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 地址是否有效 + +```java +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 的地址,见如下代码: + +```java +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 的地址,见如下代码: + +```java +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` + +```shell +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字节(ifconfig 不加 -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 模式自定义创建一个新的网桥: + +```shell +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 来查看网桥信息: + +```shell +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](http://kirito.iocoder.cn/1918847-496d0e96c237f25a.png) + +上图中的 **eth0** 表示我们主机已有的真实的网卡接口 (**interface**)。 + +网卡接口 **eth0** 所代表的真实网卡通过网线(**wire**)和外部网络相连,该物理网卡收到的数据包会经由接口 **eth0** 传递给内核的网络协议栈(**Network Stack**)。然后协议栈对这些数据包进行进一步的处理。 + +对于一些错误的数据包,协议栈可以选择丢弃;对于不属于本机的数据包,协议栈可以选择转发;而对于确实是传递给本机的数据包,而且该数据包确实被上层的应用所需要,协议栈会通过 **Socket API** 告知上层正在等待的应用程序。 + +### 5.2 TUN 工作原理 + +![1918847-85ea08bc89d9427e](http://kirito.iocoder.cn/1918847-85ea08bc89d9427e.png) + +我们知道,普通的网卡是通过网线来收发数据包的话,而 **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** 设备搭建一个基于 **UDP** 的 **VPN** ,那么整个处理过程可能是这幅样子: + +![1918847-ac4155ec7e9489b2](http://kirito.iocoder.cn/1918847-ac4155ec7e9489b2.png) + +### 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 + +```shell +sudo apt-get install openvpn +``` + +安装一个 TUN 设备: + +```shell +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 设备: + +```shell +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` 查看网卡,只给出增量的部分: + +```shell +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](http://kirito.iocoder.cn/image-20190429223515625.png) + +这个没有太多好说的,有多张真实的网卡,从普哥那儿搞到如上的 IP 信息。 + +## 7 MAC 下的差异 + +虽然 ifconfig 等指令是 `*nux` 通用的,但是其展示信息,网卡相关的属性和命名都有较大的差异。例如这是我 MAC 下执行 `ifconfig -a` 的返回: + +```shell +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](https://www.freebsd.org/cgi/man.cgi?gif(4)) to support [the transition](http://en.wikipedia.org/wiki/6to4) from IPv4 to the IPv6 standard. +> +> `gif0` is a more [generic tunneling interface](https://www.freebsd.org/cgi/man.cgi?gif(4)) [46]-to-[46]. +> +> `awdl0` is [Apple Wireless Direct Link](https://stackoverflow.com/questions/19587701/what-is-awdl-apple-wireless-direct-link-and-how-does-it-work) +> +> `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)](https://en.wikipedia.org/wiki/TUN/TAP)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:** + +```java +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: + +```java +-DDUBBO_IP_TO_REGISTRY=1.2.3.4 +``` + +也可以指定 Dubbo 服务绑定在哪块网卡上: + +```java +-DDUBBO_IP_TO_BIND=1.2.3.4 +``` + +## 9 参考文章 + +[TUN/TAP 设备浅析](https://www.jianshu.com/p/09f9375b7fa7) + +[what-are-en0-en1-p2p-and-so-on-that-are-displayed-after-executing-ifconfig](https://stackoverflow.com/questions/29958143/what-are-en0-en1-p2p-and-so-on-that-are-displayed-after-executing-ifconfig) \ No newline at end of file diff --git a/site_config/blog.js b/site_config/blog.js index 0355aea..d335673 100644 --- a/site_config/blog.js +++ b/site_config/blog.js @@ -164,6 +164,13 @@ export default { postsTitle: '所有文章', list: [ { + title: '研究 Dubbo 网卡地址注册时的一点思考', + author: '@lexburner', + dateStr: 'Apr 30th, 2019', + desc: '研究 Dubbo 网卡地址注册时的一点思考', + link: '/zh-cn/blog/dubbo-network-interfaces.html', + }, + { title: 'Dubbo 本地存根和本地伪装', author: '@beiwei30', dateStr: 'Apr 9th, 2019',