背景

继一年前的博文记一次失败的尝试——折腾openwrt用上南大IPv6后,按捺不住一颗躁动的心又一次折腾手上被大材小用了的Netgear WNDR3700 V4,在屡战屡败之后,这次终于找到了一种近乎通用的解决路由器不能使用教育网原生IPv6问题的解决方法,特此分享。

前言

这里介绍的是 在教育网IPv4、IPv6双栈环境下,网线直接连电脑可以直接获取到IPv6地址,连上IPv6互联网;但网线连接路由器后,路由器获取不到IPv6地址,并且电脑连接路由器也获取不到IPv6地址的问题。如果你的网络不支持IPv6就不要强求啦;再或者你的路由器设置IPv6后就能正常工作了,就不需要再看我写的瞎折腾了吧。

还有一点,你的路由器必须支持刷DD-WRT(理论上来说OpenWrt也是可以的,但我没测试过)。

你要是不想听我啰嗦的话就直接跳到“解决方法”那去吧~

问题描述

网线直接连电脑,马上就获取到IPv6地址了:

~ ifconfig
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=b<RXCSUM,TXCSUM,VLAN_HWTAGGING>
    ether 98:90:96:e4:34:c8
    inet6 fe80::6f:3441:2122:557c%en0 prefixlen 64 secured scopeid 0x4
    inet6 2001:250:5002:8100::2:5e62 prefixlen 128 dynamic
    inet 114.212.85.96 netmask 0xfffff800 broadcast 114.212.87.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: active

ping一下试试:

~ ping6 ipv6.google.com
PING6(56=40+8+8 bytes) 2001:250:5002:8100::2:5e62 --> 2404:6800:4008:800::200e
16 bytes from 2404:6800:4008:800::200e, icmp_seq=0 hlim=43 time=207.961 ms
16 bytes from 2404:6800:4008:800::200e, icmp_seq=1 hlim=43 time=211.010 ms

但是当用路由器后,无论我在路由器里怎么设置,电脑死活都没有IPv6地址了:

DDWRT IPv6 Setting

en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=b<RXCSUM,TXCSUM,VLAN_HWTAGGING>
    ether 98:90:96:e4:34:c8
    inet6 fe80::6f:3441:2122:557c%en0 prefixlen 64 secured scopeid 0x4
    inet 192.168.1.132 netmask 0xffffff00 broadcast 192.168.1.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: active

SSH连上DD-WRT后,DD-WRT的WAN口也没有IPv6地址:

vlan2     Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6A
          inet addr:114.212.85.225  Bcast:114.212.87.255  Mask:255.255.248.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:12509 errors:0 dropped:1094 overruns:0 frame:0
          TX packets:11056 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:6789653 (6.4 MiB)  TX bytes:2078525 (1.9 MiB)

解决思路

思路是来自于我的路由器官方固件其实就支持IPv6,并且竟然神奇地能用!

在Netgear官方固件的Web管理界面中,“高级设置” -> “IPv6” -> “因特网连接类型”里选择“穿透”,电脑不管是有线还是无线,只要连接上路由器就能获取到IPv6地址连上互联网(此处应有图)。需要强调的是 只有设置成“穿透”才有效,“DHCPv6”、“静态”等均无效

Netgear官方固件过于鸡肋,折腾DD-WRT就是必然了。虽然DD-WRT本身支持IPv6,但怎奈南大校园网就是不吃DD-WRT那一套,各种折腾均以失败告终。最后捋捋思路,想想Netgear是如何实现IPv6的。

“穿透”说白了就是把路由器的内网暴露在网线那头的公网上(希望你听得懂),只不过这里是只把IPv6网络暴露在公网上。这样当我的电脑或手机连上路由器的时候,对于IPv6网络而言,我的电脑或手机是直接连上公网的(理解成没有经过路由器);也可以理解成对于IPv6而言,路由器是工作在交换机模式,对于公网而言路由器是透明的,DHCPv6服务器能直接看到我的电脑或手机,没有路由器挡着。这样电脑或手机就相当于是直接连着网线的,显然能获取到IPv6地址了。

所以现在就是要让路由器把IPv6“穿透”。再来看看路由器,对于路由器来说,【WAN口】和【多个LAN口以及WiFi】是两个不同的网络,路由器的工作就是在【WAN口】下充当为一个网络终端(比如电脑),目的是服务【多个LAN口以及WiFi】的网络需求,而这【多个LAN口以及WiFi】就是路由器下的内网了。现在我们的目的是让【多个LAN口以及WiFi】的IPv6网络暴露在【WAN口】所在的公网上(路由器透明),用路由器里的术语来说,就是把【WAN口】和【多个LAN口以及WiFi】“桥接”起来。

“桥接”的话我们直接用Linux的网络底层命令来实现(这个才是关键)。我们的需求是:

  1. IPv4维持正常的模式,路由器接管所有IPv4流量,充当本地DHCP服务器;
  2. IPv6暴露在公网下,打通WAN口和内网。

解决办法就是:

  1. brctl桥接外网和内网;
  2. ebtables将所有非IPv6流量交给路由器进行路由。

