过年前时间积累了不少文章的坑都没填,一直到最近发现梯子有问题折腾一下IPv6,直到现在才想起来网志好久没有写orz

于是这次折腾IPv6还是学到了很多东西,GFW还真是网络知识的催化剂啊XD

背景

原来的路由器工作得挺好,但近来一次在东京的Linode上IP被屏蔽,这个现象似乎中枪的不在少数,不少国内Linode用户都出现这个现象。于是乎,决定把家里的网络能力扩展成双栈的(dual-stack,即IPv4/IPv6都支持)

先来说说家里网络的条件

想来家里的网络结构应该和诸位差不会太多吧?一台路由器(现在都带无线了吧?),其他设备比如计算机、手机、平板等等都连接到它上面接入互联网。

现在使用的这台路由器是一台支持802.11n的Baffulo WZR-HP-G300NH,它的好处在于千兆有线,六类线能轻松跑上70MB/s的速度,比较满意,此外支持DDWRT,虽然一直没刷;宽带则是电信FTTH的下行20M,通过PPP拨号上网,没有使用IPTV;家里需要上网的设备有计算机(通过有线),手机、平板、PS3等(通过WiFi),大致的拓扑如下

             telecom broadband
                     |
        +------------+------------+
        | buffalo wireless router |
        +---+----------+--------+-+
           /           |          \
        wired        wired       wireless
         /             |            \
    +---+-------+   +--+--+     +--+----------------------+
    |  PC       |   | RPi |     | notebook / mobile / PS3 |
    +-----------+   +-----+     +-------------------------+
    |  virtual  |
    |  machine  |
    +-----------+

之前的梯子一直是在PC的一台虚拟机里,端口通过路由上设置NAT转发到这台虚拟机上。缺点显而易见,PC需要关闭或者异常断电,梯子的效果就没了。

在梯子以外,某还在这台机子上跑了不少服务,例如DNS、openvpn server用以从外部连到家里。

去年7月份的时候某搞了一块Raspberry Pi;这块相信一定有所耳闻,廉价的ARM兼容开发板,许多开源硬件玩家应该都会接触到;刚开始买来时某就是打算拿它替代某那台身兼多职的虚拟机;ARMv6的CPU、256MB内存已经足够使用了,USB扩展以及其他扩展接口可以玩出很多花样。

(去年还出了新款,内存升级到512MB,前几天有同僚还入了一块,国内也有正规代理)

再来说说这次折腾的主要目的,IPv6——最大的吸引力在于IPv6目前没有过滤,另外因为目前世界上IPv6真正的流量还相当少,所以(IPv6网络间的)访问速度是很有信心的。不幸的是,Buffalo自带的固件里并不支持该设置(泪奔,Airport Extreme里自带…),没折腾过DDWRT某又觉得直接开刷风险比较大所以…

准备工作

对于同样想用上IPv6的玩家们,这里就要介绍一些要准备的工作了。

  1. 一个IPv6的隧道方式
  2. 能掌握自己网络公网上通信的端口
  3. 有爱折腾的耐心

第1条需要牵涉到背景知识,其实IPv6很早就被提出,也真正在互联网上使用有不短的时间了,尤其是那些大一些的骨干网络和机房;基于各种大家都隐约知道的原因,我朝网络还未部署,起码从未公布过(教育网不能算,教育网从未是对公众开放的);一般情况下我们使用的还是32位的IPv4地址通信,而IPv4地址的枯竭问题也是21世纪以来互联网的一大事件,IPv6如此缓慢的普及脚步看来还得再过上几年才会遇到历史的拐点吧。既然只能进行IPv4通信的网络无法通信,那么就需要利用各种隧道技术,在IPv4的节点间建立起IPv6的隧道,常见的有6in4等技术;也有很多网络服务提供商提供了这样的服务,例如HESixXS等还向公众免费提供隧道服务,某就是使用的HE的免费服务

第2条是必要条件,因为也要让远端的节点知道该和谁建立隧道,某采用的是公网IP直接通信的方案;而看网上的资料,采用NAT后,在网关后的机器上也是可以实现的(当然之后的路由配置等就要相应改变了),不论是哪种,都需要能控制通信访问(NAT也得把公网端口映射进来)

