序言

WireGuard 是最新一代开源 VPN 工具,相较于IPsec和OpenVPN等大多数古老魔法,具有易于配置、快速且安全的特点。

通过 WireGuard 用户无论是想提升上网体验,还是想在服务器之间组网都变得更加简单。从 2020 年 1 月开始,它已经并入了 Linux 内核的 5.6 版本,这意味着大多数 Linux 发行版的用户将拥有一个开箱即用的 WireGuard。

本文简单介绍一下 WireGuard 使用,以及基于 wireguard 的 kubernetes cni 插件 kilo。

1.WireGuard

WireGuard是基于C语言编写,简单说来具备以下特性:

  • 基于UDP协议
  • 核心部分:加密密钥路由
    • 公钥和 IP 地址列表(AllowedIPs)关联起来
    • 每一个wg接口都有一个私钥和一个 Peer 列表
    • 每一个 Peer 都有一个公钥和 IP 地址列表
  • 发送包时,AllowedIPs起到路由表的功能
    • 正常情况下,Peer的每个AllowedIPs,应该填写一条静态路由到wireguard设备!!!
  • 接收包时,AllowedIPs起到权限管理的功能:Packet的Source IP位于服务端的 AllowedIPs 列表时被接收,否则被丢弃

2.安装

由于WireGuard已经被和入了Linux内核,使用WireGuard时我们只需升级内核到5.6以上版本,并且安装客户端工具 wireguard-tools 即可,以下是在Centos7上的安装步骤:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
yum remove kernel-lt-*  kernel-tools kernel-tools-libs kernel-headers -y

rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install kernel-ml kernel-ml-* perf -y

grep "^menuentry" /boot/grub2/grub.cfg | cut -d "'" -f2
grub2-set-default <内核版本>
grub2-editenv list
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
1
2
3
4
5
6
7
8
yum install epel-release elrepo-release
yum install wireguard-tools #5.6以下版本的内核,可以安装 kmod-wireguard 

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
# 这里不要打开proxy_arp,在板瓦工等云主机上这个配置可能为被平台判定为arp flood攻击
# 参考:什么是https://www.cnblogs.com/Widesky/p/10489514.html攻击
# echo "net.ipv4.conf.all.proxy_arp = 1" >> /etc/sysctl.conf 
sysctl -p /etc/sysctl.conf

3.配置

安装完成后,可以使用ip link命令在服务器上添加 wireguard 类型的虚拟网卡。不同服务之间可以通过该虚拟网卡进行通信。

1
2
3
ip link add dev wg0 type wireguard
# 通过 IP 命令来指定 wg0 设备的IP,而不在配置文件中通过Address参数去指定(否则 wg setconf 会失败)
ip address add dev wg0 10.200.200.2/24  

上述命令在服务器上创建了设备 wg0 并配置地址为 10.200.200.2/24。

用户通过以下命令生成配置,并启动wg0网卡。其中 /etc/wireguard/privatekey 和 /etc/wireguard/publickey 文件是 wg0 的密钥/公钥,局域网中不同 wg0 之间通过交换公钥实现连接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
mkdir -p /etc/wireguard
umask 077
wg genkey > /etc/wireguard/privatekey
wg pubkey < /etc/wireguard/privatekey > /etc/wireguard/publickey

cat << EOF > /etc/wireguard/wg0.conf
[Interface]
ListenPort = 51820
PrivateKey = <Server_PrivateKey>
EOF

wg setconf wg0 /etc/wireguard/wg0.conf 
# 启动wg0 网卡
ip link set up dev wg0

添加以下IP规则(PS:有必要的话写入 /etc/sysconfig/iptables)

1
2
3
4
5
iptables -I INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I FORWARD -i wg0 -o wg0 -m conntrack --ctstate NEW -j ACCEPT
iptables -t nat -I POSTROUTING -s 10.200.200.0/24 -o em1 -j MASQUERADE # 这一条规则很重要,假设我们需要创建的局域网为 10.200.200.0/24 
iptables -I INPUT  -p udp -m multiport --dport 51820 -j ACCEPT

4. WireGuard 创建局域网

场景1

Server A:有公网IP Server B:位于局域网中无公网IP,但可以正常访问 Server A (wg0 地址配置为 10.200.200.11)

实现:Server A 可以通过 WireGuard 创建的虚拟网卡访问 Server B

Server A 上 WireGuard 配置文件如下:

1
2
3
[Peer]
AllowedIPs =  10.200.200.11/32 # 
PublicKey = <Server_B_PublicKey>