关于第2点可能有点不太明白。结合第1点来看,仅桥接外网和内网会导致不仅IPv6,而且IPv4也暴露在了公网上;所以我们需要将所有非IPv6的数据包全部拦截下来,交给路由器进行正常的路由,而对于IPv6的数据包则交给网桥,即直达内网。

下面开始实践吧~

解决方法

路由器刷DD-WRT

这个不细说了,就是上www.dd-wrt.com下载对应的固件,按步骤刷到路由器上,记得30/30/30清除nvram。

注意刷好后 不需要 在“Setup” -> “IPv6”中将“IPv6”设为“Enable”,因为我们会直接用Linux命令操作,和DD-WRT层面不一样。

打开路由器SSH访问

后面会说用处。

  1. “Services” -> “Services” -> “SSHd”设置为“Enable”;
  2. “Administration” -> “Management” -> “SSH Management”设置为"Enable";
  3. “Security” -> “Block WAN SNMP access”勾去掉;
  4. 命令行ssh root@192.168.1.1连上路由器(密码是第一次登录Web管理界面设置的密码)。

SSH DDWRT

寻找broute模块

broute模块是ebtables里的broute表,可以控制数据包的转发和丢弃,随后要用到。在x86等的Linux下broute模块是默认加载的,但DD-WRT默认不加载此模块,且不能简单通过insmod加载。这里需要找到broute模块文件,并手动加载。

命令

root@DD-WRT:~# ls /lib/modules/
3.18.45

ls /lib/modules/命令可以看到/lib/module/下编译的模块所在的版本号目录,对于不同路由器来说,这个版本号目录名是不同的,比如我的是3.18.45

然后

root@DD-WRT:~# ls /lib/modules/3.18.45/

(注意替换3.18.45为你的版本号)就能看到你的路由器里早已编译好的模块了,仔细找找是不是有个文件名叫ebtable_broute.ko?现在记下这个绝对路径/lib/modules/3.18.45/ebtable_broute.ko(注意版本号别弄错了)。

找到内网网桥名字和外网端口

之前把【多个LAN口以及WiFi】说的很模糊,实际上来说,“内网”就是一个网桥将各个LAN口以及WiFi口连接起来了。我们可以用brctl show命令看到内网网桥:

root@DD-WRT:~# brctl show
bridge name bridge id       STP enabled interfaces
br0     8000.04a15191fd6a   no      vlan1
                            ath0
                            ath1

这里我的内网网桥名字是br0,其连接vlan1ath0ath1这三个端口;这三个端口又是什么呢?我们通过ifconfig命令看到:

root@DD-WRT:~# ifconfig
ath0      Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6B
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:39 errors:0 dropped:0 overruns:0 frame:0
          TX packets:352 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:6964 (6.8 KiB)  TX bytes:90200 (88.0 KiB)

ath1      Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6C
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:785 errors:0 dropped:0 overruns:0 frame:0
          TX packets:811 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:179123 (174.9 KiB)  TX bytes:288450 (281.6 KiB)

br0       Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6A
          inet addr:192.168.1.1  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14406 errors:0 dropped:108 overruns:0 frame:0
          TX packets:11559 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2307923 (2.2 MiB)  TX bytes:7275430 (6.9 MiB)

br0:0     Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6A
          inet addr:169.254.255.1  Bcast:169.254.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

eth0      Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6A
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:26089 errors:0 dropped:0 overruns:0 frame:0
          TX packets:22319 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:9392655 (8.9 MiB)  TX bytes:9309251 (8.8 MiB)
          Interrupt:4

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING MULTICAST  MTU:65536  Metric:1
          RX packets:2 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:132 (132.0 B)  TX bytes:132 (132.0 B)

vlan1     Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6A
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:13580 errors:0 dropped:0 overruns:0 frame:0
          TX packets:11263 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2133400 (2.0 MiB)  TX bytes:7141450 (6.8 MiB)

vlan2     Link encap:Ethernet  HWaddr 04:A1:51:91:FD:6A
          inet addr:114.212.85.225  Bcast:114.212.87.255  Mask:255.255.248.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:12509 errors:0 dropped:1094 overruns:0 frame:0
          TX packets:11056 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:6789653 (6.4 MiB)  TX bytes:2078525 (1.9 MiB)

ath0ath1显然就是两个天线(WiFi)端口了,vlan1不言而喻就是那些LAN口;这里br0看IP也明显多了,就是内网的网桥。再看看上面br0里没出现的vlan2,看IP就能发现是WAN口了。

现在记下内网网桥名字br0(你的可能不一样),以及WAN口名字vlan2(你的可能不一样)。

配置穿透试试

执行如下命令:

root@DD-WRT:~# /sbin/insmod ebtables
root@DD-WRT:~# /sbin/insmod ebtable_broute
root@DD-WRT:~# /sbin/insmod /lib/modules/3.18.45/ebtable_broute.ko
root@DD-WRT:~# ebtables -t broute -A BROUTING -p ! ipv6 -j DROP -i vlan2
root@DD-WRT:~# brctl addif br0 vlan2