再来介绍一下某的方案条件

  1. 电信拨号后有一个公网IP,采用DDNS可以比较方便的使用
  2. 路由设备自己控制(在这里是Raspberry Pi)
  3. 因为是公网映射,所以像路由一样走2张网卡更科学,可以搞张USB网卡,淘宝上很多便宜的
  4. IPv6隧道使用HE的服务
  5. RPi的OS采用的是Gentoo Linux(某就是爱Gentoo XDD)

如果同样使用RPi又嫌自己构建OS麻烦的,可以直接使用debian的镜像

而既然要作为家用路由器,它还会担当以下这些职责

  1. sshd,这个必然的…
  2. iptables 路由,自然的防火墙也需要设置
  3. polipo + supervisord,HTTP proxy,简单好用
  4. openvpn server/client,加密通道,出去以及进来(使用IPv6连接,重点~)
  5. dhcpd,DHCP服务,自动分配LAN里连上的其他设备
  6. radvd,IPv6的地址分配服务,这样让LAN里的设备也可以和互联网直接进行IPv6通信
  7. dnsmasq,DNS请求转发,走的是openvpn的加密通道,解决域名污染问题;不过相对的因为是到国外解析所以CDN等IP可能也是国外的结果,可能会出现慢的情况,所以某不在意啦XDDD 皮神 [@SAPikachu]SAPikachu 的方案是通过chnroute走,很不错的。

改造后的拓扑

             telecom broadband        (HE tunnel broker)
                     |                     /
                    eth1(ppp0)            /
                  +--+--+                /
                  | RPi +------he6-------
                  +--+--+
                    eth0
                     |
        +------------+------------+
        | buffalo wireless router |
        +---+-------------------+-+
           /                      \
        wired                    wireless
         /                          \
    +---+-------+               +--+----------------------+
    |  PC       |               | notebook / mobile / PS3 |
    +-----------+               +-------------------------+

开工

IPv6隧道服务申请

某使用的HE的服务,去tunnelbroker上注册一个帐号后新建一个tunnel节点(他们允许免费使用建立最多5个节点,机房某就使用了默认推荐的节点)

建立完成后,tunnel节点会有以下这些信息

  • 隧道服务器的IPv4地址,这里假设是210.210.210.210
  • 隧道服务器的IPv6地址,这里假设是2607:f8b0:400c:c04::1
  • 本地IPv6地址,这里假设是2607:f8b0:400c:c04::2
  • 本地IPv6 prefix /64 的路由段 2607:f8b0:400d:c04
  • 最后还需要填上自己的公网IPv4地址,curl ifconfig.me就可以拿到

注意,远端服务器应该有来源通信地址的验证,所以如果是动态IP,每次更改后需要来修改这个节点设置里自己的IPv4地址。tunnelbroker也有提供API更新,后面详细介绍

以下这些设置均参考了来自网络的资源

OS 和 Kernel

如果是使用其他的二进制发行版,这步可以省略,不过确保kernel的模块都有支持到;某相信移动设备的Linux kernel默认还是会精简掉不少东西的…

构建ARM架构交叉编译以及RPi环境的准备工作某就不在这里赘述了,近期某也会把它整理出来,需要的话,可以看Gentoo的wiki

(某有留buildpkg,所以有兴趣的巨巨可以找某要bin包XDDD)

一些必要的kernel功能,主要是openvpn要用的tun/tap啊,iptables啊,ppp啊等

最后的squashfs是为了在RPi读portage更快做的,某把portage直接打包成squashfs,SD卡上读取速度快不少。

Gentoo的话,需要在全局USE里加上ipv6的flag来让软件包都支持ipv6;嘛,现在应该都已经带了

IPv6的iptables NAT功能要在3.7才加入,现在RPi的kernel最新还是3.6.11所以暂时用不到;反正对某来说IPv6只要出去就可以了XDDD

这份配置某也放到gist上了

RPI 3.6.11 kernel for home router

Networking support --->
  Networking options --->
    [*] Network packet filtering framework (Netfilter) --->
      Core Netfilter Configuration --->
        <*> Netfilter connection tracking support
        <*>   FTP protocal support
        <*>   IRC protocal support
        <*>   Connection tracking netlink interface
        -*- Netfilter Xtables support (required for ip_tables)
            *** Xtables targets ***
        <*> LOG target support
        <*> "MARK" target support
            *** Xtables matches ***
        <*> "conntrack" connection tracking match support
        <*> "iprange" address range match support
        <*> "mac" address range match support
        <*> "Multiport" Multiple port match support
        <*> "state" match support
      IP: Netfilter Configuration --->
        <*> IPv4 connection tracking support (required for NAT)
        <*> IP tables support (required for filtering/masq/NAT)
        <*>   Packet filtering
        <*>     REJECT target support
        <*>   Full NAT
        <*>     MASQUERADE target support
        <*>     REDIRECT target support
        <*>   Packet mangling

