码迷,mamicode.com
首页 > 其他好文 > 详细

WireGuard

时间:2020-12-14 13:50:02      阅读:7      评论:0      收藏:0      [点我收藏+]

标签:tld   字母   emc   根据   comm   xxxxxx   ext   gossip   isa   

WireGuard

来源 https://www.cnblogs.com/ryanyangcs/p/13253212.html

 

原文链接:https://fuckcloudnative.io/posts/wireguard-docs-theory/

WireGuard 是由 Jason Donenfeld 等人用 C 语言编写的一个开源 威屁恩 协议,被视为下一代 威屁恩 协议,旨在解决许多困扰 IPSec/IKEv2Open威屁恩 或 L2TP 等其他 威屁恩 协议的问题。它与 Tinc 和 MeshBird 等现代 威屁恩 产品有一些相似之处,即加密技术先进、配置简单。从 2020 年 1 月开始,它已经并入了 Linux 内核的 5.6 版本,这意味着大多数 Linux 发行版的用户将拥有一个开箱即用的 WireGuard。

无论你是想破墙而出,还是想在服务器之间组网,WireGuard 都不会让你失望,它就是组网的『乐高积木』,就像 ZFS 是构建文件系统的『乐高积木』一样。

WireGuard 与其他 威屁恩 协议的性能测试对比:

技术图片

可以看到 WireGuard 直接碾压其他 威屁恩 协议。再来说说 Open威屁恩,大约有 10 万行代码,而 WireGuard 只有大概 4000 行代码,代码库相当精简,简直就是件艺术品啊。你再看看 Open威屁恩 的性能,算了不说了。

WireGuard 优点:

  • 配置精简,可直接使用默认值
  • 只需最少的密钥管理工作,每个主机只需要 1 个公钥和 1 个私钥。
  • 就像普通的以太网接口一样,以 Linux 内核模块的形式运行,资源占用小。
  • 能够将部分流量或所有流量通过 威屁恩 传送到局域网内的任意主机。
  • 能够在网络故障恢复之后自动重连,戳到了其他 威屁恩 的痛处。
  • 比目前主流的 威屁恩 协议,连接速度要更快,延迟更低(见上图)。
  • 使用了更先进的加密技术,具有前向加密和抗降级攻击的能力。
  • 支持任何类型的二层网络通信,例如 ARPDHCP 和 ICMP,而不仅仅是 TCP/HTTP。
  • 可以运行在主机中为容器之间提供通信,也可以运行在容器中为主机之间提供通信。

WireGuard 不能做的事:

  • 类似 gossip 协议实现网络自愈。
  • 通过信令服务器突破双重 NAT。
  • 通过中央服务器自动分配和撤销密钥。
  • 发送原始的二层以太网帧。

当然,你可以使用 WireGuard 作为底层协议来实现自己想要的功能,从而弥补上述这些缺憾。

本系列 WireGuard 教程分为两个部分,第一部分偏理论,第二部分偏实践。本文是第一部分,下面开始正文教程。

1. WireGuard 术语

Peer/Node/Device

连接到 威屁恩 并为自己注册一个 威屁恩 子网地址(如 192.0.2.3)的主机。还可以通过使用逗号分隔的 CIDR 指定子网范围,为其自身地址以外的 IP 地址选择路由。

中继服务器(Bounce Server)

一个公网可达的对等节点,可以将流量中继到 NAT 后面的其他对等节点。Bounce Server 并不是特殊的节点,它和其他对等节点一样,唯一的区别是它有公网 IP,并且开启了内核级别的 IP 转发,可以将 威屁恩 的流量转发到其他客户端。

子网(Subnet)

一组私有 IP,例如 192.0.2.1-255 或 192.168.1.1/24,一般在 NAT 后面,例如办公室局域网或家庭网络。

CIDR 表示法

这是一种使用掩码表示子网大小的方式,这个不用解释了。

NAT

子网的私有 IP 地址由路由器提供,通过公网无法直接访问私有子网设备,需要通过 NAT 做网络地址转换。路由器会跟踪发出的连接,并将响应转发到正确的内部 IP。

