在OpenWrt路由器上使用IPv6

现在的教育网和宽带基本都提供了IPv6支持。但是,由于网络性质使然,IPv6并不能像IPv4那样,直接使用路由器共享,这使得路由器接入IPv6后,所有连接到路由器的设备都不能连接v6。

为此,必须对路由器进行一番魔改。如果路由器使用的是OpenWrt,或基于OpenWrt的固件,那么在经过一番设置后,IPv6的共享就不再是传说了。

设置脚本

为了方便路由器的配置,我将所有设置过程做成了一个脚本,并在GitHub上开源。

项目地址:https://github.com/AnClark/OpenWrt-IPv6-Scripts

脚本地址:戳这里直接下载,脚本文件名为operation.sh

使用脚本的步骤很简单,只要你准备好用于发送文件的WinSCP,与用于远程登录路由器的PuTTY。

  • 第一步,使用WinSCP,登录到路由器上,并将operation.sh上传到/tmp文件夹中。
  • 第二步,使用PuTTY或WinSCP自带的Shell,运行/tmp/operation.sh。稍等片刻,即可配置好。下次重启路由器,就可以自动将WAN的IPv6共享给路由器局域网内的设备了。

如果使用Linux,或在Windows下使用Cygwin或Git-SCM(一个在Windows下使用Git的套件),那么使用它们自带的scpssh工具,会方便得多:

# 将脚本发送到路由器上。注意路由器地址
scp operation.sh [email protected]:/tmp/
# 在路由器上运行脚本
ssh [email protected] "chmod +x /tmp/install.sh; sh /tmp/operation.sh"

原理

脚本完成的设置过程,包括了如下具体操作:

(1)安装kmod-ipt-nat6

kmod-ipt-nat6是用于启用IPv6 NAT的内核模块。在OpenWrt中,使用包管理工具opkg进行安装。

opkg update
opkg install kmod-ipt-nat6

(2)更改IPv6 ULA前缀

ULA,全称为“唯一的本地IPv6单播地址”(Unique Local IPv6 Unicast Address)[1]。这一步,我们要把它的前缀由f改为d,以将本地的IPv6地址向外广播,而不是localhost那样的闭环。

用于实现这一步的脚本如下:

uci set network.globals.ula_prefix="$(uci get network.globals.ula_prefix | sed 's/^./d/')"
uci commit network

OpenWrt的属性参数由uci工具管理,类似于Android的property机制(setprop/getprop),使用“域”来组织各类参数。脚本的第一行,在$()中调用uci命令,获取当前的ULA前缀,将执行结果传递给sed命令。这里sed命令使用了替换语法——sed 's/<原文本>/<替换文本>/',将开头第一个字符替换为字符“d”。Shell的$()语法可将命令运行结果作为文本使用,那么该用法内的命令结果就作为新的ULA前缀被应用。最后,运行第二行的命令,提交修改。

(3)将DHCP服务器模式设置为“总是广播默认路由”

如果没猜错的话,DHCP服务器模式设置为“总是广播默认路由”,可让内网中的设备能够快速获取IPv6地址。但该选项的具体原理还得查查OpenWrt的文档。

uci set dhcp.lan.ra_default='1'
uci commit dhcp

(4)添加NAT6初始化脚本

NAT6初始化脚本的作用,就是在路由器启动时自动开启IPv6的NAT功能。该脚本保存到/etc/init.d/nat6中,在我的脚本中使用bash的Here Document功能来创建它。

NAT6的内容如下:

#!/bin/sh /etc/rc.common
# NAT6 init script for OpenWrt // Depends on package: kmod-ipt-nat6

START=55

# Options
# -------

# Use temporary addresses (IPv6 privacy extensions) for outgoing connections? Yes: 1 / No: 0
PRIVACY=1

# Maximum number of attempts before this script will stop in case no IPv6 route is available
# This limits the execution time of the IPv6 route lookup to (MAX_TRIES+1)*(MAX_TRIES/2) seconds. The default (15) equals 120 seconds.
MAX_TRIES=15

# An initial delay (in seconds) helps to avoid looking for the IPv6 network too early. Ideally, the first probe is successful.
# This would be the case if the time passed between the system log messages "Probing IPv6 route" and "Setting up NAT6" is 1 second.
DELAY=5

# Logical interface name of outbound IPv6 connection
# There should be no need to modify this, unless you changed the default network interface names
# Edit by Vincent: I never changed my default network interface names, but still I have to change the WAN6_NAME to "wan" instead of "wan6"
WAN6_NAME="wan6"

# ---------------------------------------------------
# Options end here - no need to change anything below

