[NOTE] 内网穿透

在开了一台具有公网IP的云主机之后,利用代理服务器来实现本地服务器内网穿透的计划终于可以开始了。

ngrok实现内网穿透

一、相关原理

1.正向代理

正向代理.png

正向代理中,client的目的地址是目标服务器,但由于种种原因,与目标服务器之间连接慢或者无连接,因此,使用了一个中间媒介——代理服务器来承担一个中介的功能。

2.反向代理

反向代理.png

反向代理与正向代理不同,Client的目的地址就是Proxy服务器,所有的请求都是发给Proxy,有Proxy分配给它之后的服务器执行任务,并返回结果。反向代理主要特点:

  • 保护和隐藏原始资源服务器
  • 加密和SSL加速
  • 负载均衡
  • 缓存静态内容
  • 压缩
  • 减速上传
  • 安全
  • 外网发布

3.内网穿透

Ngrok事实上是一个隧道,即建立安全通道从公共端点到本地运行的网络服务,同时捕捉检查和重播所有流量的反向代理。

简单来说,他可以代理你本地的数据,并将其转发到外网,流程如下:

1
2
3
4
5
1. 本地内网主机和服务器A构建一条连接
2. 用户访问服务器A
3. 服务器A联系本地内网主机获取内容
4. 服务器A将获取到的内容发送给用户
5. 通过上面的流程,就实现了用户访问到了我们内网的内容。

更直观点,借用frp文档中的这张图来说明。

architecture.png

二、环境与源码

  • 相关依赖

    1
    sudo apt-get install build-essential golang mercurial git
  • Ngrok源码

    1
    2
    3
    git clone https://github.com/inconshreveable/ngrok.git ngrok
    ## 建议请使用下面的地址,修复了无法访问的包地址
    git clone https://github.com/tutumcloud/ngrok.git ngrok

    如一定要使用上面的官方地址,需要修改 src/ngrok/log/logger.go 中的源代码,
    log "code.google.com/p/log4go"

    修改为 log "github.com/alecthomas/log4go"

    否则编译时会报错 build fails: 'package code.google.com/p/log4go: unable to detect version control system for code.google.com/ path'

三、生成TLS证书

以我们的域名nuzar.top为例,由于暂时没有通过备案,无法设置二级域名,因此暂先使用一级域名。

生成TLS的命令如下:

1
2
3
4
5
6
7
NGROK_DOMAIN="nuzar.top"

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 3650 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 3650

将官方ngrok.com的证书替换为刚刚生成的私有证书,当然你也可以购买一个SSL证书,这个地方似乎可以用阿里云提供的nuzar.pem证书,但由于是初次搭建内网穿透的服务,先已稳为主。

1
2
3
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key

四、编译服务端与客户端程序

  • 设置Golang编译目录和参数

    1
    2
    export GOPATH=/go
    export CGO_ENABLED=0

    第二条命令是为了可以在 docker 环境下运行的需要,否则会报错 Unable to run a go program inside docker /bin/sh: ./ngrokd: not found

  • 编译ngrok服务端和客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # Linux 32 位系统 ngrok 编译命令
    export GOOS=linux GOARCH=386
    make release-server release-client

    # Linux 64 位系统 ngrok 编译命令
    export GOOS=linux GOARCH=amd64
    make release-server release-client

    # Windows 32 位系统 ngrok 编译命令
    export GOOS=windows GOARCH=386
    make release-server release-client

    # Windows 64 位系统 ngrok 编译命令
    export GOOS=windows GOARCH=amd64
    make release-server release-client

    # macOS 32 位系统 ngrok 编译命令
    export GOOS=darwin GOARCH=386
    make release-server release-client

    # macOS 64 位系统 ngrok 编译命令
    export GOOS=darwin GOARCH=amd64
    make release-server release-client

五、运行ngrokd服务端

编译完成之后可以在 bin 目录下看到 ngrokd(服务端)和 ngrok(客户端),运行服务端时需要将 assets/server/tls/snakeoil.crt 和 assets/server/tls/snakeoil.key

ngrokd服务端启动:

1
./ngrokd -tlsKey=snakeoil.key -tlsCrt=snakeoil.crt -domain="nuzar.top" -httpAddr=":80" -httpsAddr=":443" -tunnelAddr=":4443"

这行命令中,指定了相关证书、域名,之后的80、443端口指的是ngrokd会监听这些端口的信息,如果收到信息,就会转给相应的协议地址进行处理,4443端口用于服务端和客户端保持隧道连接。

六、运行ngrok客户端

在客户端通过scp命令从服务端中拉取客户端的可执行程序,并编写一个ngrok.cfg文件

1
2
server_addr: "nuzar.top:4443"
trust_host_root_certs: false

