ISATAP+NAT: 路由器后面的客户端怎么通过 ISATAP 访问 IPv6

现在用的是一台小米路由器,配置起来比较麻烦。我买的 R2S 还在路上。因此服务器要访问 IPv6 (主要是隧道需要)就需要直接在服务器上配置 ISATAP 隧道。如果服务器直接用公网 IP 上网,这配置起来简单,只需要参考学校官网提供的教程即可。但是这个教程无法适用于服务器放置于路由器之后的情况。

网上找到了这篇文章:Mac OS X下配置ISATAP(包括在NAT后)备份链接),里面提供了一个 macOS 上可用的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/local/bin/fish
function isatap

set REMOTE_IP 166.111.21.1
set LINK_PREFIX "fe80::200:5efe"
set GLOBAL_PREFIX "2402:f000:1:1501:200:5efe"

if sudo ifconfig gif0 destroy
echo "Previous gif0 destroyed"
end

if test (count $argv) = 0
set PUBLIC_IP (curl ifconfig.me)
else
set PUBLIC_IP $argv
end
set LOCAL_IP (sudo ifconfig en0 | grep inet | grep -v inet6 | awk '{print $2}')

echo "Public IP: $PUBLIC_IP, Local IP: $LOCAL_IP"

sudo ifconfig gif0 create
sudo ifconfig gif0 tunnel $LOCAL_IP $REMOTE_IP
sudo ifconfig gif0 inet6 $LINK_PREFIX:$PUBLIC_IP prefixlen 64
sudo ifconfig gif0 inet6 $GLOBAL_PREFIX:$PUBLIC_IP prefixlen 64

sudo route delete -inet6 default
sudo route add -inet6 default $GLOBAL_PREFIX:$REMOTE_IP

echo "Done"
end

虽然形式不一样,对比学校官网提供的 Bash 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
REMOTE_IP6="2402:f000:1:1501:200:5efe"
REMOTE_IP4="166.111.21.1"

IFACE4=`ip route show|grep default|sed -e 's/^default.*dev \([^ ]\+\).*$/\1/'`
IP4=`ip addr show dev $IFACE4 | grep -m 1 'inet\ ' | sed -e 's/^.*inet \([^ \\]\+\)\/.*$/\1/'`

sudo ip tunnel del sit1 # 删除已经创建的设备,若没有则忽略
sudo ip tunnel add sit1 mode sit remote $REMOTE_IP4 local $IP4
sudo ip link set dev sit1 up
sudo ip -6 addr add $REMOTE_IP6:$IP4/64 dev sit1
sudo ip -6 route add default via $REMOTE_IP6:$REMOTE_IP4 dev sit1

其基本步骤都是:

  1. 建立一个 sit 隧道;
  2. 为隧道 interface 添加地址;
  3. 添加默认路由;

对 NAT 场景的处理的主要区别在第二步。在第二个脚本中,使用的是从本地的 ip route 获取的IP。如果是在路由器场景下,这拿到的是 LAN 地址。而在第一个脚本中,是通过 curl ifconfig.me 来获取的 IP,这拿到的是公网 IP。以同样的精神,我对学校提供的脚本进行了改造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash
USER=$(whoami)

if [ "$USER" != "root" ]; then
echo "This script should be run as root!"
exit 1
fi

REMOTE_IP6="2402:f000:1:1501:200:5efe"
REMOTE_IP4="166.111.21.1"

IFACE4=`ip route show|grep default|grep "192.168"|sed -e 's/^default.*dev \([^ ]\+\).*$/\1/'`
IP4=`ip addr show dev $IFACE4 | grep -m 1 'inet\ ' | sed -e 's/^.*inet \([^ \\]\+\)\/.*$/\1/'`
PUBLIC_IP=`curl ifconfig.me`

echo "Public: ${PUBLIC_IP}, Local: ${IP4}"

DEFAULT_ROUTE=`ip -6 route show |grep default|sed -e 's/^default via \([^ ]\+\).*$/\1/'|grep fe80`

if [ ! -z "$(ifconfig | grep sit1)" ]; then
ip tunnel del sit1 # 删除已经创建的设备,若没有则忽略
echo "Delete old tunnel sit1"
fi
ip tunnel add sit1 mode sit remote $REMOTE_IP4 local $IP4
ip link set dev sit1 up
ip -6 addr add $REMOTE_IP6:$PUBLIC_IP/64 dev sit1
ip -6 route add default via $REMOTE_IP6:$REMOTE_IP4 dev sit1

if [ ! -z "$DEFAULT_ROUTE" ]; then
ip -6 route del default via $DEFAULT_ROUTE dev $IFACE4 proto static metric 100 pref medium
echo "Remote original gateway"
fi

为什么要修改为公网地址呢?这是因为 ISATAP 不支持路由发现,因此需要固定 IP 来指引回溯流量找到客户端。不过这里有一个问题:公网地址实际上是路由器的地址,这意味着 ISATAP 响应是发送给路由器的。路由器如何知道这个响应应该发送给 LAN 的那个地址呢?

从实际操作来看,路由器应该是记住了发送 ISATAP 的客户端,而且并非“逐包”记忆。这意味着如果你再配置了一个 ISATAP 客户端之后,如果更换了了另一个地址配置 ISATAP 客户端,那么响应包会无法被路由器正确的转发,这时你需要重启路由器。当然,这也意味着你再一个路由器后面只能挂一个 ISATAP 客户端。

完美的方式是在路由器上使用 ISATAP 客户端,等我的 R2S 到了就可以试了。