公开端点(Public Endpoint)

节点的公网 IP 地址:端口,例如 123.124.125.126:1234,或者直接使用域名 some.domain.tld:1234。如果对等节点不在同一子网中,那么节点的公开端点必须使用公网 IP 地址。

私钥(Private key)

单个节点的 WireGuard 私钥,生成方法是:wg genkey > example.key

公钥(Public key)

单个节点的 WireGuard 公钥,生成方式为:wg pubkey < example.key > example.key.pub

DNS

域名服务器,用于将域名解析为 威屁恩 客户端的 IP,不让 DNS请求泄漏到 威屁恩 之外。

2. WireGuard 工作原理

中继服务器工作原理

中继服务器(Bounce Server)和普通的对等节点一样,它能够在 NAT 后面的 威屁恩 客户端之间充当中继服务器,可以将收到的任何 威屁恩 子网流量转发到正确的对等节点。事实上 WireGuard 并不关心流量是如何转发的,这个由系统内核和 iptables 规则处理。

如果所有的对等节点都是公网可达的,则不需要考虑中继服务器,只有当有对等节点位于 NAT 后面时才需要考虑。

在 WireGuard 里,客户端和服务端基本是平等的,差别只是谁主动连接谁而已。双方都会监听一个 UDP 端口,谁主动连接,谁就是客户端。主动连接的客户端需要指定对端的公网地址和端口,被动连接的服务端不需要指定其他对等节点的地址和端口。如果客户端和服务端都位于 NAT 后面,需要加一个中继服务器,客户端和服务端都指定中继服务器作为对等节点,它们的通信流量会先进入中继服务器,然后再转发到对端。

WireGuard 是支持漫游的,也就是说,双方不管谁的地址变动了,WireGuard 在看到对方从新地址说话的时候,就会记住它的新地址(跟 mosh 一样,不过是双向的)。所以双方要是一直保持在线,并且通信足够频繁的话(比如配置 persistent-keepalive),两边的 IP 都不固定也不影响的。

Wireguard 如何路由流量

利用 WireGuard 可以组建非常复杂的网络拓扑,这里主要介绍几个典型的拓扑:

① 端到端直接连接

这是最简单的拓扑,所有的节点要么在同一个局域网,要么直接通过公网访问,这样 WireGuard 可以直接连接到对端,不需要中继跳转。

② 一端位于 NAT 后面,另一端直接通过公网暴露

这种情况下,最简单的方案是:通过公网暴露的一端作为服务端,另一端指定服务端的公网地址和端口,然后通过 persistent-keepalive 选项维持长连接,让 NAT 记得对应的映射关系。

③ 两端都位于 NAT 后面,通过中继服务器连接

大多数情况下,当通信双方都在 NAT 后面的时候,NAT 会做源端口随机化处理,直接连接可能比较困难。可以加一个中继服务器,通信双方都将中继服务器作为对端,然后维持长连接,流量就会通过中继服务器进行转发。

④ 两端都位于 NAT 后面,通过 UDP NAT 打洞

上面也提到了,当通信双方都在 NAT 后面的时候,直接连接不太现实,因为大多数 NAT 路由器对源端口的随机化相当严格,不可能提前为双方协调一个固定开放的端口。必须使用一个信令服务器(STUN),它会在中间沟通分配给对方哪些随机源端口。通信双方都会和公共信令服务器进行初始连接,然后它记录下随机的源端口,并将其返回给客户端。这其实就是现代 P2P 网络中 WebRTC 的工作原理。有时候,即使有了信令服务器和两端已知的源端口,也无法直接连接,因为 NAT 路由器严格规定只接受来自原始目的地址(信令服务器)的流量,会要求新开一个随机源端口来接受来自其他 IP 的流量(比如其他客户端试图使用原来的通信源端口)。运营商级别的 NAT 就是这么干的,比如蜂窝网络和一些企业网络,它们专门用这种方法来防止打洞连接。更多细节请参考下一部分的 NAT 到 NAT 连接实践的章节。