boot() {
        [ $DELAY -gt 0 ] && sleep $DELAY
        logger -t NAT6 "Probing IPv6 route"
        PROBE=0
        COUNT=1
        while [ $PROBE -eq 0 ]
        do
                if [ $COUNT -gt $MAX_TRIES ]
                then
                        logger -t NAT6 "Fatal error: No IPv6 route found (reached retry limit)" && exit 1
                fi
                sleep $COUNT
                COUNT=$((COUNT+1))
                PROBE=$(route -A inet6 | grep -c '::/0')
        done
 
        logger -t NAT6 "Setting up NAT6"
 
        WAN6_INTERFACE=$(uci get "network.$WAN6_NAME.ifname")
        if [ -z "$WAN6_INTERFACE" ] || [ ! -e "/sys/class/net/$WAN6_INTERFACE/" ] ; then
                logger -t NAT6 "Fatal error: Lookup of $WAN6_NAME interface failed. Were the default interface names changed?" && exit 1
        fi
        WAN6_GATEWAY=$(route -A inet6 -e | grep "$WAN6_INTERFACE" | awk '/::\/0/{print $2; exit}')
        if [ -z "$WAN6_GATEWAY" ] ; then
                logger -t NAT6 "Fatal error: No IPv6 gateway for $WAN6_INTERFACE found" && exit 1
        fi
        LAN_ULA_PREFIX=$(uci get network.globals.ula_prefix)
        if [ $(echo "$LAN_ULA_PREFIX" | grep -c -E "^([0-9a-fA-F]{4}):([0-9a-fA-F]{0,4}):") -ne 1 ] ; then
                logger -t NAT6 "Fatal error: IPv6 ULA prefix $LAN_ULA_PREFIX seems invalid. Please verify that a prefix is set and valid." && exit 1
        fi
 
        ip6tables -t nat -I POSTROUTING -s "$LAN_ULA_PREFIX" -o "$WAN6_INTERFACE" -j MASQUERADE
        if [ $? -eq 0 ] ; then
                logger -t NAT6 "Added IPv6 masquerading rule to the firewall (Src: $LAN_ULA_PREFIX - Dst: $WAN6_INTERFACE)"
        else
                logger -t NAT6 "Fatal error: Failed to add IPv6 masquerading rule to the firewall (Src: $LAN_ULA_PREFIX - Dst: $WAN6_INTERFACE)" && exit 1
        fi
 
        route -A inet6 add 2000::/3 gw "$WAN6_GATEWAY" dev "$WAN6_INTERFACE"
        if [ $? -eq 0 ] ; then
                logger -t NAT6 "Added $WAN6_GATEWAY to routing table as gateway on $WAN6_INTERFACE for outgoing connections"
        else
                logger -t NAT6 "Error: Failed to add $WAN6_GATEWAY to routing table as gateway on $WAN6_INTERFACE for outgoing connections"
        fi
 
        if [ $PRIVACY -eq 1 ] ; then
                echo 2 > "/proc/sys/net/ipv6/conf/$WAN6_INTERFACE/accept_ra"
                if [ $? -eq 0 ] ; then
                        logger -t NAT6 "Accepting router advertisements on $WAN6_INTERFACE even if forwarding is enabled (required for temporary addresses)"
                else
                        logger -t NAT6 "Error: Failed to change router advertisements accept policy on $WAN6_INTERFACE (required for temporary addresses)"
                fi
                echo 2 > "/proc/sys/net/ipv6/conf/$WAN6_INTERFACE/use_tempaddr"
                if [ $? -eq 0 ] ; then
                        logger -t NAT6 "Using temporary addresses for outgoing connections on interface $WAN6_INTERFACE"
                else
                        logger -t NAT6 "Error: Failed to enable temporary addresses for outgoing connections on interface $WAN6_INTERFACE"
                fi
        fi
 
        exit 0
}

当然,为了让该脚本能够运行,还必须要修改权限。并且,存放在/etc/init.d中的脚本作为Linux中的服务来看待,因此还需启用它。

chmod +x /etc/init.d/nat6         # 设置可执行权限
/etc/init.d/nat6 enable           # 启用NAT6脚本

(5)关闭不需要的防火墙规则

OpenWrt的防火墙中有一个默认规则,叫Allow-ICMPv6-Forward。该规则在我们的实际使用中并不需要,因为我们用于IPv6内网穿透的Masquerade模块已经取代了它的功能。

uci set firewall.@rule["$(uci show firewall | grep 'Allow-ICMPv6-Forward' | cut -d'[' -f2 | cut -d']' -f1)"].enabled='0'
uci commit firewall

(6)接收广播并开启IPv6转发

接下来要修改的文件是/etc/sysctl.conf,其中包含的广播和IPv6转发有关的配置需要进行调整,将它们的值都设为“2

  • net.ipv6.conf.default.forwarding
  • net.ipv6.conf.all.forwarding
  • net.ipv6.conf.default.accept_ra
  • net.ipv6.conf.all.accept_ra

