背景
在云服务中,IPv6地址往往比IPv4更充裕,但许多VPS提供商默认只分配一个IPv6地址。当你需要为爬虫、邮件服务或容器分配独立IPv6地址时,手动添加地址不仅繁琐,还可能遇到内核限制。本文将介绍一种基于systemd-networkd的优雅方案,让你轻松添加无限个IPv6地址,且重启后自动生效。
原理
传统方式使用ip addr add添加IPv6地址,但每个网卡有数量限制(通常为64个)。通过systemd-networkd,我们可以利用NDP代理(Neighbor Discovery Proxy) 和地址生成模式,让内核自动管理地址池,突破数量限制。核心思路是:
- 将网卡配置为接受多个IPv6地址,并启用NDP代理。
- 使用
ip route添加路由,让子网内所有地址都可达。 - 通过脚本或服务动态添加地址,或利用
Address指令批量配置。
前提条件
- VPS运行Linux(推荐Debian/Ubuntu或CentOS 8+)
- systemd版本≥240(大多数现代发行版满足)
- VPS已分配至少一个IPv6地址和子网(例如
2001:db8::/64) - root权限
步骤一:确认当前IPv6配置
首先,查看你的IPv6地址和网关:
ip -6 addr show
ip -6 route show default
假设你的主网卡为eth0,IPv6地址为2001:db8::1/64,网关为fe80::1。
步骤二:配置systemd-networkd
systemd-networkd是systemd的网络管理守护进程,默认可能未启用。我们将其配置为管理eth0。
1. 启用systemd-networkd
systemctl enable systemd-networkd
systemctl start systemd-networkd
2. 创建网络配置文件
在/etc/systemd/network/下创建文件20-eth0.network:
[Match]
Name=eth0
[Network]
DHCP=no
IPv6AcceptRA=no
BindCarrier=eth0
[Address]
Address=2001:db8::1/64
[Route]
Destination=::/0
Gateway=fe80::1
[IPv6SendRA]
# 启用NDP代理,使子网内地址可达
ProxyNDP=yes
注意:ProxyNDP=yes是关键,它让系统响应子网内所有地址的邻居请求,即使这些地址没有配置在网卡上。
3. 重启networkd
systemctl restart systemd-networkd
步骤三:批量添加IPv6地址
现在,我们可以通过一个脚本动态添加大量IPv6地址。以下脚本生成从2001:db8::2到2001:db8::1000的地址:
#!/bin/bash
# add-ipv6.sh
SUBNET="2001:db8::"
START=2
END=4096 # 可根据需要调整,但不要超过子网大小
for i in $(seq $START $END); do
ADDR=$(printf "$SUBNET%x" $i)
ip addr add $ADDR/64 dev eth0
# 可选:添加路由,确保流量进入
# ip route add $ADDR/128 dev eth0
done
运行脚本:
chmod +x add-ipv6.sh
./add-ipv6.sh
注意:直接使用ip addr add添加大量地址可能触发内核限制(最大约64个)。此时,NDP代理的作用就显现了:即使地址没有显式添加,只要路由可达,系统也能响应邻居请求。因此,我们实际上不需要添加所有地址,只需添加路由即可。
步骤四:使用路由代替地址(推荐)
更优雅的方式是只添加一条路由,让整个子网都可达:
ip route add 2001:db8::/64 dev eth0
但这样所有地址都会指向eth0,如果VPS提供商实施了反向路径过滤(rpfilter),可能会丢弃数据包。我们需要调整rpfilter:
sysctl -w net.ipv6.conf.eth0.rp_filter=2 # 宽松模式
持久化:在/etc/sysctl.d/90-ipv6.conf中添加:
net.ipv6.conf.eth0.rp_filter=2
net.ipv6.conf.all.rp_filter=2
然后应用:
sysctl -p /etc/sysctl.d/90-ipv6.conf
步骤五:动态分配与回收
如果你需要为每个服务分配独立地址,可以编写一个守护进程,监听请求并从地址池中分配。这里提供一个简单的bash函数:
# 分配一个地址
function assign_ipv6() {
local pool_file="/var/ipv6_pool.txt"
local used_file="/var/ipv6_used.txt"
local subnet="2001:db8::"
local next=$(cat $pool_file 2>/dev/null || echo 2)
# 查找未使用的地址
while ip addr show | grep -q "${subnet}$(printf '%x' $next)"; do
((next++))
done
local addr="${subnet}$(printf '%x' $next)/64"
ip addr add $addr dev eth0
echo $next > $pool_file
echo $addr >> $used_file
echo $addr
}
回收地址:
function release_ipv6() {
local addr=$1
ip addr del $addr dev eth0
# 从used_file中删除
sed -i "\|$addr|d" /var/ipv6_used.txt
}
常见坑与解决
坑1:NDP代理无效
如果配置了ProxyNDP=yes但其他主机无法ping通地址,检查内核参数:
sysctl net.ipv6.conf.eth0.proxy_ndp
如果为0,手动开启:
sysctl -w net.ipv6.conf.eth0.proxy_ndp=1
坑2:地址冲突
多个服务使用相同地址会导致路由混乱。务必使用状态管理,如上面脚本中的used_file。
坑3:重启后地址丢失
使用systemd-networkd配置后,静态地址会保留。但动态添加的地址会丢失。解决方案:将分配脚本作为systemd服务运行,或使用ip addr add命令写入/etc/rc.local。
更好的做法:利用systemd-networkd的[Address]段支持多个Address指令,但数量有限。对于大量地址,建议在[Network]下添加Address=2001:db8::/64,然后通过路由管理。
总结
通过systemd-networkd的NDP代理和路由配置,你可以轻松实现VPS上无限个IPv6地址的添加。核心要点:
- 启用NDP代理,让子网内所有地址可达。
- 使用路由而非显式地址,突破内核限制。
- 调整rp_filter避免丢包。
- 编写脚本管理地址池,实现动态分配。
这种方法不仅简化了管理,还提高了灵活性,适用于大规模IPv6地址需求场景。
延伸阅读
希望这篇教程对你有所帮助!如果你有更好的方案,欢迎在评论区交流。