如果某一端同时连接了多个对端,当它想访问某个 IP 时,如果有具体的路由可用,则优先使用具体的路由,否则就会将流量转发到中继服务器,然后中继服务器再根据系统路由表进行转发。你可以通过测量 ping 的时间来计算每一跳的长度,并通过检查对端的输出(wg show wg0)来找到 WireGuard 对一个给定地址的路由方式。

WireGuard 报文格式

WireGuard 使用加密的 UDP 报文来封装所有的数据,UDP 不保证数据包一定能送达,也不保证按顺序到达,但隧道内的 TCP 连接可以保证数据有效交付。WireGuard 的报文格式如下图所示:

技术图片

关于 WireGuard 报文的更多信息可以参考下面几篇文档:

WireGuard 的性能

WireGuard 声称其性能比大多数 威屁恩 协议更好,但这个事情有很多争议,比如某些加密方式支持硬件层面的加速。

WireGuard 直接在内核层面处理路由,直接使用系统内核的加密模块来加密数据,和 Linux 原本内置的密码子系统共存,原有的子系统能通过 API 使用 WireGuard 的 Zinc 密码库。WireGuard 使用 UDP 协议传输数据,在不使用的情况下默认不会传输任何 UDP 数据包,所以比常规 威屁恩 省电很多,可以像 55 一样一直挂着使用,速度相比其他 威屁恩 也是压倒性优势。

技术图片

关于性能比较的更多信息可以参考下面几篇文档:

WireGuard 安全模型

WireGuard 使用以下加密技术来保障数据的安全:

  • 使用 ChaCha20 进行对称加密,使用 Poly1305 进行数据验证。
  • 利用 Curve25519 进行密钥交换。
  • 使用 BLAKE2 作为哈希函数。
  • 使用 HKDF 进行解密。

WireGuard 的加密技术本质上是 Trevor Perrin 的 Noise 框架的实例化,它简单高效,其他的 威屁恩 都是通过一系列协商、握手和复杂的状态机来保障安全性。WireGuard 就相当于 威屁恩 协议中的 qmail,代码量比其他 威屁恩 协议少了好几个数量级。

关于 WireGuard 加密的更多资料请参考下方链接:

WireGuard 密钥管理

WireGuard 通过为每个对等节点提供简单的公钥和私钥来实现双向认证,每个对等节点在设置阶段生成密钥,且只在对等节点之间共享密钥。每个节点除了公钥和私钥,不再需要其他证书或预共享密钥。

在更大规模的部署中,可以使用 Ansible 或 Kubernetes Secrets 等单独的服务来处理密钥的生成、分发和销毁。

下面是一些有助于密钥分发和部署的服务:

如果你不想在 wg0.conf 配置文件中直接硬编码,可以从文件或命令中读取密钥,这使得通过第三方服务管理密钥变得更加容易:

 
  [Interface]
  ...
  PostUp = wg set %i private-key /etc/wireguard/wg0.key <(cat /some/path/%i/privkey)

从技术上讲,多个服务端之间可以共享相同的私钥,只要客户端不使用相同的密钥同时连接到两个服务器。但有时客户端会需要同时连接多台服务器,例如,你可以使用 DNS 轮询来均衡两台服务器之间的连接,这两台服务器配置相同。大多数情况下,每个对等节点都应该使用独立的的公钥和私钥,这样每个对等节点都不能读取到对方的流量,保障了安全性。

 

===============

Linux Kernel 5.6 包含的 WireGuard

来源 https://zhuanlan.zhihu.com/p/147377961

 

为什么需要虚拟专用网

当我们需要在家中连上公司网络修复线上 bug 的时候,当拉不起专线的创业公司需要连接两个机房的服务器的时候,都需要用到虚拟专用网络1(Virtual Private Network)技术。

我们可以看一下阿里云,开启最便宜的一个 IPSec 虚拟专用网网关最低也需要 375 元一个月,而且还必须按固定带宽付费,除了用公司网络看电影日志,谁用的上 5M 的带宽啊。对企业来说,这可能不算什么,但是对个人来说,我自己的虚机一个月都用不了 375,虚拟专用网网关却这么贵,阿里云简直是坑一个算一个。而我们在自己的服务器上搭一个虚拟专用网的话,费用是 ¥0/月。

