header image

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

2017年10月13日更新:转发其他IP的端口

鉴于IPv4地址资源的枯竭,而电信公司又不部署IPv6,越来越多的宽带用户难以获得公网IP,导致NAS、私有云等应用难以实现,所以我们需要一种有效的能穿透NAT的工具。

省事的方法

目前有很多提供NAT穿透服务的网站,比如老牌的花生壳,以及NAT123等等。但缺点显而易见——免费的服务限制太多(限端口、限带宽),收费的又太贵。

还有一些基于这次提到ngrok提供服务的,虽然收费便宜点,但还是不能随心所欲。

ngrok介绍

ngrok是国外一个开发者基于GO语言写的服务,并且通过ngrok.com提供服务,不过1.0版本的软件是开源的(虽然停止了更新)。上面说到的基于ngrok和我们这次要架设的服务,都是基于1.0版本的。

准备工作

首先你要有自己的服务器(运行Linux的VPS),用来转发请求,然后你需要穿透NAT的设备要能够运行第三方的程序。最后你需要一台Linux的电脑(当然你在服务器上执行操作也可以,Windows 10的WSL也是不错的选择)。下面的代码会说明是在服务器本地机器还是需要穿透NAT的设备上执行。

所有的Linux操作都是基于Ubuntu或Debian,其他系统的操作大同小异,如果不会请留言。

你需要给你的服务器绑定一个域名,对于大多数人这不是问题了。如果你要用到子域名的话,再把子域名的泛解析绑定。

比如绑定了example.com的域名,如果要用子域名的话就要添加*的泛解析,DNS服务商都会有说明的,请详细阅读。

获取源代码

我们首先要在本地机器上部署编译环境:

sudo apt-get install build-essential golang mercurial git

然后获取源代码:

git clone https://github.com/inconshreveable/ngrok.git

这样会把源代码克隆岛ngrok目录下,我们切换一下目录:

cd ngrok

创建证书

因为ngrok的隧道是通过TLS传输数据的,我们可以用默认的证书,但这样谁都可以连接你的服务器了,所以我们要自己创建证书。

以下代码在本机一条一条地执行。第一条中的example.com记得改成你自己的域名。

NGROK_DOMAIN="example.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

我们需要的是server.keyserver.crtbase.pem三个文件。我们要放到对应的目录里:

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会默认监听IPv6端口,考虑到国内的网络环境,我们需要修改一下代码,否则将无法使用。主要修改两个地方,一个是src/ngrok/server/tunnel.go的109行net.ListenTCP后面的"tcp"改成"tcp4",另一处是src/ngrok/conn/conn.go的57行的net.Listen后的"tcp"同样改成"tcp4"

编译

编译很简单,我们首先通过GOOSGOARCH两个变量指定编译的目标系统和目标架构,目标系统有darwin(即OS X)、freebsdlinux,目标架构有386(32位系统)、amd64(64位系统)和arm(ARM嵌入式系统)。然后再通过serverclientrelease-serverrelease-client来指定编译的是客户端还是服务器端。有没有release的区别是,包含release的编译结果会把assets目录下的内容包括进去,从而可以独立执行。如果你今后还要更换证书,建议编译不包含release的版本。

以下代码都在本机执行。

编译服务器:

GOOS=linux GOARCH=amd64 make release-server

得到的服务器文件是bin/ngrokd

编译客户端,因为我的NAS是ARM架构的所以如下,各位请按自己的需求编译:

GOOS=linux GOARCH=arm make release-client

得到的客户端文件是bin/linux_arm/ngrok

不同系统或架构编译得到的文件位置可能不同,但都在bin目录下,并且文件名服务器是ngrokd客户端是ngrok,下面请根据实际情况操作。

配置服务器

首先把文件复制到服务器上。请选择你熟悉的手段。我是直接scp上传:

scp bin/ngrokd example.com:~

然后就要到服务器上操作了,通过SSH连接到服务器上。

首先把文件放到/opt/ngrokd目录下(这步可根据你的习惯来):

sudo mkdir /opt/ngrokd
sudo cp ngrokd /opt/ngrokd/

然后我们就要建立服务了。我使用的是Ubuntu 16.04 LTS,所以用systemd很简单,把如下内容保存到/lib/systemd/system/ngrokd.service这个文件(自己创建):

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

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

[Install]
WantedBy=multi-user.target

其中ExecStart这一项后面就是我们ngrokd执行命令了。参数-domain表示服务器域名,请改成你自己的域名-httpAddr表示默认监听的HTTP端口,-httpsAddr表示默认监听的HTTPS端口,因为我用不到所以都设置成空字符串""来关闭监听,如果需要打开的话记得格式是:12345(冒号+端口号)这样的;-tunnelAddr表示服务器监听客户端连接的隧道端口号,格式和前面一样;-log表示日志文件位置;还有个-log-level用来控制日志记录的事件级别,选项有DEBUGINFOWARNINGERROR

如果编译的是不带release的版本,还可以通过-tlsCrt-tlsKey选项来指定证书文件的位置。

设置好之后,我们就要启用服务了,很简单:

sudo systemctl enable ngrokd

其中enable是命令表示启用服务(开机会自动启动),其他命令还有disable(禁用服务)、start(启动服务)、restart(重启服务)、stop(停止服务)、status(查看服务状态)等。所以我们就启动服务:

sudo systemctl start ngrokd

然后通过status查看服务,如果是active (runing)那么就OK了。

配置设备

先把从本机把客户端复制到我们的设备(这里我的是NAS)上。请选择你熟悉的手段。我还是一样使用scp

scp bin/linux_arm/ngrok 192.168.11.50:~

