关于搭建自己的NAT穿透服务这件事
ShawJie

关于搭建自己的NAT穿透服务这件事

最近买了一台NAS,不想仅仅止步于在内网的使用。那么需求也就应运而生,我需要把我的NAS映射在公网上。在分析了各种技术方案之后根据现实条件选择了NAT穿透,因此也就有了这一篇内容,于此做个记录,也给可能会碰到同类型需求的各位分享一下自己的实现步骤。

内网 → 公网

​ 在说明这个映射过程之前,我们需要先知道NAT是什么。上个世纪80年代,当时的人们在设计网络地址的时候,没考虑过2的32次幂台终端设备连入互联网情况,以及增加IP的长度(即使是从4字节增到6字节)对当时设备的计算、存储、传输成本也是相当巨大的。而后,IP地址不够用了,NAT(Network Address Translation)网络地址转换技术也就诞生了,NAT的本质是一群机器共用一个公网IP,即内网的机器发出的请求会通过NAT转换为公网地址,隐藏内网IP,而公网的响应通过NAT映射到对应的机器。

DDNS + 端口映射

​ DDNS(Dynamic Domain Name Server)即动态域名服务,是将用户的动态IP地址映射到一个固定的域名解析服务上,用户每次连接网络的时候客户端程序就会通过信息传递把该主机的动态IP地址传送给位于服务商主机上的服务器程序,在目前家用网络大多使用动态IP的大环境情况下,DDNS可以保证域名指向的正确性。而配合DDNS进行的端口映射操作就可以把内网机器上的服务映射出去。

NAT穿透

​ NAT穿透实现服务向外暴露的思路和通过DDNS+端口映射不太一样,NAT穿透的前提是需要在有公网IP的服务器上部署Server端,在需要进行暴露的服务器部署Client端。Client端在启动后会与Server端完成身份验证并建立隧道,而后用户访问行为会先到Server端,Server端会根据Client端在启动时的注册信息分配到对应的Client端,Client端将响应传输到Server端,Server端将Client端的响应返回给用户。整个调用链路比较类似反向代理。

Ngrok

Ngrok是NAT穿透的一个具体实现应用,官网对于Ngrok的描述如下:Ngrok is a reverse proxy that creates a secure tunnel from a public endpoint to a locally running web service. ngrok captures and analyzes all traffic over the tunnel for later inspection and replay.(翻译:Ngrok是一个可以从公网端点创建安全通道到本地Web服务的反向代理,Ngrok会捕获并分析所有通过隧道的流量以用于之后的检查和重播)

关于Ngrok的运行时序图如下:

image

搭建过程

​ 由于整体的Ngrok服务端/客户端搭建操作步骤较为繁琐,涉及的环境包括Go、Git、make。在搭建过程中为了简化搭建的操作,同时在多机器部署时保证环境的一致性,使用Docker进行了整体的服务端、客户端构建。

服务端

​ 目前服务端部署于Aliyun ECS,系统为CentOS 7.x。Dockerfile如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
## 基于Golang-alpine镜像进行构建
FROM golang:alpine

## 设定Ngrok服务端域名参数
ARG DOMAIN_NAME

## 对apline系统包仓库进行换源并安装 openssl make git
## 在安装完成之后通过git将Ngrok源码克隆到本地
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
apk add --no-cache openssl make git && \
cd / && \
mkdir data && \
cd data && \
git clone https://github.com/inconshreveable/ngrok.git source

## 添加Ngrok 编译构建脚本到镜像
ADD build.sh /data

WORKDIR /data

## 调用编译构建的脚本
RUN sh build.sh $DOMAIN_NAME && \
export PATH=$PATH:/data/source/bin

VOLUME ["/data"]

## 将Ngrok通道监听地址向外暴露
EXPOSE 4443

## 启动Ngrok服务端
CMD ["/data/source/bin/ngrokd"]

build.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export NGROK_DOMAIN="$0"

cd /data/source

## 基于传入的Ngrok域名生成ssl证书
export NGROK_DOMAIN="$DOMAIN_NAME" && \
openssl genrsa -out rootCA.key 2048 && \
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 5000 -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 5000 && \
cp -f rootCA.pem assets/client/tls/ngrokroot.crt && \
cp -f device.crt assets/server/tls/snakeoil.crt && \
cp -f device.key assets/server/tls/snakeoil.key