技术图片

Introducing WireGuard

WireGuard 是新一代的虚拟专用网协议,相比于 IPSec 和 OpenXXX 等软件,特点是简单安全高效,总共只有不到四千行代码,由于过于优秀,已经被吸收进了 Linux 5.6+ 的内核中。

代码体积对比:

技术图片

WireGuard 和其他虚拟专用网的速度对比:

技术图片

一图胜千言,可以说是相当出色了吧~

鉴于我密码学学得不是很好,所以本文主要聊聊工程实现和使用,对加密算法的选择不做展开,我们假定 WireGuard 作者做了很好的选择 :P

1. Virtual Private Network 一般情况下是缩写成三个字母的,但是鉴于中文互联网中,该技术常用于突破网络封锁,所以本文中使用全称以避免被河蟹吃掉。另外,本文只讨论协议本身,如果用于突破网络封锁,请责任自负。

工作原理

WireGuard 以 UDP 实现,但是运行在第三层 —— IP 层。每个 Peer 都会生成一个 wg0 虚拟网卡,同时服务端会在物理网卡上监听 UDP 51820 端口。应用程序的包发送到内核以后,如果地址是虚拟专用网内部的,那么就会交给 wg0 设备,WireGuard 就会把这个 IP 包封装成 WireGuard 的包,然后在 UDP 中发送出去,对方的 Peer 的内核收到这个 UDP 包后再反向操作,解包成为 IP 包,然后交给对应的应用程序。

WireGuard 实现的虚拟网卡就像 eth0 一样,可以使用标准的 Linux 工具操作,像是 ipifconfig 之类的命令。所以 WireGuard 也就不用实现 QoS 之类的功能,毕竟其他工具已经实现了。这也很符合 Unix 哲学——Do one thing and do it well.

为什么 WireGuard 不实现自己的第四层协议而要用 UDP 呢?答案很现实,因为长久以来人们在第四层只有 TCP 和 UDP 两个协议,所以大多数的路由器配置都是只接受这两个协议,其他的包一律按错误处理。实际上因为这个原因,包括 http/3 在内的很多协议现在也都只能在 UDP 上实现。还好 UDP 仅仅是 IP 上一层非常薄的封装,性能损失也不大。

WireGuard 实现方式是内核模块,所以上面所说的解包封包转发等操作都是在内核实现的,基本不需要什么复制。相对而言,OpenXXX 这种在用户层实现的协议就需要在内核和用户空间之间拷贝来拷贝去,对性能是硬伤。

WireGuard 没有使用复杂的 TLS 机制,它的每个 Peer 都需要预先生成一对密钥(公钥和私钥),WireGuard 不负责密钥的分发工作。就像是 ssh 的密钥需要你自己想办法放到服务器上一样。

我们知道像是 TLS 这种协议支持各种各样的加密方式,一不留神就会出现兼容性问题。WireGuard 的解决方式也很简单,那我们只支持一种足够好的加密方式就得了,也就避免了配置问题。不得不说,这种方式确实是简单粗暴,也确实省了不少事儿。或许你会觉得这样不够灵活,但是过早优化不正是万恶之源么。

也正因为免去了协议协商、加密协商、密钥协商的过程(毕竟 WireGuard 已经帮我们做了选择),所以 WireGuard 得以实现在一个 RTT 内实现链接建立。不过在负载较重的时候,WireGuard 会要求 2 RTT 的握手。

我们知道非对称的加密是很耗费计算资源的,所以和大多数协议一样, WireGuard 也会在链接建立之后使用对称加密,这就需要在握手阶段协商出一个对称的密钥来。实际上,WireGuard 使用了两个密钥,一个用来发送,一个用来接收。

实际上 IPSec 这些复杂的握手协议让我想到了已经被废弃的 http digest 加密。虽然因为把人绕晕了,看起来很安全,但是实际上并不是完全安全,反倒是防君子不防小人。。