Server B 上 WireGuard 配置文件如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Interface]
PrivateKey = <Server_B_PrivateKey>
Address = 10.200.200.11/24

[Peer]
PublicKey = <Server_A_PublicKey>
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1, ::/1, 8000::/1
Endpoint = <Server A 公网IP>:51820
PersistentKeepalive = 25

场景2

在以下机器之间创建子网10.200.200.0/24,包含以下机器:

  • 10.200.200.2 :Server A 具有公网IP
  • 10.200.200.47:Server B 无公网IP(局域网所在网段为172.24.135.0/24)
  • 10.200.200.11:Server C 无公网IP(与Server B位于不同局域网)

实现:

  • 使子网之间的 VPN IP 可以相互访问
  • ~~Server C 可以访问172.24.135.0/24的其他机器,~~实现Server B和Server C可以相互访问

Server A 作为中继服务器配置如下:

1
2
3
[Peer]
AllowedIPs =  10.200.200.47/32, 172.24.135.0/24
PublicKey = <Server_B_PublicKey>

并添加以下路由规则:

1
2
3
4
# 中继服务器上要手动添加以下静态路由规则:
ip route add 172.24.135.0/24 via 0.0.0.0 dev wg0
# 或者以下方式添加
route add -net 172.24.135.0/24 netmask 255.255.255.0 wg0

Server B配置如下:

1
2
3
4
5
6
7
8
9
[Interface]
ListenPort = 51820
PrivateKey = 10.200.200.47/24

[Peer]
PublicKey =  <Server_A_PublicKey>
AllowedIPs =  10.200.200.0/24
Endpoint =  <Server A 公网IP>:51820
PersistentKeepalive = 25

Server C配置如下:

1
2
3
4
5
6
7
8
9
[Interface]
PrivateKey =  
Address = 10.200.200.11/24 # VPN 网络中该Client的IP

[Peer]
PublicKey =  <Server_A_PublicKey>
AllowedIPs = 10.200.200.0/24,172.24.135.0/24
Endpoint =  <Server A 公网IP>:51820
PersistentKeepalive = 25

kilo

将Kubernetes 和 WireGuard 结合的CNI插件:Kilo,该插件有以下能力。

1. CNI网络插件

Kilo 可以替代Flannel、Calico等常规网络插件,为Pod提供集群内的网络IP,此种模式下我们可以实现:

  • 将多个不同网络的机器,加入到同一个K8S中进行管理,而这些节点不一定需要有稳定的公网IP(PS:这种能力非常适合一些IoT、边缘计算场景)~~

限制条件:

  • 所有节点必须安装WireGuard(建议直接将系统内核升级到5.6以上版本)
  • 所有节点必须提供一个可访问的UDP端口(默认是51820)
  • Kilo在不同Region之间创建网络,需要知道节点的确切位置。Kubernetes Node 可以使用 topology.kubernetes.io/region为每个Node打标签,或者为每个Node添加kilo.squat.ai/location annotation
  • 每个Region中的至少一个节点必须是其他Node可访问的,即每个Region中要有一个公网IP,可用用 kilo.squat.ai/force-endpoint指定这个endpoint的访问地址

关于Kilo的组网细节参考官方文档:topology

2. 附加网络工具

Kilo为K8S网络提供功能扩展,当使用Flannel为网络插件,并且以vxlan模式工作时,可以通过以下方式安装kilo。

1
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/master/manifests/kilo-kubeadm-flannel.yaml 

Kilo在每台主机的宿主机网络运行,启动后会进行以下操作:

  • 为每台宿主机创建一张wireguard,名称为kilo0

  • 在每台宿主机上创建对应的iptables规则,PS:但是宿主机上51820的UDP端口依然要手工创建Iptable规则

  • 为每台机器创建Key和配置文件,并且持久化在**/var/lib/kilo**目录

  • 默认创建的VPN子网为10.4.0.0/16,可以通过-subnet指定

  • 根据**–mesh-granularity**配置,Kilo创建的网络拓扑有所不同:

    • location:这种模式下一个Region中只有一个Node工作,该节点称为Master用户可以手动指定,也可以由Kilo自动选择。

    • full:所有节点加入到VPN网络中,并且相互称为Peer。此时kilo0设备会替代flannel.1设备,拦截所有发到POD IP的包,并根据WireGurad配置文件的allowedIPs信息发送到相应节点!

