最近新买了一台服务器放在家里跑各种杂七杂八的服务,需整一个内网穿透的服务通过外网也能远程登录服务器。很久之前就听闻 ngrok 能实现,于是乎自己也整了一遍。这篇文章记录服务端到客户端部署的全过程,备忘。

首先得有一台外网能够访问的 vps 用以部署 ngrok 的服务端、及一个用以解析自搭建 ngrok 服务的域名。

创建证书

ngrok 的隧道采用 TSL 传输数据,如果使用默认证书的话任何人都可以连接你搭建的 ngrok 服务,在这里你可以去买,不过还是推荐自己生成证书。

export NGROK_DOMAIN="ngrok.xxx.com"
openssl genrsa -out base.key 2048
openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pem
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csr
openssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt

执行上述文件会生成 6 个文件,server.key, server.crt, base.pem 这三个才是我们需要用到的,覆盖 ngrok 默认证书

cp server.key assets/server/tls/snakeoil.key
cp server.crt assets/server/tls/snakeoil.crt
cp base.pem assets/client/tls/ngrokroot.crt

编译 ngrok 服务端和客户端

编译需要 Golang 环境,获取 ngrok 源码: git clone [https://github.com/inconshreveable/ngrok.git](https://github.com/inconshreveable/ngrok.git)

ngrok 默认监听 IPv6 端口,我大清自有国情,我们需要源码两处

  • src/ngrok/server/tunnel.go 文件中 net.ListenTCP 后面的 tcp 改为 tcp4
  • src/ngrok/conn/conn.go 文件中 net.Listen 后面的 tcp 改为 tcp4

接下来就是编译,我们需要查看将要部署 ngrok 服务的服务端和客户端 ARCH 信息以便进行跨平台编译,使用命令:

// 这里是我机器的信息,你需要根据你机器得到的信息使用对用的编译指令
dpkg --print-architecture
amd64  // 将要部署 ngrok 服务端的 vps 服务器 arch 信息
i386   // 将要部署 ngrok 客户端的内网服务器

Go (Golang) GOOS and GOARCH

cd 到 ngrok 源码的目录准备编译:

// 服务端
GOOS=linux GOARCH=amd64 make release-server

// 客户端
GOOS=linux GOARCH=386 make release-client

编译得到的可执行文件在 bin 目录下,然后我们使用 scp 命令或 async 命令把编译后得到的文件拷贝到 vps 和内网服务器上:

scp bin/linux_amd64/ngrokd {vps_server}
scp bin/linux_386/ngrok {local_server}

配置域名和 ngrok 客户端、服务端

首先你需要在域名解析网站解析域名,后续你就可以通过域名远程连接内网服务器,这也是我们最终的目的哈。

解析主机记录为 ngrock 和 *.ngrok.xxx.com 且全部为 A 记录类型,记录值填你的 vps 外网地址。

服务端配置

前面我们已经把 ngrokd 文件拷贝到 vps 的根目录,在这里我们把转移到这里:sudo mkdir /opt/ngrkd & sudo mv ngrokd /opt/ngrokd/

然后使用 systemd 设置自启动服务,把如下内容保存在 /lib/systemd/system/ngrokd.service, 域名要改成你自己的。

[Unit]
Description=ngrok server
After=network.target

[Service]
Type=simple
ExecStart=/opt/ngrokd/ngrokd -domain ngrok.xxx.com -httpAddr "" -httpsAddr "" -tunnelAddr ":4443" -log "/var/log/ngrokd.log"
Restart=on-failure

[Install]
WantedBy=multi-user.target

启动服务

sudo systemctl enable ngrokd
sudo systemctl start ngrokd
sudo systemctl status ngrokd

客户端配置

登录到内网服务器, 同样把编译好的 ngrok 拷贝过来的 ngrok 客户端,然后同样客户端服务添加到 systemd 自启动服务中,保存下面内容到文件 /lib/systemd/system/ngrok.service

[Unit]
Description=ngrok client
After=network.target

[Service]
Type=simple
ExecStart=/opt/ngrok/ngrok -config "/opt/ngrok/ngrok.yml" -log "/var/log/ngrok.log" start transmission ssh
Restart=on-failure

[Install]
WantedBy=multi-user.target

ExecStart 加载了配置文件 /opt/ngrok/ngrok.yml,其文件内容如下:

server_addr: ngrok.xxx.com:4443
trust_host_root_certs: false
tunnels:
  transmission:
    remote_port: 9091
    proto:
      tcp: 9091
  ssh:
    remote_port: 23333
    proto:
      tcp: 22

然后像启动 ngrok 服务端服务一样启动客户端服务

在这里提醒一点,我踩到了一个坑,启动客户端服务时日志 (sudo tail -f /var/log/ngrok.log) 出现报错:

[2018/02/13 01:41:28 CST] [DEBG] (ngrok/log.(*PrefixLogger).Debug:79) [view] [term] Waiting for update
[2018/02/13 01:41:28 CST] [EROR] (ngrok/log.Error:120) control recovering from failure dial tcp: lookup ngrok.xxx.com on 8.8.8.8:53: no such host

后面我在内网服务器 ping ngrok.xxx.com 提示找不到 host, 应该是 dns 的问题,nslookup ngrok.xxx.com 看了一下,果不其然,我直接把域名和 IP 写到内服务器的 /etc/hosts 文件上。

systemd 重启客户端服务之后就正常了。

最后通过 ssh -p 23333 [email protected] 即可远程登录局域网内的服务器。

推荐及参考阅读

内网穿透之ngrok [2017-10-13 UPDATED]