echo "ready to make server ->"

## 编译Ngrok-server端
make release-server

## 编译Ngrok-client端
echo "ready to make client ->"
GOOS=linux GOARCH=amd64 make release-client

Ngrok通过GOOSGOARCH两个参数支持多平台客户端的构建,默认构建的是linux-amd64的客户端,若需要构建其它平台的客户端,可以自行修改build.sh。对应图表如下:

GOOSGOARCH平台
linux386linux 32位系统
linuxamd64linux 64位系统
windows386windows 32位系统
windowsamd64windows 64位系统
darwin386Macos 32位系统
darwinamd64Macos 64位系统
linuxarmarm架构Linux系统

​ 在完成以上准备工作之后,即可通过docker build --tag [你的镜像名称:版本] --build-arg DOMAIN_NAME=[你的域名] .进行镜像的构建。

​ 在构建完成之后镜像的启动我通过了docker-compose来完成。docker-compose.yml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: "3.1"

services:

ngrok:
image: shawjie/ngrok:latest ## 构建完的镜像
command: "/data/source/bin/ngrokd -domain='[你的域名]' -httpAddr=':80'" ## http的监听端口
ports:
- "4443:4443"

## 在Ngrok之前还有一个Nginx作为整体的服务器网关
## 于此将Ngrok和Ngink配置在了同一个网络环境下
networks:
default:
external:
name: bin_nginx_default

​ 在Nginx侧的配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name *.ngrok.xxx.com; ## 你在DNS解析时配置的域名

charset utf-8;

location / {
proxy_pass http://ngrok:80; ## 用于转发http请求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
}
}

​ 于此,Server端的配置就已经完成了。至于客户端,你还得先把构建完成的ngrok-client从运行的镜像中Copy出来。

docker cp [你的容器名称]:/data/source/bin .而后根据你自己系统,将对应的client复制到client端去。

客户端

​ 目前客户端部署于Synology 218+ DMS上。
​ 在ngrok-client的同级目录下创建config.yml文件,用于配置ngrok-client连接服务端,以及相关通道的内容。

config.yml

1
2
3
4
5
6
7
8
server_addr: "ngrok.xxx.com:4443"  ## 你的域名:端口号
trust_host_root_certs: false

tunnels:
synology_nas:
subdomain: nas ## 三级域名所对应的子域名前缀 也可以使用remote_port:
proto:
http: 5000 ## 支持 http tcp值为对应的地址或端口号

​ 为了操作方便环境统一,方便管理,我依旧使用了docker来进客户端的部署。首先就是客户端运行所依托的镜像基础dockerfile:

1
2
3
4
5
6
7
8
9
10
11
FROM alpine:latest  ## 基于alpine进行镜像的构建以减小镜像大小

RUN cd /usr/local && \
mkdir service

WORKDIR /usr/local/service

VOLUME ["/usr/local/service"]

## 启动客户端 配置
CMD /usr/local/service/ngrok -config=config.yml -log=ngrok_access.log start-all

​ 而后通过docker run建立volume之间的链接,即可完成整体Ngrok Server-Client的构建、启动。

docker run --name shawjie_ngrok_client -v /usr/local/data:/usr/local/service --net host -d [你构建的客户端镜像名称]

​ 至此,你就可以通过域名直接访问到你内网的服务了。

尾巴

​ 最近的工作也还比较忙,因为入手了一台Nas,而且家里路由器也没有公网IP,所以DDNS + 端口映射的方案就不可行了,只能另求他法,Ngrok2.x的项目作者不打算开源,而是进行了商业化运作。但是简单的功能Ngrok1.x已经能完全从容应对了,Ngrok的功能除了能把我的Nas映射出去,也可完成很多别的事情,譬如微信相关开发的回调,又或是你想把一个应用临时性的向外展示,甚至说你想把内网的服务器通过Ngrok将ssh映射出来。总之,这个功能完全只是局限于你的想象力就是了,明天22岁生日,我要安心过生日去了,这个月底之前计划还要整理一篇内容,总之…加油。