3. 存在问题

  • 修改kilo的配置会影响flannel的路由配置,此时需要重启flannel容器来恢复路由
  • 区域路由模式时(–mesh-granularity=location),出现多个node绑定同一个VPN IP(原因可能是抢夺Leader时,没有清理设备信息)。
  • 一些配置修改不方便需要通过Annotations:https://github.com/squat/kilo/blob/master/docs/annotations.md

4. 参考配置

作为附加网络工具时,实现场景二的功能,参考一下配置:

  • ds配置(不影响flannel原有网络):
1
2
3
4
5
6
7
    - --kubeconfig=/etc/kubernetes/kubeconfig
    - --hostname=$(NODE_NAME)
    - --cni=false
    - --compatibility=flannel
    - --local=false
    - --mesh-granularity=location 
    - --encapsulate=never # 永远不拦截flannel的流量
  • ds配置nodeSelector只部署一个节点

  • Node打对应标签

1
2
3
4
5
6
7
8
  annotations:
    kilo.squat.ai/endpoint: 172.24.135.45:51820                     # 自动生成的
    kilo.squat.ai/internal-ip: 10.190.0.0/32                        # 自动生成的
    kilo.squat.ai/key: ++8u2w7NPb5CzIk47IHTx11L3Dw9OzvRmPSzDkU+cQ0= # 这个是公钥
    kilo.squat.ai/last-seen: "1613143193"
    kilo.squat.ai/leader: "true"                                    # 可以不设置
    kilo.squat.ai/persistent-keepalive: "25"                        # 一定要设置
    kilo.squat.ai/wireguard-ip: 10.4.0.1/16                         # 自动生成,没有测试能不能手动指定
  • Peer 配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: kilo.squat.ai/v1alpha1
kind: Peer
metadata:
  name: goblin-peer
spec:
  allowedIPs:
  - 10.4.0.1/32  # k8s 上VPN 网络
  - 10.200.200.0/24 # 公网服务端的 VPN 网络
  endpoint:  # 公网服务端的endpoint
    dns: goblin.lqingcloud.cn
    port: 51820
  persistentKeepalive: 10 # 没有用不生效
  publicKey: 
  • 公网中继服务器配置

    1
    2
    3
    
    [Peer]
    AllowedIPs =  10.4.0.1/32,172.24.135.0/24
    PublicKey = 
    
  • 公网添加静态路由

    1
    2
    
    route add -net 172.24.135.0/24 netmask 255.255.255.0 wg0
    route add -net 10.4.0.0/16 netmask 255.255.255.0 wg0
    

WGSD

wgsd(Github)是基于CoreDNS插件实现的Wireguard Peer 发现插件,用来实现 NAT to Nat 的Peer直连。

该工具能够正常工作基于以下前提:

  • Peer 双方的NAT出口能够接受任意主机发送网络包

wgsd的原理如下:

  • 部署一个 WireGuard Peer 作为注册中心,该中心有一个稳定公网IP,所有NAT后的Peer主动连接到注册中心。
  • 注册节点上部署CoreDNS插件,插件基于DNS-SRV来发布注册中心上的所有Peer信息
  • NAT Peer 上配置所有 Nat Peer 的公钥,而不配置 EndPoint
  • 每个Nat Peer 上有定时运行的wgsd-client程序,该程序根据Peer的公钥去DNS上查询其对应EndPoint,并更新到本地。

上述 WireGuard Peer 直连的方案实际上是基于nat 打洞来工作的,并非在所有环境都能成功。

通常情况下NAT包括以下四种工作模式:

  • Full cone NAT:一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送,任意外部主机都能通过给eAddr:port2发包到达iAddr:port1。
  • Address-Restricted cone NAT:一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。任意外部主机(hostAddr:any)通过给eAddr:port2发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:any. “any"也就是说端口不受限制
  • Port-Restricted cone NAT:在Address-Restricted cone NAT的前提下,任意外部主机(hostAddr:port3)通过给eAddr:port2发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:port3
  • Symmetric NAT:对每个外部主机或端口的会话都会映射为不同的端口。

实际场景中,设备可能工作在多层NAT之后,处在一种混合模式的场景下,除非是一些云服务商专门提供的NAT主机,否则上面这个工具基本没什么卵用。

NAT打洞是P2P技术中非常重要的内容,可以参考以下资料学习:

【P2P技术详解】

结论

当前github上有许多基于WireGuard的有趣开源项目,包括以下:

最后,本文是从个人的学习笔记中整理而来的。没有经过详细验证,可能存在错漏之处,请各方大佬见谅。

参考

WireGuard 教程:WireGuard 的工作原理

WireGuard 教程:WireGuard 的搭建使用与配置详解

kilo

非官方文档