基于云服务器代理与虚拟专用网络的方式实现透明内网穿透系统

1,836 阅读19分钟

前言

一般通过运营商接入的家庭网络或企业办公网络是不能对外发布网站或网络服务的,主要是受制于国家对于经营性网络服务的管理办法及运营商采取的技术性封锁。但是作为IT行业的从业人员,在一些开发与测试的场景下,又确实有这方面的临时性需求,本篇文章就是介绍在条件有限的情况下如何通过技术手段来临时突破运营商对我们的封锁,从而顺畅的进行相关开发调试工作。(PS,本方案仅供学习、开发、调试使用,如有长期经营网站的需求,请通过合法渠道采用云服务器或接入运营商专线网络)

运营商对非专线网络的技术性封锁

  • 运营商不提供稳定且固定IP
  • 运营商对常用的网站端口例如:80、443入网请求采取了封禁

研发场景中的需求

  • 微信公众平台、微信小程序,接口域名、业务域名相关的验证
  • 微信支付的通知回调接口
  • 支付宝支付的通知回调接口
  • 提供给客户访问的内部测试或演示环境
  • 其他第三方需要通过互联网直连内网服务的场景

关于内网穿透

我们使用的网络,一般分为互联网和局域网,互联网和局域网本身是互相隔离的,家用或商用办公网络一般是通过在局域网内增设路由器,并由路由器连接互联网,然后在路由器上通过NAT等网络技术打通局域网和互联网的连接性(一般只是NAT出网数据包)。简单的内网穿透可以通过端口映射的方式将局域网内特定地址的特定端口所提供的网络服务暴露在互联网上供互联网中其他的设备访问,但是端口映射具有一定的局限性,例如刚才提到的,当运营商在其自身的网关中就把我们的80、443的入网数据包给丢弃了,那么路由器本身都接收不到请求80、443的数据,又如何将数据转发到特定局域网的服务中呢?所以我们要采用另外一种更加高级的内网穿透技术。

正文 - 基于云服务器代理与虚拟专用网络的方式实现透明内网穿透系统

概要

在本方案中,我们通过在内网搭建一个VPN服务,然后将VPN服务所使用到的端口通过端口映射的方式映射到互联网供我们的云服务器从互联网中连接到局域网的VPN网络,之后通过云服务器中搭建反向代理服务,将访问云服务器的网络请求代理到VPN网络中的特定地址的特定端口的服务中,从而实现了透明的内网穿透。 在本文中我们将基于 ubuntu18.04 系统,安装 pptpd 构建VPN网络,安装 traefik 提供反向代理服务,安装 nginx 提供Web站点服务。

准备工作

  • 在云平台中准备一台用于提供代理服务的云服务器(ubuntu18.04)
  • 在局域网中准备一台用于提供VPN服务的服务器(ubuntu18.04)
  • 在局域网中准备一台用于提供Web站点服务的测试服务器(ubuntu18.04)

详细步骤

一、配置用于测试的 Web 服务器

如果你的局域网中已经配置和部署了 Web 站点,并且该站点可以在局域网中通过局域网IP+端口成功访问,则可以跳过该步骤,直接进入 二、配置VPN服务器 。 安装 nginx 服务(在本文中将通过 nginx 构建一个用于测试的 Web 站点,你还可以使用其他 Web服务器程序)

  1. 先使用 ssh 连接到用于提供Web站点服务的测试服务器,并使用 su 切换到 root 用户
  2. 接着在远程服务器中使用 apt update 更新安装源
  3. 当更新完成后通过 apt install nginx 安装 nginx 服务

当 nginx 服务被成功安装后,默认情况下会提供一个默认的 Web 站点,其配置文件在 /etc/nginx/sites-enabled/default,该站点默认监听当前服务器的80端口。你应该在当前局域网中的任意计算机中通过以下地址访问到这个站点提供的默认页面:(将下方的 10.0.11.1 替换为你搭建的 Web服务器IP地址)

http://10.0.11.1:80