Device Drivers --->
   Connector - unified userspace <-> kernelspace linker
  [*] Block Devices --->
    <*> Loopback device support
  [*] Network device support --->
    [*] PPP (point-to-point protocal) support
       PPP BSD-Compress compression
       PPP Deflat compression
    [*]   PPP filtering
       PPP MPPE compression (encryption) (EXPERIMENTAL)
       PPP over Ethernet
       PPP support for async serial ports
       PPP support for sync tty ports

Cryptographic API --->
     SHA224 and SHA256 digest algorithm
     SHA384 and SHA512 digest algorithms
        *** Compression ***
     Deflate compression algorithm

File systems --->
  [*] Miscellaneous filsystems --->
     SquashFS 4.0 - Squashed file system support
    [*]  Squashfs XATTR support
    [*]  include support for ZLIB compressed file systems
    [*]  include support for LZO compressed file systems
    [*]  include support for XZ compressed file systems

USB网卡

某是在淘宝上买的乐扩USB2.0网卡,芯片是MCS7832,kernel里带上驱动即插即用。

网络设置

现在RPi上有两张网卡,把外网叫做WAN(eth1),内网的叫做LAN(eth0),而实际上,拨号后以后公网的interface是ppp0

如果想详细理解ip tunnel如何设置的,可以参考tunnel broker节点那里配置例子,iproute2和net-tools都有,详细介绍的文章,网上一搜一大把

首先看eth1网络设置,因为是PPPoE拨号上网,ppp配置即可;eth0内网,固定IP 假设是192.168.5.1。同样的把网络启动时会自动执行的命令也一并带上,主要是那个ip tunnel的配置,而本机的IP可以随便写,因为之后ppp拨号成功后会获取IP来更新它

某使用的是iproute2

## /etc/conf.d/net


modules="iproute2"

# 内网,也是LAN的IPv4/IPv6网关,注意IPv6地址要是prefix /64段上的地址,例如我用了这个段下的1地址
config_eth0=("192.168.5.1/24" "2607:f8b0:400d:c04::1/64")

# DNS设置
dns_servers_eth0="8.8.8.8"
dns_servers_eth1="8.8.8.8"

# 模拟远端IPv6隧道出口的地址,放在eth1上安全一些
config_eth1="2607:f8b0:400c:c04::1"

# 配置公网ppp拨号,实际以后使用的公网网卡指的是ppp0
config_ppp0="ppp"
link_ppp0="eth1"
plugins_ppp0="pppoe"
pppd_ppp0="defaultroute"

# PPPoE拨号密码,电信的话打10000号报上宽带帐号问密码就行
username_ppp0="your_username"
password_ppp0="your_password"

depend_ppp0() {
  need net.eth1
}

# 建立隧道的重点,remote地址用节点里的IPv4地址
# local地址随便填,ppp拨号后会来修改
iptunnel_he6=(
    "mode sit remote 210.210.210.210 local 123.123.123.123 ttl 255"
    )

# 根据实际需要修改
# mtu_he6="1480"

# he6是隧道网卡,配置节点里的本地IPv6地址
config_he6="2607:f8b0:400c:c04::2/64"
routes_he6="default via 2607:f8b0:400c:c04::1 dev he6"

之后建立服务软链文件,配置开机启动

cd /etc/init.d
ln -s net.lo net.ppp0
ln -s net.lo net.he6

rc-update add net.eth0 boot
rc-update add net.ppp0 boot
rc-update add net.he6 boot

修改ppp的运行后置脚本,它会在ppp拨号成功并获取到公网IP后运行

## /etc/ppp/ip-up.d/95-update-he-tunnel-endpoint.sh

#!/bin/bash
## 这里第一个参数ppp网卡设备,如果有多个,也要像某这样判断一下
## 第4个是获取到自己的公网IP地址
## curl用于每次重新拨号后去更新tunnel节点的客户端IPv4地址,用户名密码即tunnel broker的帐号,tunnel id在tunnel页面能找到

