首发于HomeNas
基于sshuttle构建穷人的虚拟专网

基于sshuttle构建穷人的虚拟专网

前言

当你创建了一个家用网络,里面有一堆可访问的设备的时候. 你可能想在外面随时随地都可以方便的访问自家的服务. 而sshutle就是这么一款穷人专用的虚拟专用网络方案. 前提是你的网络需要有一个公用IP. 同时你的家用的服务器通过SSH远程登陆. 满足这两个条件, 就可以使用 sshutle 实现远程回家.

远程回家方案对比

我们的家庭网络一般是有如下的典型拓扑结构, 通过光猫接入公网. 光猫需要是桥接方案工作. 然后把网络桥接到了路由器.然后使用路由器进行拨号. 这样路由器上就有了相应的公网IP. 然后家用设备通过路由器交换数据包完成互访.



我们的目标是在外部的时候能够方便访问家里的打印机,路由器,NAS设备. 以及其它智能家居的设备.比如摄像头. 下面我们看一下不同方案之间的差异. 基本上可以概括为两个方案: 跳板机 和 虚拟专用网络.

远程桌面

这个可能是最简单,最容易实现的方案. 大家平时也经常会用到的方案. 一方面是大家比较熟悉Windows. 另外一方面这个也足够的成熟. 不需要做额外的操作.方便好用.



以上是我简化后的远程桌面方案. 中间省去了其它的网络设备. 真正使用时可能会需要使用DMZ或者是端口绑定(转发)把这台Windows主机暴露到公网中. 使用的时候远程登陆到这台机器上,然后所有对内网资源的访问都在这到这台Windows电脑上完成. 所有的操作看起来都是在这台电脑上直接完成的一样. 正因为这样,由于这台电脑本身处于内网环境, 自然其也能直接访问到我们家里的所有网络设备. 只是所有的访问结果通过远程桌面的形式显示到了我们的本地计算机的屏幕上.

当然这个远程桌面并不只有这一种方案. 比如如果是Ubuntu系统,那你还可使用开源的VNC进行连接. 如果没有公网IP,那还可以使用第三方的服务进行远程桌面登陆. 比如 向日葵.

虚拟专用网络

以上方案有一点不好的是,我所有的操作都被代理到了这台windows主机上进行操作. 但是实际你想把数据,比如文件拷贝到你的本地机器的时候还要额外的一步操作. 而且这一步的操作可能还不好做.我们想要的效果是访问家里的网络设备时,在本机上能直接操作. 就像我们的电脑真的在家里的网络环境一样.



这个网络会在使用端虚拟一个网卡.然后给分配一个虚拟专用网络的IP地址. (图中是: 10.2.8.2) 这样本地笔记本就与家庭网络之间建立了一个虚拟专网,即我们的客户机就像真的是在家用网络内部一样. 所有的资源都可以直接访问. 不需要再通过远程桌面的跳板机进行访问. 当然这个最简单和最容易实现的就是使用开源方案了.

虚拟专网方案与远程桌面方案相比. 肯定是虚拟专网方案方便. 首先是安全, 虚拟专用网络都是加密流量. 而且虚拟专网让我们访问家庭网络就像是本地网络一样方便.没有任何使用上的额外的配置. 而远程桌面方案需要把远程桌面的机器的IP与端口直接暴露到公网上,这就有一定的安全风险. 特别是大家的机器都喜欢使用弱密码的情况下更是如此.

基于sshuttle的远程回家方案

如果使用过SSH登陆远程服务器的同学都知道, SSH实际上是具备端口转发的功能的. 这样也可以间接的把SSH server能访问的某个服务的端口映射到本地的某个端口进行访问.

具体可以参考: SSH端口转发详解及实例

比如我们家庭网络中有

  • 一个WIKI服务(WEB服务)
    • 机器名与IP分别是: l-websrv-wki-nas.cn1 (192.168.1.111)
    • 端口: 8080


  • 一个踏板机:
    • 机器名与IP: l-tiaobanji-nas.cn1 (192.168.1.101)
    • 端口: 22 (SSH端口)


此时以上两个机器都处于内网, 只有跳板机暴露了22端口. 此时我们可以通过如下命令进行把wiki服务的8080端口映射到本地的12345端口进行访问.

# 命令中的机器名可以使用IP直接代替.
ssh -NfL 12345:l-websrv-wki-nas.cn1:8080 root@l-tiaobanji-nas.cn1

在上面的命令执行后,就可以在访问本地的12345端口访问到wiki服务了. 这也就是所谓的端口转发,以下是网络拓扑的简化图:



通过以上的命令后,我们就能使用:localhost:12345 访问到家用网络中提供的wiki服务. 这个就是常用的端口转发功能. 我们这里使用的是本地端口转发的功能. 实际还有远程端口转发的功能.这个可以看上面提到的参考文档. 这里不展开说明.

这个方案固然好. 我们能够像访问本地资源一样访问到远程的家庭网络的资源 (就像上面说的localhost:12345). 如果要访问别的资源, 则需要再映射一个端口转发. 这时可能我们就可能使用如: localhost:23456 这样的地址访问到另外一组服务. 虽然能本地访问,但是还是有那么的一点不方便. 你得一个个的映射端口. 而且远程服务的名字和本地服务的名字实际也不一样(虽然你可以把他们做得尽可能的一样).

那有没有一种方案. 既像SSH这样可以方便的创建隧道,同时又不用一个个的去创建端口转发呢? 答案就是: sshuttle; 它就是基于ssh 隧道的一个类虚拟专用网络的应用.

以下是其介绍:

As far as I know, sshuttle is the only program that solves the following common case:
Your client machine (or router) is Linux, MacOS, FreeBSD, OpenBSD or pfSense.
You have access to a remote network via ssh.
You don’t necessarily have admin access on the remote network.
The remote network has no VPN, or only stupid/complex VPN protocols (IPsec, PPTP, etc). Or maybe you are the admin and you just got frustrated with the awful state of VPN tools.
You don’t want to create an ssh port forward for every single host/port on the remote network.
You hate openssh’s port forwarding because it’s randomly slow and/or stupid.
You can’t use openssh’s PermitTunnel feature because it’s disabled by default on openssh servers; plus it does TCP-over-TCP, which has terrible performance (see below).
摘自: sshuttle.readthedocs.io

sshuttle原理

sshuttle 是一个基于 ssh隧道的专用网络方案. 其只需要client端. 并不需要一个特定的虚拟专用网络server端. 其实现基于Python3, 要求 client端和server端都有python3的运行环境,且不低于 python3.8

怎么运行的

sshuttle 不完全是 V-P-N,也不完全是端口转发。两者兼而有之。

它就像一个 V-P-N,因为它可以转发整个网络上的每个端口,而不仅仅是您指定的端口。方便的是,它允许您使用每台主机的“真实”IP 地址,而不是在本地主机上伪造端口号。

另一方面,它的工作方式更像是 ssh 端口转发而不是 V-P-N。通常情况下,V-P-N 一次转发一个数据包,并不关心单个连接;比如,就流量而言,它是“无状态的”。sshuttle 与 stateless 相反;它跟踪每一个连接。

您可以将 sshuttle 与旧的Slirp程序之类的东西进行比较,它是一个执行类似操作的用户空间 TCP/IP 实现。但它在客户端逐个数据包地运行,在服务器端重新组装数据包。这在“真正的实时串行端口”时代还行得通,因为串行端口具有可预测的延迟和缓冲。

但是您不能安全地通过 TCP 会话(如 ssh)转发 TCP 数据包,因为 TCP 的性能从根本上取决于数据包丢失;它 必须经历丢包才能知道何时减速!同时,外部 TCP 会话(在本例中为 ssh)是一种可靠的传输方式,这意味着您通过隧道转发的内容 永远不会 丢失数据包。当然,ssh 会话本身会丢失数据包,但 TCP 会修复它,而 ssh(因此您)永远不会知道其中的区别。但是您的内部 TCP 会话也没有,并且随之而来的是极其棘手的性能。

sshuttle 在本地组装 TCP 流,通过 ssh 会话有状态地多路复用它,并在另一端将它分解回数据包。所以它永远不会以 TCP-over-TCP 作为技术终点。相反: 它只是data-over-TCP,所以这是安全的。

以上是官方文档的翻译, 我做了一个相应的连接图加以说明:



这里面实际并不需要一个看得见的agent. 这个agent只是在连接上ssh隧道后, client上传的脚本. 这个脚本就是python脚本. 然后client端就和这个python脚本的agent进行通信发送数据. 所有的对内部网络的访问都会被agent拆包后重新发起新的请求并发送到真正的目标机器上. 而我们的本地访问专用网络中的服务的时候,实际上都是由ssh server帮我们代发起的. 也就是好像是们是真的在 ssh server上进行数据收发一样. 这也是其data-over-TCP的原因. 其并不是直接在tcp上再创建了一个tcp.而是转发数据. 数据转发后由agent进行代发.

怎么安装

如果是macos. 可以使用如下命令进行安装:

brew install sshuttle

如果是debian:

sudo apt install sshuttle

如果是ubuntu

apt-get install sshuttle

如果是windows. 比较抱歉, 它没有提供原生支持.

怎么使用

最基本的,转发全部的流量. 无差别的进行所有的流量转发:

sshuttle -r username@sshserver 0.0.0.0/0

使用-r参数指定远程服务器. 更具体的参数格式为:

-r <[username@]sshserver[:port]>, --remote=<[username@]sshserver[:port]>

为了让本地服务与ssh server本身的通信不被重定向到sshuttle, 这个时候可以使用-x参数来排除一些重定向的子网:

-x <subnet>, --exclude=<subnet>

针对转发所有的子网写法: 0.0.0.0/0 有一个简写方法: 0/0 , 即:

sshuttle -r username@sshserver 0/0

如果想DNS也走sshserver被代理一次,则可以使用 --dns

sshuttle --dns -r username@sshserver 0/0

上面我们是把所有的网络请求都重定向到了sshserver.这个明显是有些过了, 我们只需要把发往家庭网络服务的请求重定向到 sshserver. 因此.我们可以指定一个特定的子网. 子网的格式如下:
a.b.c.d[/width][port[-port]]

  • 1.2.3.4 , 只转发一个单IP
  • 1.2.3.4/32 转发子网为: 255.255.255.255实际就是没有子网. 也就是IP本身: 1.2.3.4
  • 1.2.3.0/24 , 24bit子网. 等价于: 255.255.255.0 子网掩码
  • 0/0 , 匹配所有的IPV4地址
  • ::/0 , 匹配所有的IPV6地址.
  • 1.2.3.4:8000 只隧道传输 1.2.3.4目标iP地址的8000端口.
  • 1.2.3.0/24:8000-9000 , 会传输 1.2.3.x ip的 8000-9000端口.

综上, 如果不使用一个特殊的私有DNS的时候. 我们可以把内网地址: 192.168.1.x的网络请求进行转发.

sshuttle -r username@sshserver:22 192.168.1.1/24 -x 192.168.1.1:22

总结

以上基本实现我们的需求. 如果文中如果有不清楚的地方.由于某些原因可能省去了部分内容. 可以去我的博客(firfor.cn)查看完整版本.

  1. shuttle使用文档: sshuttle.readthedocs.io
  2. 源代码地址: github.com/sshuttle/ssh
编辑于 2023-03-12 23:38・IP 属地北京