Windows VPS上部署Ngrok服务器,实现多平台客户端(树莓派)SSH内网穿透

场景需求:家里的几台树莓派通过家用WIFI路由器上网,虽然装了Teamviewer可以远程穿透内网控制图形界面,但远程时屏幕分辨率太小,体验不佳,于是想让树莓派上的SSH也能拥有穿透内网的功能。

前言,关于Ngrok

查阅了很多资料,发现大多数内网穿透功能的实现都与一款Go语言编写的Ngrok项目相关,Ngrok是一整套的服务器+客户端解决方案。因为其是由Go语言编写的,所以天生具备很强的跨平台能力(但目前基本上所有资料都指向在Linux上部署该服务器端)。博主使用的是阿里云的Windows VPS,并且这段时间正好也在使用Go编程,因此这篇文章将具体介绍如何在Windows上部署Ngrok项目,并且在家用树莓派上部署客户端,实现树莓派的SSH内网穿透。

关于Ngrok,值得注意的有以下几点:

  • Ngrok并不仅仅用来22端口的SSH内网穿透,通过简单配置,它可以将遵循TCP/UDP协议的多个端口进行内网穿透。
  • Ngrok是由一台服务器端和一个或者多个客户端组成的体系。
  • Ngrok需要一台部署在公网固定IP上的服务器,最好有可正常指向的域名,Ngrok的服务器端就部署在上面,如本次实验的阿里云主机。
  • Ngrok的客户端装在需要映射的各个没有公网IP的机器上,比如本次实验的树莓派。
  • Ngrok服务器会在接受到Ngrok客户端请求时分配固定或者随机的端口号,将客户端请求的端口与之映射,从而达到内网穿透的目的。
  • 用户在远程使用SSH客户端进行连接时,不需要任何Ngrok程序,对终端用户来说,Ngrok是透明的。
  • Ngrok自身有一台欧洲的公网服务器,供没有VPS的用户注册使用,该服务器并不是以分配端口的方式映射,而是以分配子域名的方式,随机子域名是免费使用的,固定子域名映射是收费的。
  • 普通玩家可以直接使用Ngrok的官网服务器,缺点就是有一点点慢,而且每次客户端重启后子域名会随之变化,无法固定使用。
  • 国内也有一些商家提供了Ngrok服务,比如Sunny-Ngrok,同样是收费的,速度可能快一点。

Ngrok源代码是开源的,部署在Git上,但是博主使用下来略觉繁琐,涉及到很多自认为没必要使用的技术,后期我重新在Git上发布了我重构后的Ngrok项目,配有码云同步镜像,以供其他朋友直接使用诸如go build或者go install简单命令进行跨平台编译和安装。

为了致敬原作者,我们对这些高端的Golang编程技术做一个简单的了解,对Go语言没有兴致的朋友可以直接跳过下面这段:

  • go-bindata 技术,该技术是一套将任意资源文件转成二进制数据反向生成到Go代码供hash调用的技术,Ngrok的源码利用这一技术将公网证书和密钥,以及站点的HTML生成到go代码中去了,使其变成了一堆byte数组,在程序中hash调用。这么做固然有它的好处,但缺点也显而易见,因为这套技术要提前用一个项目的程序生成另一个项目的Go代码,所以编译起来难免繁琐,于是Ngrok提供了makefile文件以供用户make编译,这就有点尴尬了,本来好好的跨平台Go语言项目,被make和makefile活生生憋成了只能Linux使用的了。博主初期使用时硬着头皮安装了Cygwin,以及其中的make命令,个中的苦逼无以言表,索性后来了解原理后,将官方的Ngrok重构了一遍,避免了使用几乎是专属于Linux的make指令。
  • build -tags 技术,通过Ngrok项目,我第一次知道Go的这个特性,可以在代码顶端加上// +build !release等标记,注明编译时的模式,比如前面这种表达方式,就是在非release编译模式下才会编译这个文件,然后使用go build -tags 'release'或者go build -tags 'debug'来控制是否编译这个代码文件。这个特性确实灵活,可以在项目中编写不同的编译模式下的代码,缺点呢,就是没有详细文档的情况下,第三方用户并不知道有哪些编译模式,加大了源码本地编译的复杂度,所以这也是原版Ngrok项目使用makefile文件的原因之一。
  • equinox.io 技术,这项技术可以让Go代码在编译和运行期间保持更新,代码作者更新代码后,能够在第三方用户实时更新,具体没有了解,我看原版Ngrok在进行release编译时增加了很多equinox.io令牌密钥,以连接远程仓库,保证代码更新。