如果在浏览器中打开该地址,应该返回类似以下页面:

至此,用于测试的 Web 服务器配置完成。

二、配置VPN服务器

安装 pptpd 服务(在本文中将通过 PPTP 协议构建 VPN 服务,你还可以使用其他协议构建自己的 VPN 服务)

  1. 先使用 ssh 连接到用于提供VPN服务的服务器,并使用 su 切换到 root 用户
  2. 接着在远程服务器中使用 apt update 更新安装源
  3. 当更新完成后通过 apt install pptpd 安装 pptpd 服务

安装完后我们还需要对已安装的 pptpd 服务进行相关配置。默认情况下,pptpd 服务的配置文件路径是 /etc/pptpd.conf

  1. 使用 vim /etc/pptpd.conf 打开并编辑配置文件
  2. 默认情况下配置文件中的内容都是被注释的,需要删除注释符 # 并进行相关配置,例如:
option /etc/ppp/pptpd-options
localip 10.0.254.1 #本机IP地址,也是 pptpd 服务要监听的IP地址
remoteip 10.0.254.100-254 #当客户端连接后可分配给客户端的IP地址池

接着我们还需要配置 VPN 账号信息,以供连接 VPN 的时候进行安全的鉴权。默认情况下,VPN 账号的配置文件路径是 /etc/ppp/chap-secrets

  1. 使用 vim /etc/ppp/chap-secrets 打开并编辑配置文件
  2. 进行相关配置,例如:
# Secrets for authentication using CHAP
# client        server  secret                  IP addresses
# 账号     VPN服务 密码       分配固定的客户端IP地址
proxy_100 pptpd P@ssw0rd100 10.0.254.100

在上面的配置文件中每一行代表的是一个 账号/密码对。 每行都必须配置 用户名、VPN服务、密码、分配的IP地址。通过使用空格符对参数进行分隔。

为了使配置生效,我们还要重启 pptpd 服务,通过以下命令:

service pptpd restart

为了提供 VPN 服务,我们还需要打开 Linux 内核中的 IP 转发功能

  1. 使用 vim /etc/sysctl.conf 打开并编辑配置文件
  2. 进行相关配置,例如:
net.ipv4.ip_forward=1

编辑完 sysctl.conf 配置文件后需要重启系统才能生效,也可以通过执行 sysctl -p 立即生效。

为了让连接了 VPN 的客户端系统能够访问局域网中其他的设备,我们还需在这台提供 VPN 服务的服务器上做路由配置,通过 iptables 工具将对局域网子网的数据包通过局域网网卡转发到我们的局域网中。例如:(将下方的 10.0.0.0/16 替换为你的子网地址,将 ens18 替换为你的局域网网卡)

iptables -t nat -A POSTROUTING -s 10.0.0.0/16 -o ens18 -j MASQUERADE

为了让我们的 VPN 服务向互联网中提供服务,我们还需要在路由器中将互联网地址的 pptpd 服务端口映射到这台局域网的 VPN 服务器中,pptpd 默认采用的端口是 1723,在笔者的网络环境中 VPN 服务器的局域网IP地址为 10.0.254.1,所以我需要将互联网的 1723 端口映射到 10.0.254.11723 端口,如图:

至此,VPN服务器配置完成

三、配置代理服务器

安装 pptp-linux 客户端

  1. 先使用 ssh 连接到用于提供代理服务的云服务器,并使用su切换到root用户
  2. 接着在远程服务器中使用apt update更新安装源
  3. 当更新完成后通过apt install pptp-linux安装 pptp-linux 客户端

当安装了 pptp-linux 客户端之后,我们可以通过 pptpsetup 工具创建一个新的 VPN 连接,例如:(将下方的 113.118.213.231 替换为你路由器使用的的互联网IP,将 proxy_100 替换为你的VPN帐号,将 P@ssw0rd100 替换为你的VPN密码)

pptpsetup --create pptpd --server 113.118.213.231 --username proxy_100 --password P@ssw0rd100 --encrypt