为了避免消息重放攻击,WireGuard 的第一个包中携带了一个加密过后的时间戳,如果收到了攻击者重放的消息,直接打开后丢弃就行了,WireGuard 不会做出任何反应。

限于篇幅,这里不展开 WireGuard 的定时器等细节了。

路由表

技术图片

由于每个节点都有自己的公钥和私钥,实际上就建立了公钥和节点之间的一一映射。当 wg0 需要向外发送的包的时候,会查这个表来找到正确的公钥。当接收到一个外部的包的时候,会根据公钥来检查是否是合法的目标 IP。

当一个 Peer 发过来一个包,并且通过了验证之后,路由表就会记录下对方的外部地址,这样当需要给对方发包的时候,就可以直接通过这个地址发送了。同时,如果对方切换了网络,那么路由表就会跟着新的包更新,也就是 WireGuard 支持「漫游」, 就像是 mosh 一样。

这个路由表是用 radix tree(压缩字典树)实现的。

当一个 Peer 想把自己的所有流量都通过 WireGuard 的网路的时候,应该把 AllowedIP 设成 0.0.0.0/0(后面会讲到)。

连接过程

技术图片

如左图所示,在正常情况下,WireGuard 只需要一个 RTT,两次握手就会建立连接状态,然后就开始传输数据了。在握手的过程中交换了对称密钥,并且在路由表中生成了对方地址。

当响应方负载过高的时候,会要求发起方再验证一次 Cookie 才接受这个请求,以避免 DDoS 攻击。

关于报文的链接建立的具体细节这里就不展开了,有时间了单独写一篇文章,或者你可以直接看 WireGuard 的论文就好了。。

安装和使用

由于我们现在使用的内核普遍还在 4.x,所以还是需要安装下 WireGuard 的。以 Ubuntu 18.04 为例:

sudo add-apt-repository ppa:wireguard/wireguard
sudo apt-get update
sudo apt-get install wireguard resolvconf

安装完成后,我们就得到了两个新命令:wg 和 wg-quick。其中 wg 是基础命令,wg-quick 是一个封装好的实用工具,实际上,他会调用 wg 和 iptables 等工作。

网络规划

我们使用 10.100.0.0/16 作为虚拟专用网的子网网段。服务器所在的内网网段为:172.17.0.11/20。服务器的公网 IP 为 1.1.1.1

WireGuard 的原理是生成一个虚拟网卡,一般来说我们叫他 wg0,然后通过这个虚拟网卡通信。

WireGuard 的配置文件是 ini 风格的,每个配置文件都有 [Interface] 和 [Peer] 两个部分。

其中 Interface 就是自身的接口配置,比如说对于服务器来说,需要配置 ListenPort,对于服务端和客户端来说都需要配置自己的 Address。Privatekey 填写自己的私钥内容。

Peer 这部分就比较有意思了,Peer 指的是 WireGuard 连接的另一端,对于服务器来说,Peer 就是客户端,而对于客户端来说,Peer 自然就是服务器。一个配置文件中可以有多个 Peer,这样的话,服务端就可以有多个客户端连接了。Peer 中可以指定 AllowedIPs,也就是允许连接的 IP。比如说,客户端指定了 10.100.0.0/16,172.17.0.11/20,那么就只有这部分 IP 的流量会通过虚拟专用网路由,而如果指定了 0.0.0.0/0,那么就是所有的流量通过虚拟专用网了。所以如果你只是想访问内网,也就是远程办公的话,那么可以选择前一种方法,如果是想通过国外主机访问某些受限的信息的话,当然要使用后一种了。Publickey 填写对方的公钥内容。

服务器配置

首先,生成 WireGuard 两对公钥和私钥,分别是服务器和客户端的:

wg genkey | tee server_privatekey | wg pubkey > server_publickey
wg genkey | tee client_privatekey | wg pubkey > client_publickey

这时候就得到了 privatekey 和 publickey 这四个文件。

