南门口技术指北

SaaV - SSH as a VPN

前言

假如你有一个小型局域网,比如办公室,里面只有一台机器有固定的公网 IP 地址,如何使用已有设施快速让自己回到家也可以愉快地享受福报?

这次我们团队的清华大佬将介绍如何使用 OpenSSH 实现搭建 VPN ,并通过这个过程学习 Linux 下的 TUN/TAP 设备的基本知识以及相关的网络知识,对于进一步了解 Calico 等 SDN 的实现也会有帮助。

Overview

如图所示,在办公室有多台机器通过交换机组成一个小型局域网,网段为 10.10.0.0/16 。其中一台机器我们叫做 server ,它拥有固定的公网 IP 地址,可以从家里面使用 SSH 登录。

设置完成以后,家里的机器将会有一块虚拟网卡 tun0 ,该网卡拥有地址 10.10.0.200 ,家里的机器就像连入了办公室的网络环境一般可以正常与办公室内的机器通信。当然,实际这么操作并不会降低两者之间的延迟。

怎么实现的

首先,建立虚拟连接。

在家里的机器上执行 `sudo ssh -w 0:0 $SERVER_IP` 登录 server ,登录完成后家里的 SSH client 会在本地创建一个虚拟网卡设备 tun0 ,同时在 server 上面也会由 sshd 进程创建对应的虚拟网卡设备 tun0 。

需要注意的是,为了拥有权限创建虚拟网卡设备,我们需要使用 root 用户登录,所以在 server 的 ssh 配置中需要有如下配置:

PermitRootLogin yesPermitTunnel yes

登录成功后,可以分别在家里的机器和 server 上面执行 `ip addr show tun0` 来确认是否正确创建了虚拟网卡设备。

然后,配置虚拟网卡。

现在我们需要给两块虚拟网卡配地址,假定 10.10.0.100/32 和 10.10.0.200/32 在办公室尚未被别的机器使用,我们将把这两个地址分别配置给 server 和家中的机器。

在家里的机器执行:

ip link set tun0 upip addr add 10.10.0.200/32 peer 10.10.0.100 dev tun0

在 server 上执行:

ip link set tun0 upip addr add 10.10.0.100/32 peer 10.10.0.200 dev tun0

这里可以留意下配置地址的时候我们需要同时指定 peer ,这是因为我们的虚拟网卡在数据链路层使用的是 Point to Point Protocol 。

配置好两端的地址后,我们可以简单进行个测试,在家里执行 ping 10.10.0.100 应该可以看到 ping 是通畅的。

接着我们要配置路由规则。 现在我们需要让家里 的机器知道 10.10.0.0/16 的 IP 报文应该走 tun0 发送。

执行 ip route add 10.10.0.0/16 via 10.10.0.200 。这条路由规则将会让家里的机器把到 10.10.0.0/16 的 IP 报文走 tun0 发送,假设我们有一个 IP 报文 {src: 10.10.0.200, dst: 10.10.120.23} 在家里的机器上需要发送,内核检查路由表后发现目标 IP 10.10.120.23 匹配路由规则 10.10.0.0/16 via 10.10.0.200 ,于是内核将 IP 报文由 tun0 发出。

配置完路由表后,开启 server 的 IP 转发功能。

在 {src: 10.10.0.200, dst: 10.10.120.23} 经由家中的 tun0 发出后在 server 的 tun0 上被接收到,默认情况下 server 检查发现目标 IP 不是自己的 IP 于是丢弃掉该 IP 报文。我们需要让 server 充当一个路由器帮我们把 IP 报文转发到办公室的内网。通过在 server 上执行 echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward 开启 IP 转发功能。开启 IP 转发功能以后, server 收到我们的 IP 报文后检查自己的路由表,发现目标 IP 由自己的 eth0 可达,于是执行 ARP 获得主机 10.10.120.23 的 MAC 地址,然后走 eth0 发出 IP 报文,该报文的源 IP 和目的 IP 不变。

最后, 让 server 响应 ARP 请求。

至此,主机 10.10.120.23 已经可以正确收到我们从家里的机器发出的 IP 报文,现在需要解决的问题是让主机 10.10.120.23 能够将 IP 报文发给家里的机器。假设主机 10.10.120.23 要发送 IP 报文 {src: 10.10.120.23, dst: 10.10.0.200} ,内核查其路由表得知目标 IP 应该是 eth0 所在的网段,于是在 eth0 上面广播 ARP request 要求获取 10.10.0.200 的 MAC 地址。因为 server 已经开启了 IP 转发功能,所以只要 server 能够响应该 ARP 请求让主机 10.10.120.23 把 IP 报文发给 server 。server 即可以经由 tun0 转发 IP 报文到我们家里的机器。

在 server 上执行命令 sudo arp -sD 10.10.0.200 eth0 pub 将会让 server 在 eth0 上以 eth0 的 MAC 地址响应对于 10.10.0.200 的 ARP 请求。如此主机 10.10.120.23 发送的 IP 报文将会被 server 在 eth0 上收到,由于开启了 IP 转发,server 会将该 IP 报文经由 tun0 发送到我们家中的机器。

One thing more 关于 TUN/TAP

TUN/TAP 是内核提供的虚拟网络设备,程序里面我们可以通过 open("/dev/net/tun") 打开一个 TUN 设备,然后使用 ioctl(TUNSETIFF) 向内核注册该设备。之后应用程序就可以通过 read/write 调用来与该设备交互,TUN 设备的话应用程序读写的是 IP 报文,而 TAP 设备的话应用程序读写的是以太网帧。与 SSH Tunnel 不同的在于,这种方式更加的底层,纯粹是路由和 ARP 作用。

所以我们上面介绍的方法,SSH 在背后做的事情大致是:在家里的机器打开并注册一个 TUN 设备,读取该设备上的 IP 报文经由 TCP 连接发送到 server ,server 接收到 IP 报文后写入到 server 的 TUN 设备,逆向过程类似。具体关于如何使用 TUN 我写了一个简单的演示项目:https://github.com/dlee-libo/tun-bridge 有兴趣的话可以参考。