总之,上面的这些特性,虽然优点多多,但使用起来难免加大了难度,并且牺牲了Go优良的跨平台特性,这是我不能忍的,于是我重新构造了Ngrok项目,旨在让更多玩家,在众多平台上简单轻松的编译Ngrok项目。

言归正传,准备工作

下面我们言归正传,从零开始部署Ngrok,说说准备工作:

硬件部分

  • 一台公网IP服务器,本实验采用Windows系统,拥有域名hexcode.cn。
  • 一台装有RASPBIAN操作系统的树莓派,用来部署Ngrok客户端,使其可以SSH内网穿透
  • 一台Windows PC,开发环境

软件部分

  • Win32 OpenSSL v1.1.0f Light证书生成程序
  • MobaXterm几乎是最好的SSH客户端
  • go1.9.2.windows-amd64.msiGo语言编译器,我现在用得最多的语言,一股子的亲切感,交叉编译Linux,ARM,X86等平台的目标程序就跟玩一样
  • Ngrok源码这是我自己重构的Ngrok项目,原版的要想用起来还得安装Cygwin等一堆工具,现在非常纯净,拿到手直接用最简单的命令编译,各平台通吃。这份代码不需要手工download,下面会介绍直接用命令获取。
  • Git-windows-amd64这个就不赘述了,程序猿都知道。

从零开始操作

证书生成

Ngrok需要使用SSL证书确保穿透过程中的通信是加密安全的,因此需要SSL证书,值得庆幸的是并不强制去购买CA认证机构颁发的证书,普通用户直接使用OpenSSL工具就可以自行生成证书。方法如下:

  • 下载上述准备工作软件部分的SSL证书生成程序,安装后,将安装路径下的bin目录添加到环境变量,方便cmd直接运行openssl.exe
  • 任意路径打开cmd,执行下面命令:(注意,将两处MY_DOMAIN全部替换成自己的域名,比如我的hexcode.cn)
    openssl genrsa -out rootCA.key 2048
    openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=MY_DOMAIN" -days 5000 -out rootCA.pem
    openssl genrsa -out device.key 2048
    openssl req -new -key device.key -subj "/CN=MY_DOMAIN" -out device.csr
    openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000
    
  • 全部执行完毕后,会在当前路径下生成6个文件,我们服务器端会用到其中的 snakeoil.crt , snakeoil.key ,客户端会用到 ngrokroot.crt 文件,先不管它们,留到下面使用。

编译Windows平台下的服务器端程序

得益于Go的众多优秀的特性,让一份跨平台的代码能够快速地适配不同平台编译和安装。相比其他语言不同的版本,不同的编译方式,编译工具,甚至有些是超级重量级的,比如JAVA,各个平台下的JDK少说也要300M,Go的编译器全平台覆盖,并且只有90M左右,非常轻巧,并且只需要在某一平台下下载一次,就可以轻松靠着两个环境变量编译出全平台覆盖的可执行程序,交叉编译从未如此简单过。

一聊起Go的优点,我就差点收不住。我们来实际体验一下吧,在上方准备工作软件部分下载Go的Windows平台编译器,按照提示安装,设置Windows环境变量PATH路径添加Go安装目录下的bin文件夹,方便cmd中直接使用go命令,为了还能直接使用go get命令,我们需要安装Git,下载地址在上方,下载回来直接根据提示全部以默认选项安装即可。
另外需要配置GOPATH,GOROOT两个环境变量,这两个并不是必须的,但为了减少麻烦还是建议设置,其中GOROOT变量指向Go的安装路径,比如C:\GoRootGOPATH是我们的工作区路径,随意填写,写完了就在此路径下编写程序就行了,当然如果你仅仅用来完成这个项目,以后不会学习Go也不会编写Go语言的代码,这个路径对你来说仅仅是接下来自动下载源代码的保存路径而已。这里我们简单设置成C:\GoPath
如图所示:

OK,接下来正式下载,编译,和安装Ngrok的服务器端。
你一定会以为这一系列操作很复杂,其实对于Go来说仅仅是一条命令行的指令:

go get github.com/newflydd/ngrok/main/ngrokd

就这么简单,就这么神奇,简单到你不管在什么路径下执行这行命令都没有问题。这行命令的背后可不简单,go首先会去下载我上传到git上的Ngrok代码,然后试着编译,编译过程中它会发现Ngrok项目还七零八落地依赖了其他各个项目,于是Go用递归的方式再去执行go get命令,直到将所有依赖通通下载回来,最终它成功编译后,将生成的目标程序存放在%GOPATH%\bin目录下,默认情况下它是根据当前机器的环境来生成可执行文件,如果做一些简单的调整,它即可以交叉编译不同平台下的可执行文件。

执行过程可能会有些漫长,跟网络环境相关,建议使用全局科学上网工具。
成功编译后,在%GOPATH%\bin目录下会出现ngrokd.exe可执行程序,这就是你要上传到Windows服务器的程序,大小在8M左右,同时你需要上传第一步生成SSL证书中的 snakeoil.crt , snakeoil.key 两份文件到同目录。ngrokd.exe双击直接运行是没有意义的,它需要配有参数,因此我们编写bat脚本来方便运行(将下面的MY_DOMAIN替换成自己的域名,比如我的hexcode.cn):

ngrokd.exe -domain="MY_DOMAIN" -log ./ngrokd.log &

将上面脚本保存为run.bat,双击运行是一个命令行界面,同目录下会出现ngrokd.log,打开看一眼,如果出现以下字样,就说明服务器端程序已经正常运行了:

Listening for public http connections on [::]:80
Listening for public https connections on [::]:443
Listening for control and proxy connections on [::]:4443

运行时的画面如下:

编译Linux ARM平台下的树莓派客户端

试想一下,如果没有交叉编译,我们该如何在树莓派上安装Ngrok客户端:我们必须同样在树莓派上安装Go的Linux ARM版的编译器;RASPBIAN系统已经安装了git,所以我们不需要独立安装git;我们还得用go get指令下载和编译Ngrok的源码;openssl指令不用装了,因为证书已经生成了。

虽然也不是特别复杂,但还是挺费时间的,而且还会占用树莓派的资源,平白无故地要去安装接近90M的Go编译环境。得益于Go的强大,交叉编译发挥了巨大作用,我们在Windows X86 AMD64上就能编译出能在Linux ARM上跑的可执行文件。

打开一个新的cmd命令行,任意路径,执行下面命令:

set GOOS=linux
set GOARCH=arm
go get github.com/newflydd/ngrok/main/ngrok

OK,就这么简单,就这么神奇,Windows AMD64上的Go利用简单的两个环境变量,直接构造出了Linux ARM上的可执行文件,多么伟大的语言。。。
GOOS GOARCH的组合可以参照:

GOOS GOARCH
darwin 386
darwin amd64
freebsd 386
freebsd amd64
linux 386
linux amd64
linux arm
windows 386
windows amd64

在树莓派中运行Ngrok客户端程序

Go将生成的Linux平台下的程序保存在%GOPATH%\bin\linux_arm目录下,名为ngrok,我们需要将其copy到树莓派中。
我们使用超级强大的终端工具MobaXterm来连接树莓派并上传ngrok,MobaXterm的下载地址在本文上方,下载后按照提示就能安装,因为目前树莓派的SSH还不具备内网穿透功能,因此下面操作必须在局域网中完成。

使用MobaXterm建立与树莓派的SSH连接,这里并不是重点,相关操作如果不清楚可以自行百谷。
MobaXterm具有强大的文件上传功能,直接将使用下面图例中的按钮将Windows本地的ngrok客户端程序上传到指定目录,同时将我们第一步SSL证书环节生成的 ngrokroot.crt 文件上传进去。

我这里操作的目录为:/home/pi/workspace/go-apps/ngrok/,此时上传上去的ngrok文件不具备执行能力,需要使用下面命令为其添加执行权限:

chmod a+x ngrok

同样,ngrok程序不可以直接运行,也需要参数,并且还需要一个配置文件,我们执行下面操作新建配置文件,并编写sh脚本,以便直接运行。

nano ngrok.cfg