wireguard 的配置都在 /etc/wireguard 目录下,在服务器上编辑 /etc/wireguard/wg0.conf 并输入以下内容。

[Interface]
Address = 10.100.0.1/16  # 这里指的是使用 10.100.0.1,网段大小是 16 位
SaveConfig = true
ListenPort = 51820  # 监听的 UDP 端口
PrivateKey = < 这里填写 Server 上 privatekey 的内容 >
# 下面这两行规则允许访问服务器的内网
PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# Client,可以有很多 Peer
[Peer]
PublicKey = < 这里填写 Client 上 publickey 的内容 >
AllowedIPs = 10.100.0.2/32  # 这个 Peer 只能是 10.100.0.2
# 如果想把所有流量都通过服务器的话,这样配置:
# AllowedIPs = 0.0.0.0/0, ::/0

然后,我们可以使用 wg-quick up wg0 启动 wg0 这个设备了。

通过 wg show 可以看到:

-> % sudo wg show
interface: wg0
  public key: xxxxxx
  private key: (hidden)
  listening port: 51820

peer: xxxxx
  allowed ips: 10.100.0.2/32

客户端配置

在客户端新建文件,可以放到 /etc/wireguard/wg0.conf 中,也可以随便放哪儿,用客户端读取就行。我这里使用的是 Mac 官方客户端,在 Mac AppStore 里可以找到。

[Interface]
PrivateKey = < 这里填写 Client 上 privatekey 的内容 >
Address = 10.100.0.2/32
DNS = 8.8.8.8  # 连接后使用的 DNS, 如果要防止 DNS 泄露,建议使用内网的 DNS 服务器

[Peer]
PublicKey = < 这里填写 Server 上 publickey 的内容 >
Endpoint = 1.1.1.1:51820  # 服务端公网暴露地址,51280 是上面指定的
AllowedIPs = 10.100.0.0/16,172.17.0.11/20  # 服务器可以相应整个网段以及服务器的内网
PersistentKeepalive = 25

我们在客户端中点击导入,然后选择刚刚的文件,然后就可以连接啦:

技术图片

可以使用内网地址(10.100.0.1)访问,当然可以使用 172.17.0.11:

技术图片

添加新的客户端

还是执行 wg genkey | tee client_privatekey | wg pubkey > client_publickey 生成新的客户端私钥和公钥。

然后在 /etc/wireguard/wg0.conf 中添加新的 [Peer]

[Peer]
PublicKey = < 这里填写 Client 上 publickey 的内容 >
AllowedIPs = 10.100.0.3/32  # 这个 Peer 只能是 10.100.0.3

注意这里我们使用了新的 IP 地址。然后更新一下服务端:

sudo wg-quick strip wg0 > temp.conf
sudo wg syncconf wg0 temp.conf

这里我们使用了 wg-quick strip 命令,wg0.conf 中的一些指令是 wg-quick 才能使用的,而不是 wg 原生的配置。

不过有时候可能还是不太好用,这时候不用急,重启一下机器总会解决的。

然后生成新的客户端:

[Interface]
PrivateKey = < 新的 private key>
Address = 10.100.0.3/32

# 其他的和原来保持不变

注意其中,我们只更新了 PrivateKey 和 Address 部分

创建服务

在服务器上,我们可以使用 systemd 开启 WireGuard 服务,重启后就不用再配置了。

sudo systemctl enable wg-quick@wg0.service

DNS 配置

我们可以使用 dnsmasq 作为虚拟网络内部的 DNS,这里不再展开,可以查看 dnsmasq 的相关文档。

在上面的配置中,我们使用了 8.8.8.8 作为 DNS,这一步其实很关键,否则的话你访问的网址其实还是被 DNS 查询泄露了。

参考

  1. Donenfeld, Jason. (2017). WireGuard: Next Generation Kernel Network Tunnel. 10.14722/ndss.2017.23160.
  2. ***/

 

============= End

 

WireGuard

标签:tld   字母   emc   根据   comm   xxxxxx   ext   gossip   isa   

原文地址:https://www.cnblogs.com/lsgxeva/p/14105764.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!