一般地,上述设置需要用户手动修改。但为了实现脚本的自动化,我使用文本处理程序sed来进行自动替换。

touch /etc/sysctl.conf

a=$(sed -n '/net.ipv6.conf.default.forwarding/=' /etc/sysctl.conf)
if [ ! "$a" ]; then
    echo "net.ipv6.conf.default.forwarding=2" >> /etc/sysctl.conf
else
    sed -i "${a}d; $((a-1))a net.ipv6.conf.default.forwarding=2" /etc/sysctl.conf
fi

a=$(sed -n '/net.ipv6.conf.all.forwarding/=' /etc/sysctl.conf)
if [ ! "$a" ]; then
    echo "net.ipv6.conf.all.forwarding=2" >> /etc/sysctl.conf
else
    sed -i "${a}d; $((a-1))a net.ipv6.conf.all.forwarding=2" /etc/sysctl.conf
fi

a=$(sed -n '/net.ipv6.conf.default.accept_ra/=' /etc/sysctl.conf)
if [ ! "$a" ]; then
    a=$(sed -n '/net.ipv6.conf.all.forwarding/=' /etc/sysctl.conf)
    sed -i "${a}a net.ipv6.conf.default.accept_ra=2" /etc/sysctl.conf
else
    sed -i "${a}d; $((a-1))a net.ipv6.conf.default.accept_ra=2" /etc/sysctl.conf
fi

a=$(sed -n '/net.ipv6.conf.all.accept_ra/=' /etc/sysctl.conf)
if [ ! "$a" ]; then
    a=$(sed -n '/net.ipv6.conf.default.accept_ra/=' /etc/sysctl.conf)
    sed -i "${a}a net.ipv6.conf.all.accept_ra=2" /etc/sysctl.conf
else
    sed -i "${a}d; $((a-1))a net.ipv6.conf.all.accept_ra=2" /etc/sysctl.conf
fi

一般情况下,这些设置都会排在一起,所以在使用sed进行操作时,可以先定位到其中一个设置项,然后对其值进行修改。有些设备上的/etc/sysctl.conf并没有上面的设置项,因此还要考虑这一情况,在配置文件中加上相应的项——首先把net.ipv6.conf.default.forwarding=2加到文件尾部,然后再依次添加剩下的几项。

TODO: 还需要讲解sed的用法!

(7)配置ip6tables,加入转发规则

最后设置ip6tables,加入IPv6 NAT转发的规则,并重启系统的防火墙服务:

echo "ip6tables -t nat -I POSTROUTING -s $(uci get network.globals.ula_prefix) -j MASQUERADE" >> /etc/firewall.user
/etc/init.d/firewall restart

测试IPv6使用状况

以上设置完成后,重启路由器,内网的IPv6设备就能够获得IPv6地址了。将任意一台设备连接到路由器上,打开它们的网络设置,如果得到了IPv6地址,且不是“fe80”开头,就说明设置成功。

当然,想让内网设备使用IPv6上网,就必须确定外网能否支持。一个简便的方法是,使用ssh登录路由器,然后ping谷歌的IPv6地址(http://ipv6.google.com.hk/ ),若能够ping通,就说明外网访问IPv6没问题。

ping6 ipv6.google.com

使用测试网站,可以更专业、更直观地检测IPv6的连接性。只要内网设备配置正确,测试均能通过:

  • Test IPv6
  • IPv6 Test
在OpenWrt路由器上使用IPv6_第1张图片
笔者的测试结果,路由器已连接华中科技大学教育网,并已经配置好IPv6 NAT

注:

  1. fe80”开头的地址是内网地址,而非公网。我路由器上的设备都能连接IPv6外网,它们的IPv6地址以 d 开头。
  2. 中国教育网(CERNET)支持IPv6访问。笔者的学校——华中科技大学,为教育网的重要节点,只要能登录校园网都能使用IPv6。
  3. 运营商网络,包括电信、联通、移动、各地广电和长城宽带,IPv6服务须额外申请,而且并不是所有地方都能办理。

致谢与参考

  • zqp19950813提供配置方法。本文的脚本基于他的教程编写。
  • 【转发】 通过openwrt的NAT6转发,使后端设备获得ipv6网络

扩展阅读

  • 校园网环境配置 OpenWRT 路由器作为 IPv6 网关(XJTU限定)
  • Padavan/Openwrt/LEDE下实现ipv6 nat /napt66
  • 从零开始配置支持锐捷认证与IPv6的路由器 [K2P]

  1. 《IPv6地址》. 北海石松 - 博客园. https://www.cnblogs.com/gogly/articles/2558150.html ↩

你可能感兴趣的:(在OpenWrt路由器上使用IPv6)