通过单个树莓派 从运营商家庭宽带的DHCP获取多个动态公网IP
香港的运营商,给家庭宽带配的光猫普遍都会提供动态的公网IP,光猫一般有2-4个口,每个口都会分配独立的动态公网IP。
需求背景
如果我要跑多个独立项目,但又不想共享一个出入口IP的话(走NAT做端口转发或者放在同一个机器上分配不同监听端口),那么就需要多个公网IP,如果光猫的口不够用,最简单的方式,就是从光猫接一个交换机出来,然后把多个设备都连到交换机,因为都在同一个L2广播域,所以连交换机的设备和直接连光猫是一样的。不过这样的问题是,浪费硬件、浪费电、浪费空间,拆多台设备管理上也很麻烦,所以有另一种更好的方法。
尝试macvlan方案
使用linux kernel自带的macvlan driver在主网卡虚拟多张网卡,每张虚拟网卡自带独立的MAC地址,配置后会自动在主网卡上注册一个rx_handler,这时候主网卡物理从外部收到传入封包时,会先进入macvlan_handle_frame()函数来预处理,寻找封包目的地MAC地址对应的虚拟网卡,匹配上后会修改skb->dev到目标虚拟网卡并返回RX_HANDLER_ANOTHER,进入到虚拟网卡来处理(不再继续主网卡原有的处理流程),并且最终进入到虚拟网卡所在namespace的网络栈。
docker原生支持macvlan,于是直接创建几个用macvlan网络的容器来尝试,测试下来,在我自己搭建的实验环境下,容器里的udhcpc能顺利从DHCP获取IP地址,但是一旦接到光猫上实测,只要udhcpc一尝试经过光猫从运营商的DHCP获取IP,就会断连。经过一番实验排查后,大概率是光猫限制了一个口只能有一个设备(一个口绑定一个MAC地址),所以macvlan没法使用。
尝试DHCP Client Identifier方案
我开始研究其他路径,发现DHCP协议里,客户端发Discover包有一个Client Identifier可选字段(Option 61),用来给服务器区分客户端,这个字段平时大概率不会用到。我的猜想,只要client id提供不同的值,就算是同一个MAC地址,从DHCP服务器的角度,属于不同的客户端,理论上就会分配不同的IP地址。
我去网上寻找能支持自定义client id字段的DHCP客户端软件,但基本没找到,就算有也是只能发一个discover包,没有实现全周期管理。于是我自己写了个python脚本,实现discover、offer、request、ack全流程并且支持自定client id。用了python的scapy套件来构建封包,核心代码如下:
from scapy.layers.inet import IP, UDP
from scapy.layers.dhcp import DHCP, BOOTP
from scapy.layers.l2 import Ether

验证成功
实测成功了,使用不同的client id,运营商的DHCP会给我分配不同的公网IP地址,并且租期是一致的,没有比较短,可以稳定使用。用ip addr add添加新分配的IP后,这些公网IP都能同时使用。另外,只要保持在每次到期前跟DHCP续期,这个动态IP是不会变的,所以基本可以视为是半固定的公网IP。
于是我把python脚本继续完善了一下,使用多个固定的client id来获取多个固定的公网IP,并且到期前自动续期,跑了几天的日志如下:

至于如何使用这些IP,给主网卡ip addr add全部加上就好,这些IP就可以同时使用。

容器隔离方案
如果需要隔离namespace或者在容器内使用,可以使用ipvlan driver的L2模式,把获取到的公网IP绑到虚拟网卡上,运作原理和上述的macvlan差不多,主要区别是ipvlan虚拟网卡会共用主网卡的MAC地址,不会独立分配MAC地址,绕开光猫一口一MAC的限制。
这样就成功让每个项目,在独立的容器里,有独立的namespace以及独立的公网IP,全部放到一个树莓派里运作和管理。
2025.05更新
运营商的系统和光猫貌似升级了,client id方法已经不管用,同一个来源MAC地址,不论client id给什么值,只会返回同一个IP地址。但是,通过永不放弃的精神,实验发现新的光猫没有一个口只能绑定一个MAC地址的限制,所以改成用macvlan的方式,由于来源MAC地址不同,DHCP服务器认为是不同的设备,会分配不同的IP地址,而光猫也不限制设备数,所以又可以继续使用了。