NPS 内网穿透教程 / 入门+进阶

0x00 介绍

nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发,可支持任何tcp、udp上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还支持内网http代理、内网socks5代理、p2p等,并带有功能强大的Web管理端

相比于已经较为成熟的FRP、Ngrok等,这个内网穿透工具的功能更加强大,以及可以更方便地进行配置(因为有Web面板)。我在改造内网时使用过前面两者,但都不是很方便,而最后找到了NPS,发现它特别简单好用而且还满足我的需求,于是就用了。

关于内网穿透的原理、原因我就不多说了,要实现内网穿透我们至少需要以下两个主机:

  • 一台有公网IP的服务器(VPS)运行服务端(NPS
  • 一个或多个运行在内网的服务器或者PC运行客户端(NPC

笔者写这篇教程的服务器运行Debian系统(如果你用Ubuntu也是一样的),客户端是运行Raspbian(也是基于Debian)的树莓派4B。

0x01 安装

项目Release地址: https://github.com/cnlh/nps/releases

请注意将下面的下载地址更换成最新的,写这篇教程时的最新版是0.23.2

服务端

一般情况下VPS都是用的X86_64架构的CPU,所以下载服务端可以直接找到linux_amd64_server.tar.gz下载,然后解压到相应位置,例如在root用户下:

cd ~
wget https://github.com/cnlh/nps/releases/download/v0.23.2/linux_amd64_server.tar.gz
tar xzvf linux_amd64_server.tar.gz
cd ~/nps

在nps目录下面会有一个nps可执行文件、conf配置目录和web网页目录,我们只需要修改conf/nps.conf即可:

vim conf/nps.conf

这里我们需要改一下#web下面的几个参数,

web_host=你的服务器IP或者域名
web_username=admin(或者换成别的)
web_password=你的密码
web_port=8080(可以改成你想要的端口)

其他貌似也不需要怎么修改,为了安全起见还是不要用默认用户名和密码。关于具体的配置可以看项目README

修改完成之后,直接启动服务器(默认daemon方式启动):

./nps start

对于一些有防火墙的服务器,需要将所需端口打开(网页8080、客户端连接端口8024),如果你使用域名,还需要提前将域名正确解析到VPS的IP上。至此服务端启动完成,接下来我们打开Web界面,公网ip:web界面端口(默认8080),用你刚才设置的用户名和密码登录,然后你会看到类似这样的UI:

主界面

之后我们点开左边的“客户端”,再点击“新增”,备注就是给客户端起个名字,比如raspberrypi,其他均可以默认(之所以不开压缩和加密是因为我觉得这两个事情不应该交给中间服务器解决,而应该在相应的服务上开启,比如NGINX)。新建之后就可以看到列表里面多了一个客户端,不过是offline状态(当然,我们还没开始配置呢),点一下左边那个+号,你会看到一些信息,比如当前连接数等等,不过最重要的是命令一行,后面的一串命令记下来,还有客户端的id,待会儿要用。

客户端

就像这样:

./npc -server=你的IP:8024 -vkey=一串神秘字符 -type=tcp

客户端

这里客户端使用树莓派4B,其他平台都是类似的。

还是到刚才Release那里找到对应平台的客户端文件,因为树莓派Raspbian是ARM的,所以需要下载linux_arm_client.tar.gz,如果你是Windows平台则下载win_amd64_client.tar.gz(现在应该没有用i386也就是32位系统的PC了吧),以及Mac、Linux等都找到对应的_client.tar.gz即可。

cd ~
wget https://github.com/cnlh/nps/releases/download/v0.23.2/linux_arm_client.tar.gz
tar xzvf linux_arm_client.tar.gz
cd nps

这个解压出来就只有npc和npc.conf两个文件了,我们这里并不需要修改npc.conf,还记得刚才的那个命令么?对就是它。复制过来,然后运行即可。为了让它在后台运行,我用了screen命令。

screen -R NPC # 新建一个名叫NPC的会话
./npc -server=你的IP:8024 -vkey=一串神秘字符 -type=tcp #就刚才那个命令,复制过来就好了

然后按住Ctrl,依次按A和D,这样我们就和NPC这个会话分开了,但它会一直在后台运行,即使我们退出SSH。

这个时候回到服务端的Web界面,如果连接正常,你会发现它在线了。

0x02 配置

服务端和客户端都设置好后,我们就已经搭好了一座桥了,接下来针对一些我会用到的场景来讲解以下配置。

远程访问路由器后台

假设我的树莓派内网的路由器地址在192.168.1.1,而我想用router.copperion.test远程访问,那么就在Web界面打开“域名解析”,点击“新增”,备注写一个名字比如router,域名填写router.copperion.test(记得将相应的域名解析到VPS的IP,当然你也可以像我一样将*.copperion.test解析到VPS上,需要DNS服务商支持通配符域名),协议类型all(或者只用http或https其中一种也可以),url路由可留空(如果写了/rt,那么访问路由器后台就是router.copperion.test/rt这样的,很好理解),客户端ID填刚才新建时看到的ID,内网目标就填写192.168.1.1:80(如果有https端口就是443),其他默认即可,然后确认新增,这样就新建了一个域名解析。

这样就可以随时随地通过http://router.copperion.test来控制我的路由器啦!

远程SSH

有时在外面突然想ssh进树莓派想跑个东西怎么办?用TCP隧道,点击“新增”,类型为tcp,备注还是自己取名字比如ssh to pi,服务端端口设置为远程连接的端口(比如设置为8222,那么我连接的时候就是用VPS的IP:8222来进行连接),而目标IP则填写127.0.0.1:22(因为树莓派就是客户端,然后sshd的端口为22),客户端ID还是刚才那个ID。

新建好之后,就可以用ssh pi@公网地址 -p 8222来ssh上树莓派了。

其他

此外也可以有UDP隧道、P2P等其他强大的功能,大家可以参考项目的README来进行配置。

0x03 进阶操作

需求

这部分主要讲解我的树莓派在内网工作时如何配置得更舒服。我的目标是,将内网与外网的DNS分开,这样我在内网时,*.copperion.test直接解析到树莓派内网IP上,而我在外网时,让它解析到VPS上进行中继,在这样的基础上还要实现全HTTPS访问以保证安全性,同时根据域名的不同转到不同的服务里去,而且不改端口(80和443都被电信给屏蔽了)。

在我的树莓派上有几个容器化的服务,是用docker-compose组织的:

  • NextCloud 私有云 => nc.copperion.test
  • Aria2 远程下载 => dl.copperion.test
  • Bitwarden 密码管理 => bw.copperion.test
  • ……

我创建了一个外部网络docker network create proxy --subnet 172.18.0.0/16,然后在docker-compose.yaml里为每个服务单独指定一个静态的IP,例如:

services:
    nextcloud:
        image: nextcloud:latest
        ......
        networks:
            - default:
            - proxy:
                ipv4_address: 172.18.0.2
        ......
    aria:
        image: ......
        networks:
            - default:
            - proxy:
                ipv4_address: 172.18.0.3
        ......
    ......

networks:
  proxy:
    external: true #使用外部网络

这样每一个服务都有其对应的IP,每个服务下面可以有多个端口,就不会显得很混乱。

在只有内网时很简单,只需要一个反向代理即可,我用的是Traefik,具体可以看我的这篇文章在树莓派上搭建完全Docker化的NextCloud个人云盘,只需为docker-compose.yaml里的每个服务加label来设置前端URL规则即可,另外Traefik也支持自动向Let’s Encrypt申请HTTPS证书,这样实现自动跳转HTTPS也非常方便。

但是做外网时我踩了很多坑,比如将域名路由改成URL路由(name.copperion.test这样的改成copperion.test/name),这样在NPS里只需要设置一个转发就可以了,但是这样在树莓派上效果不好,Traefik的一些设计问题导致copperion.test/namecopperion.test/name/访问效果不一样,会造成一些服务打不开(比如Phpmyadmin虽然200但里面的静态文件全挂了)。

而将NPS为每个服务创建一个域名解析的话,又无法实现HTTPS以及跳转。

那咋整?

两个反向代理

正常的内网穿透流程是: 用户 - NPS服务端(VPS) - NPC客户端 - Docker - 服务,在不修改这个的前提下无法满足需求,那么就加一个吧23333。既然我们可以用NPS为每一个服务创建一个域名解析(解析到刚才创建的Docker网络上,也就是172.18.0.x的地址),而它仅仅只是不支持HTTPS而已,那么为何不在VPS前面再加一个反向代理呢?经过查看NPS的文档,我发现确实可以这样做:

在nps.conf中将https_just_proxy设置为true,并且打开https_proxy_port端口,然后nps将直接转发https请求到内网服务器上,由内网服务器进行https处理。

那么按照上面的提示,我们在nps.conf中找到以下字段并更改:

#HTTP(S) proxy port, no startup if empty
http_proxy_ip=0.0.0.0
http_proxy_port=10080  #这个端口是NGINX待会儿要转发的目的端口
https_proxy_port=  #这里一定要留空
https_just_proxy=true

接着在NGINX中新建一个配置文件,大概是这样:

# /etc/nginx/sites-avaliable/ssl-copperion.test.conf

server {
    listen 443 ssl http2 default_server;
    server_name copperion.test *.copperion.test;
    include snippets/ssl-copperion.test.conf;
    include snippets/ssl-params.conf;

    location / {
        proxy_set_header Host  $http_host;
        proxy_pass http://127.0.0.1:10080;
        client_max_body_size 0;
    }
}
# /etc/nginx/snippets/ssl-copperion.test.conf

ssl_certificate /etc/letsencrypt/live/copperion.test/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/copperion.test/privkey.pem;
#这里使用了Let's Encrypt生成的泛域名证书
# /etc/nginx/snippets/ssl-params.conf

# from https://cipherli.st/
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable preloading HSTS for now.  You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
# add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
ssl_dhparam /etc/ssl/certs/dhparam.pem;

为NGINX启用这个配置文件并重启NGINX服务,就做好了VPS端的HTTPS。

接下来回到树莓派客户端,我们在Docker服务的上面加一个Traefik反向代理(这个反代用在Docker或者K8s之类的集群上很好用,而且还有服务发现),关于Traefik的设置以前那个文章里已经提到了,这里我们需要修改一下,让它用我们刚才创建的SSL证书。

把刚才在VPS上用Let’s Encrypt生成的证书和密钥文件下载到客户端上,将traefik.toml里面的EntryPoints修改一下:

[entryPoints]
   [entryPoints.http]
   address = ":80"
     [entryPoints.http.redirect]
     entryPoint = "https"
   [entryPoints.https]
   address = ":443"
     [entryPoints.https.tls]
       [[entryPoints.https.tls.certificates]]
       certFile = "/etc/traefik/copperion.test/fullchain.pem"  #这里写你的证书文件的目录,如果用Docker注意配置好Volume
       keyFile = "/etc/traefik/copperion.test/privkey.pem"

之后再重新启动一下docker上的各个服务,完成之后我们的本地反代也完成了。

本地劫持DNS

那么我们还有一件事情要做,DNS默认解析到VPS上,那怎么让我在内网时解析到内网呢?也许你已经知道我要说什么了,就是劫持DNS。

我们需要在路由器上建一个本地的DNS服务器,然后专门将*.copperion.test解析到树莓派内网IP(例如192.168.1.101),笔者使用的是PandoraBox路由器固件(基于OpenWRT),我们ssh上去,修改如下文件:

# /etc/hosts

192.168.1.101 copperion.test
192.168.1.101 www.copperion.test
192.168.1.101 nc.copperion.test
192.168.1.101 tk.copperion.test
192.168.1.101 pma.copperion.test
192.168.1.101 dl.copperion.test
192.168.1.101 rpc.copperion.test
......

由于host文件的限制,我们只能为每个子域名单独配置。

# /etc/dnsmasq.conf

......
address=/.copperion.test/192.168.1.101
address=/copperion.test/192.168.1.101
#在文件末尾加上就可以了

之后还要修改接口的DNS,有两个方法,一个是在路由器Web后台-网络-接口-WAN-高级设置,取消勾选“使用对端通告的 DNS 服务器”,然后在下面依次填上127.0.0.1、223.5.5.5、223.6.6.6,后面两个换成你喜欢的DNS皆可,一定要让本地DNS在最前面。第二个方法是直接修改/etc/config/network,找到wan对应的配置,加入一行

option dns '127.0.0.1 223.5.5.5 223.6.6.6'

即可。

DNS修改

重启路由器,这样DNS劫持的部分也做好了。

接下来分别用内网机器和外网机器(比如连4G的手机)ping域名,如果返回的正确的IP,那么再打开浏览器依次验证各个子域名是否正常工作,顺利的话我们就已经完成目标啦。

0x04 总结

终于搞好了,开心(^・ω・^§)ノ,相比FRP,我觉得NPS的Web控制就很方便,至少给了我更多的时间去折腾后面的内容,然后早日实现了,现在我NAS在树莓派上,内网时候访问用1000Mpbs带宽,而在外网依然可以正常使用(虽然慢了点),这也是为什么我不想出宿舍(误)

好的以上就是这篇教程的内容,如果有纰漏或者错误或者问题欢迎提出讨论~

本页面的全部内容在 CC BY-NC-SA 4.0 协议之条款下提供,附加条款亦可能应用
本文链接:https://www.copperion.com/2019/nps-tunnel-tutorial/