之后,你可以通过以下命令,打开/关闭 VPN 网络

pon pptpd #打开VPN连接
poff pptpd #关闭VPN连接

注意:当我们打开VPN连接后,如果因为网络波动,丢包,长时间延迟等情况,VPN连接会自动关闭,pptpd本身并没有断线自动重连的功能,需要自行处理解决。

当我们打开VPN连接后,通过 ipconfig我们可以看到多了一个名为 ppp0 的网络连接。如图:

此时要通过 VPN 隧道访问本地局域网中的服务还需要在路由表中增加相关的路由策略,用于将特定数据包通过 ppp0 这个网络连接转发至 VPN 隧道中。我们通过 route 工具在路由标中增加相关路由策略,例如:(将下方的 10.0.11.1 替换为你要访问的局域网IP地址,在这里我采用用于测试的 Web 服务器地址,你可以根据情况替换)

route add -host 10.0.11.1 dev ppp0

测试是否可以访问这个IP地址,经过上面的路由配置,此时应该能够正确接收到来自 10.0.11.1 的回复

ping 10.0.11.1

上面的命令仅配置了对于 10.0.11.1 地址的访问策略,如需访问其他地址,可参考上面的命令继续添加更多路由策略来达到目的。如果觉得逐个配置Host 地址过于繁琐,你还可以通过以下命令将访问某个子网的数据包通过 VPN 隧道转发:

route add -nat 10.0.0.0 netmask 255.255.0.0 dev ppp0

上面的命令将 10.0.0.0/16 子网的地址全部通过 ppp0 这个网络连接转发。

注意:当VPN连接断开后,ppp0 网络连接将会消失,并且关联到这个网络连接的路由配置也会自动取消。所以每当打开 VPN 连接后,都需要重新通过 route 工具增加相关路由策略

经过以上配置,你现在应该可以在这台云服务器上直接访问局域网内的网络服务了,我们可以通过 curl 工具测试是否能够通过 VPN 隧道访问到局域网内的 Web服务,例如访问之前我们搭建的用于测试的 Web站点:(将下方的 10.0.11.1 替换为你搭建的 Web服务器IP地址)

curl http://10.0.11.1:80

一切正常的话,终端中应该打印出了这个网页的 HTML 代码,如下图所示:

既然在这台云服务器上已经可以正确访问到局域网内的网络服务了,接下来我们可以在这台云服务器上安装和配置相关的反向代理服务,将局域网内的网络服务代理出去供互联网中的其他设备或终端访问。

安装 traefik 反向代理服务(在本文中将通过 traefik 构建反向代理服务,你还可以使用其他反向代理程序)

traefik 是一款开源的、基于Go语言开发的反向代理服务程序,起初接触到它是因为 Kubernetes,一般 traefik 用作与 Kubernetes 集群中提供反向代理服务,traefik 可以将 Kubernetes 中的 Service 暴露给集群外部访问,并且保留了 Service 原生的 负载均衡 能力。traefiknginx 不同的是,nginx 本质上是一款Web服务器程序,而 traefik 只专注于提供反向代理功能,而且更为夸张的是 traefik 不仅仅能提供基于 HTTP 协议的反向代理能力,还能提供基于 TCP/IP 的反向代理能力。

通过 traefikgithub 中的项目站点 github.com/containous/… 可以找到已发布的二进制可执行程序包

wget https://github.com/containous/traefik/releases/download/v2.1.6/traefik_v2.1.6_linux_amd64.tar.gz #通过 wget 工具将二进制程序包下载到本地
tar -xf traefik_v2.1.6_linux_amd64.tar.gz #解压包文件后会得到一个 traefik 的可执行程序
mv traefik /usr/local/bin/ #将 traefik 移动到 `/usr/local/bin` 目录下
mkdir /etc/traefik #创建用于保存traefik配置文件的文件夹
mkdir /etc/traefik/configs #创建保存动态配置文件的文件夹