这里注意server_addr一定要和之前生成TLS证书步骤中的地址保持一致,否则服务端会报tls: bad certificate证书错误。

七、小结

遗憾的是,经过一个白天的努力,我确认所有步骤都正确的情况下,依然会在服务端显示证书有问题,相关问题这篇博客都说的很清楚,我也都已经一一确认过。并且由于ngrok1.x版本的开发人员已经停止维护,ngrok2.x版本为闭源软件,决定采用另一套方案。

frp实现内网穿透

首先说明,frp是由国人开发的,有非常好的中文文档支持,项目目前处于项目开发的初期,可靠性上面可能存在一些问题,但是在ngrok方案迷之出错的情况下,还是值得一试的。

frp配置

十分简单,从这找到适合你机器的最新版本,同样的,frp也是分服务端和客户端,但是在你下载的文件中,已经包含了可执行的服务端和客户端程序,十分友好。

解压下载的.tar.gz包,可以看到一堆文件中包含四个文件frpc frpc.ini frps frps.ini,这四个文件分别是客户端和服务端的程序及其配置文件,也就是说,如果你正好是个强迫症的话,你完全可以在服务端只留下frps frps.ini这两个文件,客户端同理。

使用最简化的配置:

1
2
3
# frps.ini
[common]
bind_port = 7000

启动frps: ./frps -c ./frps.ini

1
2
3
4
5
6
7
8
9
10
11
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
# 即可通过x.x.x.x:6000来访问127.0.0.1:22

启动frpc: ./frpc -c ./frpc.ini

当遇到端口占用的问题时,通过netstat -tunlp可以查看占用端口的程序的pid,一定要在确定自己要做什么的情况下,kill -9 {PID}

配置多个内网主机

错误的多客户端配置

使用一台阿里云的公网服务器,我们可以配置很多内网机器的 frp 内网穿透,公网服务器上只需要按照上述的配置一次即可,但是内网机器的配置稍有不同,如果使用了一样的配置则后添加的内网机器是无法连接上公网服务器的。这里假设另一台内网机器2的 frpc.ini 配置如下,来说明会遇到的问题:

1
2
3
4
5
6
7
8
9
[common]
server_addr = xxx.xxx.xxx.xxx <==这里还是按照上面的假设,公网服务器的ip为xxx.xxx.xxx.xxx
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6001 <==remote_port设置为另一个值

两个内网主机的配置除了 remote_port 不一样之外,都是一样的。但是在内网机器2上运行 frpc 后,公网服务器的 nohup.out 中会记录一下的错误:

[W] [control.go:332] [280d36891a6ae0c7] new proxy [ssh] error: proxy name [ssh] is already in use
1
后来发现,frp 中是通过 [ssh] 这个名字来区分不同客户端的,所以不同的客户端要配置成不同的名字。

正确的多客户端配置
内网机器1和内网机器2的配置应该区分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
内网机器1:
[ssh] <==不同点
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000 <==不同点

内网机器2:
[ssh1] <==不同点
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6001 <==不同点

在两个内网机器上分别运行 frpc 客户端程序后,一般就可以通过以下的方法 ssh 登录:

1
2
3
4
5
6
内网机器1:
ssh -p 6000 user_name1@server_addr

内网机器2:

ssh -p 6001 user_name2@server_addr

保护辣个frp

1.使用nohup启动

这是frps的后台启动(路径写你服务器上的绝对路径),如果要查看日志的话,就直接使用cat nohup.out,就可以查看了。

nohup /path/to/your/fprs -c /path/to/your/frps.ini

这是frpc的后台启动

nohup /path/to/your/fprc -c /path/to/your/frpc.ini

2.使用systemctl来控制启动

1
vim /lib/systemd/system/frps.service

写入:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=frps service
After=network.target syslog.target
Wants=network.target

[Service]
Type=simple
#启动服务的命令(此处写你的frps的实际安装目录)
ExecStart=/your/path/frps -c /your/path/frps.ini

[Install]
WantedBy=multi-user.target

启动frps:systemctl start frps

设置开机自启:systemctl enable frps

  • 如果要重启应用,可以这样,sudo systemctl restart frps
  • 如果要停止应用,可以输入,sudo systemctl stop frps
  • 如果要查看应用的日志,可以输入,sudo systemctl status frps

3.使用supervisor来控制

这个貌似只在ubuntu上好使,CentOS上使用须谨慎

sudo apt install supervisor

创建 supervisor frps 配置文件,在 /etc/supervisor/conf.d 创建 frp.conf

1
2
3
[program:frp]
command = /your/path/frps -c /your/path/frps.ini
autostart = true
1
2
3
4
# 重启supervisor
sudo systemctl restart supervisor
# 查看supervisor运行状态
sudo supervisorctl status

客户端也是一样。