PPP_DEV=$1

PPP_LOCAL_IP=$4

TUNNEL_ID="tunnel_id"

if [[ $PPP_DEV == "ppp0" ]]; then
  logger "Updating he6 tunnel local endpoint to $PPP_DEV IP $PPP_LOCAL_IP"
  ip tunnel change he6 local $PPP_LOCAL_IP
  curl -k "https://USERNAME:PASSWORD@ipv4.tunnelbroker.net/ipv4_end.php?ip=AUTO&tid=${TUNNEL_ID}"
fi

启动所有网卡服务

/etc/init.d/net.eth0 start # 这个应该已经开了
/etc/init.d/net.ppp0 start

ppp0拨号成功,把eth1也带起来,根据前面设置自动配置ip tunnel,正常的话这时已经配置完毕,尝试访问

ping6 www.google.com

至此RPi本身已经具备了访问IPv6互联网的能力,剩下的是做一些路由器的配置,也让LAN里其他设备能够访问IPv6

radvd

radvd是一个用于广告,分配IPv6地址的服务;可以理解为IPv4的DHCP。使用它可以自动为LAN的其他支持IPv6的设备分配地址已经推送路由,这样其他设备也可以具备访问IPv6网络的能力。而某使用的是stateless的IPv6配置,安装后修改配置文件

# /etc/radvd.conf


interface eth0 #监听在LAN的网卡上
{
  AdvSendAdvert on;
  AdvLinkMTU 1480;
  MaxRtrAdvInterval 300;

  prefix 2607:f8b0:400d:c04::1/64  #这个地方就是分配的/64的路由段,以及指定内部IPv6网关的地址
  {
    AdvRouterAddr on;
    AdvOnLink on;
    AdvAutonomous on;
  };

};

下面是其他作为路由器的配置

ip forwarding

需要打开kernel的转发配置,这样网络数据包才能经过转发

# /etc/sysctl.conf

net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding=1
net.ipv4.conf.default.rp_filter = 1
net.ipv6.conf.all.forwarding=1

iptables

IPv6因为其实现和地址丰富的关系,往往一个请求可以路由到顶层线路再转,只要链路通,甚至可以直接访问到任何一个IPv6的地址,这样做非常危险,所以防火墙设置尤为重要,这里给出的是参考的配置

IPv6的NAT需要再3.7才加入支持,目前RPi的kernel是3.6.11。IPv6只允许向外访问以及相关的向内访问的话还是可以做到的

IPv6的iptables命令是ip6tables

ip6tables的设置参考了SixxS的wiki

IPv4

** filter

-A INPUT -i lo -j ACCEPT
-A INPUT -i eth0 -j ACCEPT # 来自LAN
-A INPUT ! -i eth0 -p udp -m udp --dport 67 -j REJECT --reject-with icmp-port-unreachable # 举例 非LAN的 udp 67端口的请求
-A INPUT -i ppp0 -p tcp -m tcp --dport 22 -j ACCEPT # 允许来自WAN 的 ssh请求
-A INPUT -i ppp0 -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT ! -i eth0 -p tcp -m tcp --dport 0:1023 -j DROP # 非LAN 的特殊权限端口
-A INPUT ! -i eth0 -p udp -m udp --dport 0:1023 -j DROP
-A INPUT -p tcp -m tcp --tcp-flags RST RST -j DROP # 忽略所有tcp reset


-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # 已经建立连接和相关的请求
-A FORWARD -s 10.0.0.0/24 -i tun0 -o ppp0 -m conntrack --ctstate NEW -j ACCEPT # 允许openvpn段访问公网
-A FORWARD -s 10.0.0.0/24 -d 192.168.5.0/24 -i tun0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT # 允许openvpn段访问LAN

# NAT用
-A FORWARD -d 192.168.0.0/16 -i eth0 -j DROP
-A FORWARD -s 192.168.0.0/16 -i eth0 -j ACCEPT
-A FORWARD -d 192.168.0.0/16 -i ppp0 -j ACCEPT
-A OUTPUT -o eth0 -j ACCEPT


** nat
# 基本都和这条类似,将公网tcp 32038 映射到内网这台IP的端口上