创建 traefik 静态配置文件

  1. 使用 vim /etc/traefik/traefik.toml 创建并编辑配置文件
  2. 进行相关配置,例如:(更详细的配置,可以参考 docs.traefik.io/reference/s…
[api]
  insecure = true #启用API
  dashboard = true #激活控制面板
  debug = true #调试模式

[entryPoints] #配置入口点
  [entryPoints.http]
    address = ":80" #监听80端口并命名为 http 的入口点
  [entryPoints.https]
    address = ":443" #监听443端口并命名为 https 的入口点

[providers] #配置提供程序
  [providers.file] #启用文件形式的提供程序
    directory = "/etc/traefik/configs" #反向代理配置所在的文件夹
    watch = true #监视配置文件夹的变动,启用后一旦配置文件有改变则立即生效,否则需要重启服务
    debugLogGeneratedTemplate = true #启用生成的配置模板的调试日志记录

[certificatesResolvers] #配置证书解析器
  [certificatesResolvers.myresolver]
    [certificatesResolvers.myresolver.acme] #配置acme协议的证书解析器并命名为 myresolver
      email = "xxx@domain.com" #acme协议中使用的电子邮箱
      storage = "/etc/traefik/acme.json" #acme协议申请到的证书保存在这个文件中
      [certificatesResolvers.myresolver.acme.tlsChallenge] #激活TLS-ALPN-01挑战

创建全局公用的动态配置文件

  1. 使用 vim /etc/traefik/configs/global.toml 创建并编辑配置文件
  2. 进行相关配置,例如:
[http.middlewares]
  [http.middlewares.https_redirect.redirectScheme]
    scheme = "https"
    permanent = true

以上配置配置了一个名为 https_redirect 的中间件,它的作用是强制将http请求重定向为https请求,在接下来的反向代理实例配置中,我们将会使用到这个中间件。

创建反向代理实例的动态配置文件

  1. 使用 vim /etc/traefik/configs/test_domain_com.toml 创建并编辑配置文件
  2. 进行相关配置,例如:
[http]
  [http.routers] #配置路由
    [http.routers.http-test_domain_com] #创建一个名为 http-test_domain_com 的路由
      entryPoints = ["http"] #该路由使用 http 入口点
      middlewares = ["https_redirect"] #该路由使用 https_redirect 中间件
      rule = "Host(`test.domain.com`)" #该路由匹配 test.domain.com 域名
      service = "test_domain_com" #该路由使用 test_domain_com 服务

    [http.routers.https-test_domain_com] #创建一个名为 https-test_domain_com 的路由
      entryPoints = ["https"] #该路由使用 https 入口点
      rule = "Host(`test.domain.com`)" #该路由匹配 test.domain.com 域名
      service = "test_domain_com" #该路由使用 test_domain_com 服务
      [http.routers.https-test_domain_com.tls] #启用TLS
        certResolver = "myresolver" #使用 myresolver 证书解析器处理证书

  [http.services] #配置服务
    [http.services.test_domain_com.loadBalancer] #创建一个名为 test_domain_com 的负载均衡服务
      [[http.services.test_domain_com.loadBalancer.servers]]
        url = "http://10.0.11.1:80" #该服务反向代理到这个 URL 地址

因为我们在之前的 /etc/traefik/traefik.toml 中配置了 dashboard = true,所以当 traefik 服务运行起来后除了能够提供反向代理服务之外,还监听了 127.0.0.1:8080 端口,提供了一个 dashboard 站点,因为这个站点只监听了 127.0.0.1 的网络地址,所以只能本机访问,如果需要让外部访问,我们也可以通过创建一个反向代理实例来对外暴露这个站点。

创建反向代理实例的动态配置文件

  1. 使用 vim /etc/traefik/configs/traefik_test_domain_com.toml 创建并编辑配置文件
  2. 进行相关配置,例如:
[http]
  [http.routers]
    [http.routers.http-traefik_test_domain_com]
      entryPoints = ["http"]
      middlewares = ["https_redirect"]
      rule = "Host(`traefik.test.domain.com`)"
      service = "traefik_test_domain_com"

    [http.routers.https-traefik_test_domain_com]
      entryPoints = ["https"]
      rule = "Host(`traefik.test.domain.com`)"
      service = "traefik_test_domain_com"
      [http.routers.https-traefik_test_domain_com.tls]
        certResolver = "myresolver"

  [http.services]
    [http.services.traefik_test_domain_com.loadBalancer]
      [[http.services.traefik_test_domain_com.loadBalancer.servers]]
        url = "http://127.0.0.1:8080"

在上面的步骤中,我们配置了两个反向代理实例,其中使用到了 test.domain.comtraefik.test.domain.com 这两个域名,为了让域名能够解析到我们用于代理的云服务器,我们还需要进入到域名的DNS管理面板,分别创建两条 A记录 的域名解析规则,将这两个域名指向到云服务器。

最后,运行反向代理服务

traefik

注意:此时 traefik 是作为前台进程运行,如果关闭当前终端或用 Ctrl+C 都会结束 traefik 进程,导致服务停止

如果一切正常的话,你应该可以在互联网中的任意一台设备通过浏览器打开并访问 http(s)://traefik.test.domain.comhttp(s)://test.domain.com,如下图所示:

我们可以在浏览器中访问 http://test.domain.com 也可以访问 https://test.domain.com,当我们通过 http 协议访问这个代理时,traefik 会自动将网页重定向至 https 协议,这是因为我们在配置相关反向路由实例时,为来自 http 入口点创建了一个路由,在这个路由中使用了 https_redirect 中间件,而这个中间件就是用于将请求重定向至 https 协议的。与此同时,你还会发现通过 traefik 反向代理暴露出去的 https 代理其证书已经被主流浏览器所信任,如下图所示:

这是因为我们在配置相关的反向路由实例时,为来自 https 协议的入口点创建了一个路由,在这个路由中启用了 tls,并且采用了 myresolver 证书解析器,该证书解析器会基于 acme 协议向 Let’s Encrypt 发起 TLS-ALPN-01 挑战,挑战成功后 Let’s Encrypt 会为当前路由的域名颁发证书。 当 traefik 运行后会定期的检查 /etc/traefik/acme.json 中是否存在相关证书数据,如果不存在则会自动发起挑战申请证书,证书申请完成后 traefik 会将证书数据存储在 /etc/traefik/acme.json 中。


至此,代理服务器配置完成

四、提高系统可靠性

经过以上的演练步骤,我们已经成功的借助于 pptpdtraefik 等工具实现了 基于云服务器代理与虚拟专用网络的方式实现透明内网穿透系统

但是我们在演练中构建的系统是十分脆弱的,接下来,我们就一起分析一下系统有哪些部分会导致可靠性差,从而通过弥补的方式提高系统的可靠性。

  • 在我们的本地局域网中是通过运营商提供的 家用商用 网络接入到互联网,在这种类型的网络中,运营商并不会给我们分配固定的IP地址,也就是说每当我们本地网络因为重启、重新拨号、被运营商重置网络等情况后,我们都需要在云服务器上更新或重新创建 pptp 连接配置,因为云服务器在连接到 VPN 时需要使用到我们本地网络的互联网IP地址。此问题可以通过使用 DDNS 技术解决,例如使用 ngrok花生壳dnspod 等工具实现动态域名解析,这样子我们在云服务器上配置 pptp 连接的时候,可以用动态域名替代IP地址。
  • 我们的云服务器使用了 pptp-linux 客户端连接到 VPN 网络,而 pptp-linux 本身是不具备断线重连的功能的,需要自行编写 守护进程 脚本来检查连接状态,当连接异常时候自动重新连接。
  • 我们的云服务器使用了 traefik 作为反向代理服务,但是 traefik 并没有提供后台运行的功能,且万一 traefik 进程因为进程异常导致进程退出后并不会重新启动进程,所以也是需要自行编写 守护进程 脚本来检查连接状态。

综上,我们开始优化之前配置的系统

动态域名解析的问题,其技术已经十分成熟且互联网中已经有许多教程和案例了,这里不在复述。 关于 VPN 的守护进程,我们可以通过编写脚本实现

  1. 使用 vim /usr/local/bin/vpn_keep.sh 创建并编辑脚本文件
  2. 编写脚本,例如:
#!/bin/bash

if [ `ps -ef | grep "pptp" | grep -v grep | wc -l` -lt 1 ]; then
    pon pptpd
fi

为刚才编写的脚本添加执行权限 chmod +x /usr/local/bin/vpn_keep.sh 此时我们编写了一个检查 pptp 进程是否存在,如果不存在则说明 VPN 已断开连接,则通过 pon pptpd 再次打开连接,该脚本只运行检查一次即结束,所以我们还要一种机制,让操作系统定时的运行这个脚本,从而实现对 VPN 连接的监控和重连。 我们通过 crontab 来配置系统的周期性执行任务,执行 crontab -e 编辑任务清单,增加一项每5秒钟执行一次 /usr/local/bin/vpn_keep.sh 脚本的任务,例如:(关于 crontab 的详细语法不在本文讨论范围内,请自行查阅)

* * * * * sleep 5; /usr/local/bin/vpn_keep.sh

添加之后,我们还需要重启 crontab 服务,让我们的配置生效

service cron restart

之前我们有提到,当 VPN 连接被断开后,相关的路由表配置也会被重置,所以在我们重新连接 VPN 后还需要重新添加相关路由策略,你可以在 /usr/local/bin/vpn_keep.sh 脚本中增加添加路由策略的脚本,但是更加有效的方式是,我们可以通过创建和编辑 /etc/ppp/ip-up.d/pptpd 来告诉 pptpdVPN 连接成功后进行相应的路由策略配置。

  1. 使用 vim /etc/ppp/ip-up.d/pptpd 创建并编辑脚本文件
  2. 编写脚本,例如:
#!/bin/bash

route add -host 10.0.11.1 dev ppp0

为了让我们的配置生效,需要重启 pptpd 服务

service pptpd restart

关于 traefik 的后台运行及守护进程,我们可以通过编写脚本实现

  1. 使用 vim /usr/local/bin/traefik_keep.sh 创建并编辑脚本文件
  2. 编写脚本,例如:
#!/bin/sh

if [ `ps -ef | grep "traefik" | grep -v grep | wc -l` -lt 1 ]; then
    /usr/local/bin/traefik &
fi

为刚才编写的脚本添加执行权限 chmod +x /usr/local/bin/traefik_keep.sh 此时我们编写了一个检查 traefik 进程是否存在,如果不存在则在后台运行该进程的脚本,该脚本只运行检查一次即结束,所以我们还要一种机制,让操作系统定时的运行这个脚本,从而实现对 traefik 的监控和重启。 我们通过 crontab 来配置系统的周期性执行任务,执行 crontab -e 编辑任务清单,增加一项每5秒钟执行一次 /usr/local/bin/traefik_keep.sh 脚本的任务,例如:(关于 crontab 的详细语法不在本文讨论范围内,请自行查阅)

* * * * * sleep 5; /usr/local/bin/traefik_keep.sh

添加之后,我们还需要重启 crontab 服务,让我们的配置生效

service cron restart

总结

本篇文章提供了一种 基于云服务器代理与虚拟专用网络的方式实现透明内网穿透系统 的技术方案,用于突破现有网络环境的限制,提供更顺畅的 学习、开发、测试环境,通过演练,我们详细的讲解并演练了如何运用 VPNpptp反向代理traefik 的基础知识来达到最终的目的,并且通过 提高系统可靠性 演练了通过 shell脚本crontab 的结合使用构建 守护进程,希望各位读者能从中学习并掌握这项技能,做到 知其已然,知其所以然