这次呢我们接着上一次 串口上网实验 的文章来继续深入。上一篇我们提到PPP封装可以做的工作远不止有在串口上建立数据链路这么简单,更重要的是可以在任何能够传输数据的信道上再建立一层数据链路的底层封装。因此这次我们希望在TLS安全层上建立一个信道,并在上面建立一个PPP连接,实现类似VPN的功能。由于俺学艺不精,因此要分很多前置工作慢慢迭代,希望一步步的迭代能逐步做到最后想要的成果。
基于TCP流的PPP封装
首先我们要做的就是想在TCP流上实现一个基本的PPP封装。由于pppd是对串口、网卡这样的流设备进行操作的,而我们的TCP流在Linux中不能表示为一个能用echo或cat进行操作的文件,所以必须用一些间接的手段。
题外话,在Linux中,Socket虽然也存在相应的文件,但对Socket的读写使用的不是文件接口而是专用的网络接口,因此二者不能混为一谈。另外,bash提供了一个方便的接口发送TCP消息:echo xxx > dev/tcp/{ip}/{port}
,这条命令会向tcp://ip:port发送一条xxx的消息,但这个只是bash的语法糖,并不是说Linux实际存在这样的设备。
不过我们有一个强大的软件可以实现这样的功能:socat。它可以在不同的数据之间转换,包括将tcp数据传输到一个虚拟终端文件上。这样的功能恰好是我们需要的,我们可以用熟悉的接口来解决复杂的问题了。
首先在两台机器上安装socat:sudo apt install socat
服务器: socat -d -d tcp-listen:15000 pty
树莓派: socat -d -d tcp:192.168.2.6:15000 pty
这里,-d -d
是让socat输出详细的调试信息,这里的目的是显示出socat分配的虚拟IO设备名;tcp-listen:15000
是在15000建立一个tcp的监听端口;tcp:192.168.2.6:15000
就是使用TCP协议连接到这个地址;pty
是请求一个虚拟的终端设备连接到左边的流上面。
socat实际上就是将两个不同的流连接在一起。这里就是将网络接口的TCP和文件接口的PTY连接在一起。
下面我们在两端建立PPP连接。
1 | ~ > socat -d -d tcp-listen:15000 pty |
可以看到为服务器分配的虚拟终端是/dev/pts/4,因此设备就是这个了。树莓派的类似,这里是/dev/pts/2
服务器: sudo pppd -detach /dev/pts/4 192.168.5.1:192.168.5.2 noauth nocrtscts local
树莓派: sudo pppd -detach /dev/pts/2 noauth nocrtscts local
提前输入好命令,两端按下回车(因为pppd的超时默认很少,时间太长会断掉),可以看到两端已经拿到了IP,再打开终端相互ping,如果ping通证明链路已经完好了。
稍微修改pppd命令,通过nodeflate nobsdcomp
关掉压缩功能后进行测速,发现速度仅能达到50Mbps和100Mbps的非常不对称的速率。相比直连情况下树莓派的300Mbps网卡还是有很大差距的,但相比上一篇的串口速率已经是一个质的飞跃了。
稍微修改socat命令,如果换成udp协议建立的原始信道,那么速度更是变成了只有56Mbps/85Mbps这样的速率,局域网环境下应该不存在针对UDP的限速行为,因此发生这样的情况还是较为令人费解的。可能是iperf本身的问题,但换成iperf3后并没有结果的改变。
TLS流的建立
TLS是一种基于证书体系的安全系统,其可以提供更安全更可靠的流式数据传输。我们仍然采用socat工具来帮助实现TLS流的建立。
生成自签名证书
我们需要的自签名证书涉及2个文件:保存密钥的key文件和保存加密过的证书信息的crt文件。然而,socat需要的并不单纯是这两个文件:它需要的是证书文件crt以及一个key和crt文件的打包文件。
密钥文件: openssl genpkey -algorithm Ed25519 -out ca.key
,其中,algorithm是指定密钥文件的算法。常用的算法有RSA,ECDSA和EdDSA。这里用的便是EdDSA算法的Ed25519曲线。EdDSA算法的好处是相比RSA,其生成key的速度更快,性能更好,占用的空间更小,相比ECDSA,由于使用的曲线安全性能更好,因此更加具有实用价值。
证书文件: openssl req -new -key ca.key -x509 -days 3650 -out ca.crt
,这时,机器会要求你回答几个问题,大部分的问题都可以随意回答,但是Common Name这项必须是你用于访问这台服务器的IP地址或域名。因为如果服务器的IP或域名和证书中Common Name项的内容不同,那么客户端就会拒绝连接,保证安全性。
打包文件: cat ca.key ca.crt > ca.pem
,我们使用cat命令将ca.key和ca.crt两个文件简单地连接在一起即可。
接下来我们要将生成的证书传输到另一台机器上面,保证两边的证书是完全相同的,才能连接成功。
建立TLS流
下面我们就可以用socat命令来建立TLS流了,过程和建立TCP流类似。但首先我们还要切换到刚刚保存证书的那个目录下。
服务器: socat -d -d openssl-listen:15000,cert=ca.pem,cafile=ca.crt pty
树莓派: socat -d -d openssl:192.168.2.6:15000,cert=ca.pem,cafile=ca.crt pty
现在,屏幕上应该已经显示了两台机器分配的PTY,这表明已经成功建立了TLS数据流。
如果将服务器和树莓派的命令对调,也就是让服务器去连接树莓派的话,由于刚刚提到客户端会对连接的地址进行验证,因此反过来是会连接失败的,而且现象是客户端首先断开连接,服务端并没有显示错误信息。但如果我们生成证书时使用的是域名,那么只要这个域名可以指向那台机器,同时那台机器拥有证书文件,客户端就会认为这就是正确的服务器从而连接。
建立TLS流的PPP封装
有了上面的基础,建立基于TLS流的PPP封装已经是呼之欲出了。保持刚刚建立的TLS流,输入以下命令:(更改为你的/dev/pts/x)
服务器: sudo pppd -detach /dev/pts/8 192.168.5.1:192.168.5.2 noauth nocrtscts local
树莓派: sudo pppd -detach /dev/pts/6 noauth nocrtscts local
现在就应该可以互相ping通对方了,至此我们已经成功建立了TLS流上的PPP封装。
速度测试
同样地,我们在pppd中更改服务器的选项,用nodeflate nobsdcomp
命令关掉压缩,以便进行更准确的速度测试,得到了40Mbps/68Mbps的速度。相比TCP流又下降了不少,大概是TLS本身传输加密数据本身数据量较大导致的。
除了基于TCP的TLS流外,还有基于UDP的DTLS流,但是由于Socat直到一个月前才放出支持DTLS的新版本,我这台机器上还没有安装,因此只能等待后续更新了。
实现NAT
既然我们要建立一个VPN,那么必不可少的就是能让树莓派使用服务器的网络进行上网。这意味着我们不能再像上一篇文章那样通过修改网关路由表这样大动干戈的方式来实现树莓派的网络访问了。当然,除了NAT以外,还可以通过ARP Proxy的方式实现类似的效果,但ARP Proxy需要有一个空余IP,仍然和网关有关系,因此这里仍然采用NAT的办法。
我们做两个NAT:一个是Source NAT,用于树莓派访问外部网络时的映射;另一个是Destination NAT,用于外部网络访问树莓派的SSH,也就是端口转发。因此下面的操作都是在服务器上进行的。
首先我们允许正常的路由转发:
1 | sudo iptables -A FORWARD -d 192.168.5.0/24 -j ACCEPT |
接下来实现SNAT,注意更换网卡的名称eth0为你自己的
1 | sudo iptables -t nat -A POSTROUTING -s 192.168.5.0/24 -o eth0 -j MASQUERADE |
最后实现DNAT,就可以用其他的电脑访问树莓派了,这里是将192.168.2.6:15001转发到了192.168.5.2:22
1 | sudo iptables -t nat -A PREROUTING -p tcp -d 192.168.2.6 --dport 15001 -j DNAT --to-destination 192. |
当然,经过了这样的操作树莓派还是没有能够利用这条线路上网。我们还需要重新配置以下默认路由。以下在树莓派上面操作:
首先把服务器的IP设置为通过原有的网卡:
1 | sudo route add 192.168.2.6 gw 192.168.2.1 dev eth0 |
接下来设置默认的路由
1 | sudo route add -net default gw 192.168.5.1 dev ppp0 |
现在我们用sudo traceroute -I www.baidu.com
测试,可以看到路由首先经过了192.168.5.1,表明数据是经过这个安全的隧道传输的。同样地,在其他的电脑上通过访问服务器的IP也可以转发到树莓派的SSH上,实现了安全隧道传输的VPN效果。
总结
可以说,我们这个土制VPN还是非常粗糙的,可用性较差但不失正确性。通过该VPN我们可以隐藏自己真实要访问的网站,所有的流量都通过虚拟的隧道进行转发,同时,隧道本身也采用了安全的TLS。代理ARP的功能虽然没有实现,但是NAT也达到了更好的效果。实际上,真正的VPN还应该拥有动态IP池等等功能,但那是不断演进的结果了。
这篇文章是 串口上网实验 的一个衍生探究,更多集中在如何在一个已经建立好的信道上进行更加低级的数据传输。正如标题所说,整个过程建立了一个“VPN”,可以利用目标电脑的网络环境来进行上网。当然,我们也可以继续深入下去其中的很多问题:为什么建立的信道上下行出现了严重的速度不对等现象?如何提高信道的利用率达到更高的速度?不使用ppp封包,在Windows下如何实现这样的功能(Windows下的wsl不支持ppp功能)?下一步可能是希望在信道连接建立后传输IPv6的数据,毕竟PPP是个很古老的协议了。