FuckWall

使用 tinc 搭建虚拟局域网

自从用上了移动宽带,就一直处于大局域网中,无法直接连接家里的设备,只能通过 frp 进行内网穿透。

后来移动普及了 IPv6,终于可以通过 IPv6 地址连接家里的设备。但过完年后,从国外到家里的 IPv6 连接一直被阻断在骨干网的出口节点,而国内到家里又能正常连接。

在这种奇葩的情况下,我本想继续用回 frp 方案,但群友建议组个虚拟局域网使用起来会更加方便,于是就有了这次的折腾。


tinc or ZeroTier?

在开始之前,我先了解了一下使用比较多的两种组网工具:tinc 和 ZeroTier。

tinc

  • 网状网络,任意两个节点之间可以直连,如果无法连接,则自动通过一个或多个中间节点转发;
  • 开源,私有部署比较方便;
  • 无图形化配置界面,需要手动编辑配置文件,且需要手动分发配置;
  • Switch 模式下工作在 L2 层,Router 模式下工作在 L3 层;
  • 支持通过 TCP 或 UDP 进行连接;
  • 单线程运行,性能比较一般,有测试表明传输速度约为裸 HTTP 传输速度的 1/10。

ZeroTier

  • 任意两个节点之间需要通过根节点来建立直连,如果无法连接,则自动通过根节点转发;
  • 开源,可自建 planet 节点以实现私有部署;
  • 图形化配置界面,可以通过网页进行配置,并自动分发配置;
  • 仅支持通过 UDP 进行连接;
  • 有免费的官方 planet 节点,但是连通性和速度都不太理想;
  • 官方免费版最多可接入 25 个节点;
  • 名词解释:
    • planet:行星节点,官方的根服务器;
    • moon:卫星节点,用户自建的私有根服务器,可中转加速行星节点;
    • leaf:叶子节点,每台连接到该网络的设备。

因为 ZeroTier 在配置上的便利性,我最开始考虑使用它来组网,并且考虑到免费版限制节点数量,外加手里有一台国内的公网 VPS,所以我打算私有部署 ZeroTier。

但在实际部署中,我发现了一个最致命的问题:配置 planet 节点时不支持使用域名,必须使用 IP 地址,而我那台 VPS 却是动态 IP 的。这直接导致我不得不放弃使用 ZeroTier 而改用 tinc。

网络规划

network-topology

网络拓扑图如上,本次组网将在各个 VLAN 节点上部署 tinc,最终实现效果如下:

  • 所有 VLAN 和 LAN 节点之间互通且尽可能直连,如果无法直连,则通过 Cloud_A 或 Cloud_B 转发;
  • VLAN 节点可通过 192.168.1.0/24 地址连接 LAN 节点;
  • LAN 节点可通过 10.0.0.0/24 地址连接 VLAN 节点。

安装

各大 Linux 发行版基本都有 tinc 包,可以直接使用包管理器安装。目前 tinc 的最新稳定版是 1.0.36,另有 1.1pre18 的预发行版可选,以下步骤默认使用 1.0.36 版本。

pacman -S tinc # Arch Linux
yum install tinc # CentOS/RHEL
apt install tinc # Debian/Ubuntu
opkg install tinc # OpenWrt

配置

tinc 默认配置文件目录为 /etc/tinc,典型的目录结构如下:

/etc/tinc
├── <net_1> # 网络名称,用于 tinc 的启动参数
   ├── hosts # 节点配置文件列表,每个节点一个文件
   │   ├── <host_1>
   │   ├── <host_2>
   │   ├── ...
   │   └── <host_n>
│   ├── <private_key> # 本节点的私钥
│   ├── tinc.conf # 本节点的网络配置文件
│   ├── tinc-down # tinc 启动时调用该脚本
│   └── tinc-up # tinc 停止时调用该脚本
├── <net_2>
├── ...
└── <net_n>

以下步骤中,配置文件仅列出本次组网所需的配置项,完整的配置项可参考:

OpenWrt 节点

  • tinc.conf

    Name = OpenWrt # 本节点名称
    Interface = tinc # 网卡名称
    Mode = router # 工作模式,Android 版本不支持 switch 模式,将会无法连接
    ConnectTo = Cloud_A # 本节点将尝试主动连接到 Cloud_A 节点
    ConnectTo = Cloud_B
  • tinc-up

    #!/bin/sh
    
    ip link set $INTERFACE up
    
    # 本节点的 IP 地址
    ip addr add 10.0.0.1/24 dev $INTERFACE
    
    # 来自 192.168.1.0/24 段、且去往 10.0.0.0/24 段的包,修改其源地址为 10.0.0.1(OpenWrt)
    iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 10.0.0.0/24 -j SNAT --to-source 10.0.0.1
  • tinc-down

    #!/bin/sh
    
    iptables -t nat -D POSTROUTING -s 192.168.1.0/24 -d 10.0.0.0/24 -j SNAT --to-source 10.0.0.1
    ip addr del 10.0.0.1/24 dev $INTERFACE
    ip link set $INTERFACE down
  • hosts/OpenWrt

    Subnet = 10.0.0.1/32 # 分配 10.0.0.1 给本节点
    Subnet = 192.168.1.0/24 # 开放 192.168.1.0/24 段给所有节点