(注意替换版本号,内网网桥名字,WAN口名字) 现在重启电脑网卡(不知道怎么重启网卡的话那就重启系统吧~),ifconfig看看IPv6回来了没有:

~ ifconfig
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=b<RXCSUM,TXCSUM,VLAN_HWTAGGING>
    ether 98:90:96:e4:34:c8
    inet6 fe80::6f:3441:2122:557c%en0 prefixlen 64 secured scopeid 0x4
    inet 192.168.1.132 netmask 0xffffff00 broadcast 192.168.1.255
    inet6 2001:250:5002:8100::2:5e62 prefixlen 128 dynamic
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: active

ping一下试试:

~ ping6 ipv6.google.com
PING6(56=40+8+8 bytes) 2001:250:5002:8100::2:5e62 --> 2607:f8b0:4007:80b::200e
16 bytes from 2607:f8b0:4007:80b::200e, icmp_seq=0 hlim=44 time=203.040 ms
16 bytes from 2607:f8b0:4007:80b::200e, icmp_seq=1 hlim=44 time=202.543 ms

IPv6回来啦! 开不开心?

穿透命令的原理

这里说说上面5行命令的原理。

insmod ebtables即加载ebtables模块,因为DD-WRT里ebtables默认是没有加载的。

insmod ebtable_broute即加载broute模块,一般来说实际是没有加载的(这里我也不太明白)。

insmod /lib/modules/3.18.45/ebtable_broute.ko即从指定的路径加载ebtable_broute.ko文件,这里是真正加载broute模块啦。

ebtables -t broute -A BROUTING -p ! ipv6 -j DROP -i vlan2即使用ebtables过滤数据链路层数据包,使用broute表的BROUTING规则,对于所有的非ipv6协议数据包进行路由(这里DROP的意思是进行路由),而其他的(IPv6)数据包则交给网桥。

brctl addif br0 vlan2即将端口vlan2(即WAN口)与br0(即内网)桥接。

如上就实现了对IPv4路由,对IPv6穿透了。

固化穿透

现在重启路由器试试?重启后上述配置的穿透就失效啦!我们需要把这个穿透规则保存到路由器,并且在路由器每次启动时都执行一次。

下面就简单点说了。DD-WRT实际早就考虑到这个需求了,用户可以自己编辑路由器启动、关机、防火墙启动时执行的脚本命令。对于我们的过滤数据包的命令,DD-WRT是建议由防火墙触发的。

需要注意的是如上穿透命令也不一定在防火墙启动时就能正常执行的,因为这时候可以ebtables等模块还没准备好,这时insmod ebtables就会出错,导致配置失败。所以我们需要给上述配置命令加点条件确保能正常执行。

在DD-WRT的Web界面中定位到“Administration” -> “Commands”,复制粘贴如下命令(注意替换版本号,内网网桥名字,WAN口名字!):

(
set -x
PATH="/sbin:/usr/sbin:/bin:/usr/bin:${PATH}"
while ! lsmod | grep -qm1 ebtables; do
    sleep 10
    /sbin/insmod ebtables
    /sbin/insmod ebtable_broute
    /sbin/insmod /lib/modules/3.18.45/ebtable_broute.ko
    ebtables -t broute -A BROUTING -p ! ipv6 -j DROP -i vlan2
    brctl addif br0 vlan2
done
) 2>&1 | logger -t $(basename $0)[$$] &

点击“Save Firewall”就好啦!

现在重启路由器,看看重启后是不是电脑或手机就自动获得IPv6地址了?

一点唠叨

这样配置下来手机或电脑或其他东西通过有线或无线连接到路由器都能获得到IPv6地址了。不过这么配置IPv6穿透实际是有一个缺陷的,即路由器本身是没有IPv6地址的,从上面说的原理来看路由器也是不可能有IPv6地址的。这样对于某一些特殊的需求就满足不了了,比如通过IPv6访问路由器的http服务器等。

文章讲的很啰嗦,因为很多地方在讲出发点和原理。从我折腾DD-WRT和openwrt这段时间来,不管中文的还是英文的网络资料里,似乎大家都侧重于讨论解决方法,而很少讨论思路和原理;网上很多回答就是直接说怎么修改配置,怎么执行命令,但很少说为什么该这么修改,为什么该这么执行。我想这对于折腾路由器、了解错综复杂的网络配置来说是不对的,网络的结构、状态换了一个场景换个一个时间就变了,不了解解决网络问题背后的原理,大概只会是跳出一个坑后又跳进了另一个坑。

注意事项

对于Windows 10的16071版,可能存在获取不到IPv6地址的bug,这个是微软的问题,尝试cmd中如下命令来重新获取IPv6地址:

ipconfig /release6
ipconfig /renew6

总结

本文介绍通过brctlebtables等命令桥接端口、过滤数据包实现了路由器的IPv6穿透,对教育网连接路由器获取不到IPv6地址的问题提供了一个通用的解决办法,也是一种DD-WRT下配置IPv6的一个通行的解决方法,仅需要“终端直接连接校园网能够自动获取IPv6地址”这一前提条件。

参考