新建ngrok.cfg配置文件,并用nano文本编辑器打开,输入以下文本(将MY_DOMAIN替换成自己的域名,比如我的hexcode.cn):

server_addr: "MY_DOMAIN:4443"
trust_host_root_certs: false

tunnels:
    ssh:
        remote_port: 50000
        proto:
            tcp: 22

我这里仅仅需要让SSH的22端口具备穿透能力,因此我只配置了ssh任务,指定了远程端口固定为50000,映射本地的SSH22端口。Ctrl+O并回车保存,Ctrl+X退出文本编辑器。

nano run.sh

新建run.sh脚本文件,并用文本编辑器打开,输入以下文本:

nohup ./ngrok -log ./ngrok.ssh.log -config ./ngrok.cfg start ssh &

Ctrl+O并回车保存,Ctrl+X退出文本编辑器。

chmod a+x run.sh

为run.sh添加可执行权限。OK,大功告成,运行./run.sh即可建立树莓派与公网服务器的通信隧道,将其22端口映射到了公网服务器的50000端口,我们接下来就可以在世界任何角落,使用任何SSH工具,连接自己公网服务器的50000端口,访问家中的树莓派了,下图用MobaXterm做一个测试:

Sublime中配置GoSublime插件,使其能够对自己编写的Go代码进行自动提示

使用sublime的package管理器安装GoSublime插件后,一般能够自动提示Go内部函数,比如fmt.Printf 等,但是比较头疼的是,对自己编写的Go代码却没有自动提示功能,特别是跨包的时候。

比如自己在package model下编写一段代码,在package main下编写另一段代码,其中main里面书写代码时就没有model包的提示。原来是GoSublime插件没有配置完善,加上以下代码在GoSublime插件的配置文件就可以了,其中env的内容需要根据自己的环境进行配置。

GoSublime.sublime-settings:

{
    "env": {
        "GOPATH": "D:\\workspace\\workspace-git\\Go",
        "GOROOT": "C:\\Go",
        "PATH":"%GOROOT%;%GOPATH%;"
    },
    "comp_lint_enabled": true,   //打开这个才有下面的 comp_lint_commands标签里面的内容

    "comp_lint_commands": [
        {"cmd": ["go", "install"]}
    ],

    "on_save": [
        {"cmd":"gs_comp_lint"}   //当按保存时以cmd自动执行的命令
    ]
}

Sublime中配置Golang开发环境

最近在接触Golang,感觉很棒,等手上这个小项目用Golang搞定后,会出一个Golang的系列博客。 在Sublime中配置Golang开发环境是比较容易的。主要是插件的选择,这里我们选择GoSublime,看中他的函数解析能力,能在大多数情况下将变量所包含的方法解析出来,实际测试下来,如果是用接口实现的话有时候也没办法。不过总体来说这个插件还是不错的。 安装方法:这里就不展开讲了,`ctrl + shift + P`打开命令窗口,输入`install package`,加载好列表后,输入`GoSublime`,回车确认。 另外比较重要的是Golang文件在Sublime中的`build system`文件,我是这么写的: ``` { "working_dir": "${file_path}", "cmd": ["cmd", "/C", "go build -o ${file_base_name}.exe"], "shell": true, "variants": [ { "name": "Run", "working_dir": "${file_path}", "cmd": ["cmd","/C","start","cmd", "/C", "${file_base_name}.exe &pause"] }, { "name" : "Test", "working_dir": "${file_path}", "cmd": ["cmd","/C","start","cmd", "/C", "go test &pause"] } ] } ``` 编译时直接`ctrl + shift +B`选第一个,运行时`ctrl + shift +B`选第二个,非常简单,选第三个进行golang的单元测试。

丁丁生于 1987.07.01 ,30岁,英文ID:newflydd

  • 现居住地 江苏 ● 泰州 ● 姜堰
  • 创建了 Jblog 开源博客系统
  • 坚持十余年的 独立博客 作者
  • 大学本科毕业后就职于 中国电信江苏泰州分公司,前两年从事Oracle数据库DBA工作,两年后公司精简技术人员,被安排到农村担任支局长(其本质是搞销售),于2016年因志向不合从国企辞职,在小城镇找了一份程序员的工作。
  • Git OSChina 上积极参与开源社区