Cloud_A、Cloud_B 节点

Cloud_A 和 Cloud_B 节点的配置类似,以下是 Cloud_A 节点的配置。

  • tinc.conf

    Name = Cloud_A # 本节点名称
    Interface = tinc # 网卡名称
    Mode = router # 工作模式,Android 版本不支持 switch 模式,将会无法连接
  • tinc-up

    #!/bin/sh
    
    ip link set $INTERFACE up
    
    # 本节点的 IP 地址
    ip addr add 10.0.0.2/24 dev $INTERFACE
    
    # 去往 192.168.1.0/24 段的包发往 10.0.0.1(OpenWrt) 处理
    ip route add 192.168.1.0/24 via 10.0.0.1 dev tinc
  • tinc-down

    #!/bin/sh
    
    ip route del 192.168.1.0/24 via 10.0.0.1 dev tinc
    ip addr del 10.0.0.2/24 dev $INTERFACE
    ip link set $INTERFACE up
  • hosts/Cloud_A

    Address = <ip_or_domain> # 本节点的公网地址,用于被其他节点连接
    Port = <port_number> # 本节点的监听端口,默认为 655
    Subnet = 10.0.0.2/32 # 分配 10.0.0.2 给本节点

创建密钥

配置完成后,需要在各个节点上分别创建密钥,用于连接时鉴权。创建过程中会提示公钥和私钥的保存路径,保持默认直接回车即可。

tincd -n VLAN -K # 生成 VLAN 网络的密钥,使用 RSA 算法

# 如果是 1.1pre18 版本
tinc -n VLAN generate-rsa-keys # 使用 RSA 算法
# 或
tinc -n VLAN generate-ed25519-keys # 使用 Ed25519 算法

创建完成后,/etc/tinc/vlan 路径下会生成 rsa_key.priv 私钥文件,并且在 /etc/tinc/vlan/hosts 目录下的本节点配置文件中会附加公钥信息。

Address = <ip_or_domain>
Port = <port_number>
Subnet = <subnet>

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA2QmhvEzTnZmRsAVGwwvSFI/ib84tRIGwg7Nh8sudanS4HuIUnkm7
g5osJ44TDMnYu5D+gmsZnukDdUAp/sNzDj6p/uE1RKLYDjEaLs9MOkCI2NcxJ9t5
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
elzSGrotqIxGde4qSC6zUm9rsqogeiR1OqeV5h8S33/gHgjG0C0fPuAEeHdum8Ea
mTvujtRMbyht+zMUcJhcuiDA/w2UJ2B6uwIDAQAB
-----END RSA PUBLIC KEY-----

分发配置

在本次组网中,Cloud_A 和 Cloud_B 节点作为中间节点,需要在其他节点无法互相直连时接受连接,因此需要将这两个节点的配置文件分发到其他节点。

同时,OpenWrt 节点在无法与其他节点直连时,需要通过 Cloud_A 和 Cloud_B 节点转发,因此也需要将 OpenWrt 节点的配置文件分发到这两个节点。

最终各节点的配置文件目录结构如下:

/etc/tinc
└── VLAN
    ├── hosts
    │   ├── OpenWrt
    │   ├── Cloud_A
    │   └── Cloud_B
    ├── rsa_key.priv
    ├── tinc.conf
    ├── tinc-down
    └── tinc-up

最后需要给 tinc-uptinc-down 脚本添加可执行权限。

chmod +x /etc/tinc/VLAN/tinc-up
chmod +x /etc/tinc/VLAN/tinc-down

至此,所有 Linux 节点的配置已经完成。

启动和测试

启动 tinc 服务:

systemctl start tinc@VLAN

如果需要开机自动启动,必须启用 tinc 全局服务和 tinc@VLAN 服务:

systemctl enable tinc
systemctl enable tinc@VLAN

测试:

# 在 OpenWrt 节点上
ping 10.0.0.2 # 测试 Cloud_A 节点
ping 10.0.0.3 # 测试 Cloud_B 节点