-A PREROUTING -i ppp0 -p tcp -m tcp --dport 32038 -j DNAT --to-destination 192.168.5.15:32038

IPv6 大致含义和上面类似,默认来自he6这个隧道的通信都是拒绝的,选择性地开放

-A INPUT -i he6 -p udp -j DROP
-A INPUT -i he6 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -i he6 -m state --state RELATED,ESTABLISHED -j ACCEPT # 允许已经连接和相关的请求
-A INPUT -i eth0 -j ACCEPT # 来自LAN的请求
-A INPUT -p ipv6-icmp -j ACCEPT # 允许ping6


-A FORWARD -i he6 -p udp -j DROP
-A FORWARD -i he6 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j DROP
-A FORWARD -s 2607:f8b0:400d:c04::/64 -i eth0 -o he6 -m state --state NEW -j ACCEPT # 允许出隧道的访问
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT

-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -o he6 -j ACCEPT
-A OUTPUT -o eth0 -j ACCEPT

DHCP

某是使用完整的DHCP功能,所以使用dhcpd

# /etc/dhcp/dhcpd.conf

subnet 192.168.5.0 netmask 255.255.255.0 {
  range 192.168.5.30 192.168.5.250;
  default-lease-time 259200;
  max-lease-time 518400;
  option subnet-mask 255.255.255.0;
  option broadcast-address 192.168.5.255;
  option routers 192.168.5.1;
  option domain-name-servers 192.168.5.1;
}

dnsmasq

这个做DNS的请求转发,还能一定时间缓存,传递的时候走openvpn后的IP这样请求无论是tcp还是udp都是加密的,预防DNS污染

strict-order # 严格按照配置顺序请求,而非传统的一起请求,采用先响应的结果

# 举例 可以给不同域名分配不同的DNS服务器,以及端口
server=/google.com/8.8.8.8
server=/stackoverflow.com/208.67.222.222#5353

polipo + supervisord

polipo是个简单而好用的http代理服务,支持上游是http或者socket代理,可以缓存加速;可惜的是不支持根据URL自动细分,这点我们可以通过PAC解决。偶尔出现的进程死掉的问题可以通过非daemon方式+supervisord来运行,这样也可以多启用几个实例来达到多个线路的支持

daemonise=false
proxyAddress=0.0.0.0
proxyPort=8123
proxyName=YourProxyName
serverSlots=4
serverMaxSlots=8
cacheIsShared=true
allowedClients=127.0.0.1,192.168.0.0/16
parentProxy=10.0.0.1:8123
logFile=/var/log/polipo.log
diskCacheRoot=/var/cache/polipo
dnsUseGethostbyname=true
maxDiskCacheEntrySize=1073741824

DDNS (dyndns.org + ddclient)

某使用的是dyndns的免费服务,提供一个host也就是域名,有API能更新IP;ddclient是一个现成的DDNS客户端更新程序

# /etc/ddclient/ddclient.conf

daemon=30       # 30秒检查一次,如果变化则更新
syslog=yes
mail=root
mail-failure=root
ssl=yes         # 走加密

use=if, if=ppp0 # 指定使用的出口

login=dyndns username
password=dyndns password

server=members.dyndns.org # dyndns 官方的服务器
protocol=dyndns2          # 指定协议
your.dyndns.org           # 指定要更新的域名

openvpn

远端主机(Linode以及PhotonVPS)都是支持IPv6的,并且慷慨大方地一次都给了4个的IPv6地址…

openvpn的代码在2.3后已经支持tun模块IPv6的连接,在连接proto那里指定tcp6即可

后话

Linode的机房似乎和HE处在同一个机房,ping6得到的延迟跟LAN差不多…而PhotonVPS在LA的机房在某家访问速度一向不错…有了IPv6干扰会少很多。折腾的意义还是相当大的。

及时只有一台PC的巨巨们也可以试着折腾来玩。DDWRT等路由固件都有这些能力,折腾起来应该也不费劲。

另外,UPnP这个服务暂时还没搞定,资料较多的linux-igd似乎很久没更新了,gentoo的overlay里没找到。另外找到的一个miniupnpd还未折腾。原理上是在iptables(或其他防火墙)里单独新建一个table出来,提供客户端UPnP的访问修改防火墙策略,达到UPnP的目的——按需要配置NAT;某使用的服务相对固定,所以手工设置NAT已经够用了(BT、eMule等)

__END__