接下来我们就要到设备上操作了,同样通过SSH连接到设备上。

首先还是一样的把程序复制到/opt/ngrok目录下(这步同样可根据你的习惯来):

sudo mkdir /opt/ngrok
sudo cp ngrok /opt/ngrok/

然后还是一样的建立服务,因为我的NAS运行的是Debian,还是用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(之后我们会建立),-log记录日志和服务器端是类似的,最后用start transmission ssh启动了配置文件中的transmissonssh两个隧道。

那么配置文件是什么样呢?我们把如下内容保存到/opt/ngrok/ngrok.yml中:

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

server_addr就是我们在服务器配置的域名和端口。

tunnels下就是隧道设置。

每一级前面多两个空格缩进的表示是上一级的子项。比如transmissionssh就是tunnels的子项,remote_portproto又是他们的子项,以此类推。

transmissionssh就是隧道的名称,每个对应一个隧道。上面的启动命令中start后面的就是这个。

remote_port表示远程对应的端口。

proto表示协议,支持的有tcphttphttps。因为我没有开启服务器上的http和https,所以我都是用的tcp(TCP也可以支持HTTP或HTTPS,之所以有独立的http和https选项是为了简化设置,直接对应服务器上的相应设置)。

tcp就是协议,后面跟着的是端口号。

所以上面的设置文件就表示,将服务器的9091端口的连接转发到设备的9091端口上,服务器的23333端口的连接转发到设备的22端口上。这样我们就可以通过访问服务器的相应端口来访问设备上的服务了。

2017年10月13日更新

还可以转发其他IP的端口,方法就是在proto下的tcp(或httphttps)后的端口号写成IP地址:端口号的格式(中间是英文冒号)。

如:

      tcp: 192.168.11.1:80

最后别忘了启用服务:

sudo systemctl enable ngrok
sudo systemctl start ngrok

最后

这样,我们就将设备上的9091端口和22端口的对应到了服务器的9091和23333端口上了。比如我要访问设备的SSH,就可以这样:

ssh -p23333 example.com

这样有个缺点,就是只能建立到运行ngrok的设备的隧道,如果你的设备不能运行ngrok,那就有点麻烦了。 2017年10月13日更新:其实是可以转发其他IP的端口的,详见上面的配置设备一节。

如果不能运行ngrok的设备提供的是HTTP或者HTTPS访问,还有一种变通的方法是在运行ngrok的设备(比如树莓派)上通过nginx的建立代理,代理到不能运行ngrok的设备上。

虽然有局限,但我们已经成功地穿透内网了。只希望IPv6快点到来,让我们不必再处理这些麻烦事。

赞赏

微信赞赏支付宝赞赏

15 comments

  1. 大俠你好,
    謝謝你的分享,
    編譯 :~/ngrok$ sudo GOOS=linux GOARCH=arm make release-client 時,發生如下問題:
    go install -tags ‘release’ ngrok/main/ngrok

    github.com/gorilla/websocket

    src/github.com/gorilla/websocket/client_clone_legacy.go:24: unknown tls.Config field ‘GetCertificate’ in struct literal
    src/github.com/gorilla/websocket/compression.go:36: undefined: flate.Resetter
    Makefile:17: recipe for target ‘client’ failed
    make: *** [client] Error 2
    請問如何處理,謝謝

    Henry

  2. go version 1.3.3 , 現在按大俠提示,把 go version 提高,再安裝,以後再報告。。。。。。。。

  3. gopkg.in/yaml.v1 (download)
    github.com/inconshreveable/go-vhost (download)
    github.com/alecthomas/log4go (download)
    github.com/nsf/termbox-go (download)
    github.com/mattn/go-runewidth (download)
    github.com/gorilla/websocket (download)
    go install -tags ‘release’ ngrok/main/ngrokd
    go build ngrok/main/ngrokd: /usr/lib/go-1.6/pkg/tool/linux_amd64/link: signal: killed
    Makefile:11: recipe for target ‘server’ failed
    make: *** [server] Error 1
    root@ngrok:~/ngrok#

    以上是 ubuntu 16.04 LTS , go version 1.6.2 編譯都發生問題。不知如何處理,謝謝。。。

    1. 我是不建议对外提供服务啦,毕竟要消耗流量等资源。源程序是用go语言编写的,其实可以做个二次开发,增加一些用户验证的功能。

  4. 已成功搭建,谢谢楼主提示。

    现有一问题,在 tunnels 内开启 ssh 成功,但同时开启 http 失败,
    请问如何设定?谢谢

    1. http你可以直接在客户端设置tcp映射80端口到服务器80端口:

      tunnels:
        somehttpserver:
          remote_port: 80
          proto:
            tcp: 80
      

      前提是服务器的80端口没有被占用(你可以用别的端口)。如果要用ngrok自带的http,你需要在服务器启动参数那里设置http端口,如:

      /opt/ngrokd/ngrokd -domain example.com -httpAddr ":80" -httpsAddr "" -tunnelAddr ":4443" -log "/var/log/ngrokd.log"
      

      上面设置了http映射到80端口,客户端设置是:

      tunnels:
        somehttpserver:
          proto:
            http: 80
      

      这样就不用在客户端设置remote_port了。因为这样有局限(比如我要设置多个不同端口的http服务),所以用tcp来得更灵活。

  5. 我发现cloud station是连接不上的,配置已经设置了6690端口,但是DS PHOTO,DS FILE,DS NOTE还有NAS管理都是可以连接上。为什么呢?
    另外:通过花生壳设置6690是可以访问的。

reply

your email will keep secret. (* required).

you can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> .