# 在 Cloud_A 节点上
ping 10.0.0.1 # 测试 OpenWrt 节点
ping 192.168.1.2 # 测试 OpenWrt 节点下的 PC 节点

# 在 Cloud_B 节点上
ping 10.0.0.1 # 测试 OpenWrt 节点
ping 192.168.1.2 # 测试 OpenWrt 节点下的 PC 节点

如果都能正常 ping 通,说明组网成功。如果有节点无法 ping 通,可以停止 tinc 服务后,使用 tincd -n VLAN -d 5 -D 命令以调试模式启动 tinc,查看日志信息。

报错的具体原因和说明可参见:Error messages

Android 节点

Android 版本的 tinc 可以在 Tinc App 上下载安装。

打开 Tinc App 后,点击右上角“配置”按钮,选择“生成节点配置和密钥”,填写网络名称和节点名称。

创建完成后,点击上方的“FTP access”开启 FTP 服务,然后用任何可用的 FTP 客户端连接并下载 files 目录,该目录结构如下:

files
└── networks
    └── VLAN # 网络名称
        ├── ed25519_key.priv # 本节点的私钥
        ├── hosts # 节点配置文件列表,每个节点一个文件
        │   └── Phone # 本节点的配置文件
        ├── network.conf # 本节点的网卡配置文件
        ├── rsa_key.priv # 本节点的私钥
        └── tinc.conf # 本节点的网络配置文件

接下来修改各文件:

  • tinc.conf

    Name = Phone # 本节点名称
    ConnectTo = Cloud_A # 本节点将尝试主动连接到 Cloud_A 节点
    ConnectTo = Cloud_B
  • hosts/Phone

    -----BEGIN RSA PUBLIC KEY-----
    MIIBCgKCAQEAwMchKIonLqZnlTxvPZjRqzQIZPu4bgFveYEnuBPj4uJH0L44NYOp
    8KRGiNc+/JrUXeEPsLGe7hXw4cq2OkM7REUKGurXhY0utBRU+xHUsZQq3UOPOgWz
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    udN2aP01cUUPP2oG+kRJVdQlqDhzvJSckDga7qFEiqu/9dNIOJ0RWQozTfM5Yp8/
    hh2WVjMSrvrvqlUiEaGkLDWUV61mLAVKGwIDAQAB
    -----END RSA PUBLIC KEY-----
    Ed25519PublicKey = 1JpfoY/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxd2+K
    Port = <port_number> # 本节点的监听端口,创建节点配置时已自动生成
    Subnet = 10.0.0.4/32 # 分配 10.0.0.4 给本节点
  • network.conf

    # network.conf: Tinc App network interface configuration file.
    # Keys can be repeated to specify multiple values.
    # Doc: https://tincapp.pacien.org/doc.html#network-interface
    
    # IP address of this node
    Address = 10.0.0.4/32
    
    # Traffic to route through the VPN
    Route = 10.0.0.0/24
    
    # 效果类似于 `ip route add 192.168.1.0/24 via 10.0.0.1`
    Route = 192.168.1.0/24
    # or, to route everything:
    #Route = 0.0.0.0/0
    
    # DNS server(s)
    #DNSServer = 10.0.0.1
    
    # Search domain(s) to associate to the DNS resolver(s)
    #SearchDomain = mynet
    
    # Applications allowed or disallowed to use the VPN connection
    #AllowApplication = com.example.app
    # or
    #DisallowApplication = com.example.app
    
    # Allow applications to bypass the VPN connection
    #AllowBypass = false
    
    # Automatic reconnection on network change
    #ReconnectOnNetworkChange = true

与 Linux 平台下分发配置一样,需要将 Cloud_A 和 Cloud_B 节点的配置文件分发到本节点,也需要将本节点的配置文件分发到 Cloud_A 和 Cloud_B 节点。

最终 files 目录结构如下:

files
└── networks
    └── VLAN
        ├── ed25519_key.priv
        ├── hosts
           ├── Phone
           ├── Cloud_A
        │   └── Cloud_B
        ├── network.conf
        ├── rsa_key.priv
        └── tinc.conf

通过 FTP 将 files 目录上传并覆盖原文件夹,返回主界面后就可以看到“连接到网络”列表下列出了 VLAN 网络,点击该网络即可连接。连接后可以查看网络和节点的状态信息,也可点击右上角“显示日志”按钮查看具体的日志信息。

后记

切换到虚拟局域网后,各节点之间互连方便了很多。尤其对于那台国内的公网 VPS,再也不用担心服务商限制的可用端口数量不够。

后续可以设置若干个域名解析到 10.0.0.0/24 段的各节点 IP 地址,这样就可以通过域名访问各节点了,更加方便。

二〇二三十二