魔百盒折腾记——内网穿透(实操篇)
系列文章
上一篇,我们聊了内网穿透的原理,这一篇我们聊一聊内网穿透的实际搭建步骤以及一些典型的应用。对于个人而言,内网穿透是基础的基础,没了内网穿透,很多事情想做也做不成。包括我们现在所熟知的各种智能设备,都是需要使用到内网穿透技术的。
内网穿透真的做到了“让天下没有难以访问的内网设备”。有了内网穿透,你也就可以随时随地控制自己的电脑;有了内网穿透,你也就可以把你的任意一台电脑变成一个服务器。想要搭建自己的博客网站?想要搭建自己的视频网站?想要搭建自己的图床?想要给自己开发的app部署一个后端服务器?有了内网穿透,你都可以轻松做到。
1. 软件下载
如果你对花生壳之类的商业软件感兴趣的话,无需继续读下去,直接联系其官方客服即可。本篇以frp为例讲解内网穿透的实际实现方法。如果你更喜欢nps,等搞明白了frp后,可以很轻松的根据其项目网站的文档依葫芦画瓢地搭建出来。frp和nps在客户端没有任何区别,只是配置方式和管理端界面有些不同而已。它们的下载地址为:
软件 | 项目地址 | 特点 |
---|---|---|
frp | https://github.com/fatedier/frp | 开发活跃,配置简单 |
nps | https://github.com/ehang-io/nps | 支持硬件和系统更多,甚至支持安卓。 |
无论是frp还是nps,通常找最新版本下载即可。这两个软件的稳定性都是非常不错的。另外,服务端和客户端可以是不同的硬件架构,比如服务器是x64,客户端是arm64,只要版本相同,就可以互联互通。版本不同的话,要看一下官方说明哈。
2. 服务端
服务端必须部署在具有公网IP的服务器上面的。你可能会有疑问,既然我已经有了一个有公网IP的服务器了,为啥还需要内网穿透呢?比如我想搭建一个网站,直接把网站搭建在这个服务器上面不就好了。关于这个疑问,我们稍后作答。
想要满足这个条件有以下几种办法:
-
方法一:跟宽带服务商斗智斗勇,突破层层障碍,拿到公网IP(具体方法请移步百度哈)。如果你用的话,电信或者联通的宽带,还是有可能做到的。如果是用的是移动的宽带,可能可以拿到IPv6的公网IP,但是,IPv4就不用想了。如果不是这三家,大概率啥都拿不到哈。
拿到公网IP以后,再使用openwrt或者Padavan系统的路由器,让内网中的某台设备暴露在互联网中。那这台设备便是一个具有公网IP的服务器了。
需要特别留意的是,设备暴露在互联网上面是一个蛮危险的行为。建议关闭ssh服务,关闭不必要的端口,以便增强安全性。设备最好是一些性能比较差的,比如arm小盒子之类的设备。即使被攻破,也因为性能差或者指令集原因,而被嫌弃。至少不会被人拿来挖矿,白白浪费自己家里的电。
-
方法二:花点小钱买个云主机。比如每年双11时,各大云服务厂商都会高新用户特价活动,1折的价格还是可以接受的。需要留意的是,对于咱们这个需求而言,ECS主机和轻量应用服务器没有区别。一般来说,轻量应用服务器性价比会更高一些。
这个方法的好处是实现起来简单。但是,需要花钱不说,带宽还会受到蛮大限制的。云服务商的带宽是很贵的。
-
方法三:去网上找免费的frp服务端,比如https://freefrp.net/。至于安全和稳定性,自己考量哈。
现在,回答一下本章节开始的那个疑问。
如果你是通过方法一拿到的公网IP,那么你暴露在公网上的设备一定会是低性能的设备。而且,该设备的安全系数是比较低的。所以,不推荐将应用直接部署在这台设备上面。而是将这台设备当成DMZ区使用,做好防护隔离,让自己的高性能设备免受互联网的侵害。
如果你是通过方法二拿到的公网IP,大概率也是低性能的云主机。尤其是特价云主机通常有时间限制,过期后老用户续费往往会非常贵。下一年也就可能在换个身份购买1折云主机了。如果拿来部署服务,每年需要迁移不说,性能还很差。一旦,你需要GPU、高性能、大内存,价格是非常非常昂贵的。
所以,无论如何,你都需要内网穿透技术,来省钱拓展。
这儿假设,你已经通过方法一或者方法二,准备好了一个具有公网IP的服务器。接下来你需要去下载,对应硬件架构的包。
$ wget https://github.com/fatedier/frp/releases/download/v0.46.1/frp_0.46.1_linux_amd64.tar.gz
$ tar -xf frp_0.46.1_linux_amd64.tar.gz
$ sudo mv frp_0.46.1_linux_amd64 /opt
$ tree frp_0.46.1_linux_amd64
frp_0.46.1_linux_amd64
├── frpc
├── frpc_full.ini
├── frpc.ini
├── frps
├── frps_full.ini
├── frps.ini
└── LICENSE
0 directories, 7 files
其中,frps
就是服务端,frps.ini
是服务端的配置文件。运行方法很简单,下面的命令即可:
./frps -c frps.ini
但是,默认给的frps.ini
中没有任何安全性。这样子运行,等于把frps开放给所有人使用。所以,可以做以下配置:
[common]
# 改掉默认端口,以尽量防止定向攻击。
bind_port = 7171
bind_udp_port = 7172
vhost_https_port=7173
vhost_http_port=7174
# 记录日志,以便出现问题时排查
log_file = /var/log/frps/frps.log
# trace, debug, info, warn, error
log_level = info
log_max_days = 30
# 设置token,这样只有客户端配置了正确的token才可以连接上来。注意,token一定要是难以猜到且比较复杂。
token=12345678
# 如果你有域名的话,可以在这儿设置一个域名。这样网站可以使用多级子域名。
subdomain_host = test.mydata.top
# 允许客户端使用的端口号
allow_ports=6000-9000
# 限制允许的连接数,以防服务端扛不住哈
max_ports_per_client=50
max_pool_count=10
另外,由于frps
需要7*24小时运行。所以,一般不要直接通过命令启动它,而是写一个进程守护脚本启动它。例如,启动脚本/opt/frp_0.46.1_linux_amd64/start.sh
,内容如下:
#!/bin/bash
SOURCE="$0"
while [ -h "$SOURCE" ]; do
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
SRC_PATH="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $SRC_PATH
if [ ! -e /var/log/frps ]; then
mkdir -p /var/log/frps
fi
while [ true ]; do
./frps -c frps.ini
sleep 10
done
然后,使用以下命令启动该脚本:
nohup bash ./start.sh >/dev/null &
这样启动后,即使frps
因为某些原因崩溃了,也会在10秒后被自动拉起来。但是,如果系统被重启了,frps还是无法自动启动。所以,我们需要把它变成一个service。让系统启动时自动启动frps。
首先,我们创建文件/usr/lib/systemd/system/frps.service
,内容为:
[Unit]
Description=frp server
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
ExecStart=/opt/frp_0.46.1_linux_amd64/start.sh
# ExecReload=target_dir/restart.sh
# ExecStop=target_dir/shutdown.sh
SuccessExitStatus=0
[Install]
WantedBy=multi-user.target
然后,执行下面的命令:
sudo systemctl daemon-reload # 重新加载配置
sudo systemctl enable frps # 让frps服务开机自动启动
sudo systemctl start frps # 立即启动frps服务
3. 客户端
相比服务端,客户端就没有什么要求了。可以是frp或者nps支持的任意系统或架构。当然,肯定不需要有公网IP了。如果客户端的设备有公网IP的话,谁还用内网穿透啊,直接访问不就好了。
3.1 基本使用方法
接下来,我们可以通过frpc
来连接我们的客户端了。同样的,我们可以简单的使用,./frpc -c frpc.ini
运行客户端。但是,因为我们的服务端配置了token,也改了端口号,所以,这儿会报错。
$ wget https://github.com/fatedier/frp/releases/download/v0.46.1/frp_0.46.1_linux_arm64.tar.gz
$ ./frpc -c frpc.ini
2023/01/10 23:57:39 [W] [service.go:133] login to server failed: dial tcp 127.0.0.1:7000: connect: connection refused
dial tcp test.mydata.top:7000: connect: connection refused
即使,把端口号改正确了,也会报错。
2023/01/10 23:58:48 [E] [service.go:289] token in login doesn't match token from configuration
2023/01/10 23:58:48 [W] [service.go:133] login to server failed: token in login doesn't match token from configuration
token in login doesn't match token from configuration
正确的配置,至少应该是:
[common]
server_addr = test.mydata.top
server_port = 7171
token = 12345678
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
其中,token需要跟服务端设置的token完全相同。接下来再运行,就能看到正确的信息了:
$ ./frpc -c frpc.ini
2023/01/11 00:00:15 [I] [service.go:298] [f37327838287a046] login to server success, get run id [f37327838287a046], server udp port [7172]
2023/01/11 00:00:15 [I] [proxy_manager.go:142] [f37327838287a046] proxy added: [ssh]
2023/01/11 00:00:15 [I] [control.go:172] [f37327838287a046] [ssh] start proxy success
现在,你就可以在任意一台联网的设备(这台设备被称为:访客端)上通过下面命令连接这台电脑了。
$ ssh -p 6000 username@test.mydata.top
实际上,你是先连接了frps
进程的6000端口,然后,再通过6000端口连接上了22端口。
在上一节中,我通过frp对ssh的端口实现了内网穿透。实际上,通过这种方式,我们可以对任意基于TCP协议的端口实现内网穿透。甚至包括http协议。
另外,为了增强安全性,可以还可以针对端口进行加密。为了减少带宽占用,还可以对数据包进行压缩。还可以防止中断,增加心跳检查。另外,不仅仅支持tcp协议,还支持udp、http、https等各种协议。配置方法在安装包的frpc_full.ini
里面都有。通常我们依葫芦画瓢即可。
不过,需要注意的是加密和压缩都只存在于frps和frpc的通信过程中,访客端与frps之间的通信既不加密也不压缩的。
通过frps转发时的时序图为:
3.2 安全连接
由于在普通模式下,只有服务端和客户端之间可以加密和压缩。访客端和服务端无法进行加密和压缩。所以,安全性不高,通信效率也偏低。于是,frp提供了一种安全连接的方式来解决这个问题。那就是在访客端再运行一个frpc,这样访客端的用户程序只是跟访客端的frpc通信。而访客端的frpc和frps之间也可以进行加密和压缩了。这样一来,访客端、服务端、客户端之间的所有通信就都是加密的了。
这种模式下的通信时序图为:
-
客户端的配置方法
[secret_tcp] type = stcp sk = abcdefg local_ip = 127.0.0.1 local_port = 22 use_encryption = true use_compression = true
-
服务器端不需要特别配置。
-
访客端的配置方法
[secret_tcp_visitor] role = visitor type = stcp server_name = secret_tcp sk = abcdefg bind_addr = 127.0.0.1 bind_port = 2022 use_encryption = true use_compression = true
接下来,我们在访客端打开一个终端程序,执行:
ssh -p 2022 username@127.0.0.1
即可连接到客户端,而且全程通信都是安全的。
3.3 点对点直连
默认情况下,我们所有的数据包都是通过frps服务器转发的。也就是说,frps是个代理。这就导致速度慢一倍。如果服务器的带宽有限的话,那就更是个问题了。有没有方法让数据包不通过frps走呢?答案是肯定的。frp提供了一种p2p模式,让双方不再通过frps转手,而是直接发送给对方。frps只是个介绍人,在建立连接期间,让双方都认识对方,这样双方也就可以直接通信了。没有中间商,效率也就高效多了。
在p2p模式下,通信时序图为:
在这种方式下,访客端也需要运行一个frpc,以便跟客户端的frpc建立连接。从时序图上看,访客端的frpc扮演了frps转发数据包的功能。但是,因为访客端的frpc跟访客端的程序在同一台设备上,不需要走互联网。速度和带宽都是非常大的,延时也可以忽略不计。而访客端的frpc与客户端的frpc是点对点直连的,所以,速度要比frps转发快很多。
-
客户端的配置方法
[p2p_tcp] type = xtcp sk = abcdefg local_ip = 127.0.0.1 local_port = 22 use_encryption = true use_compression = true
其中,sk是一个安全码,客户端与连接端需要匹配,可以进一步提高安全性。跟tcp协议不同的是,因为是直连,这儿不需要指定一个frps侧的端口号。
-
服务器端不需要特别配置。
-
访客端的配置方法
[p2p_tcp_visitor] role = visitor type = xtcp server_name = p2p_tcp sk = abcdefg bind_addr = 127.0.0.1 bind_port = 2022 use_encryption = true use_compression = true
需要留意的是,访客端是通过server_name来到frps上取连接信息的。
接来下,我们在访客端打开一个终端,执行:
ssh -p 2022 username@127.0.0.1
即可连接到客户端了。因为是直连,所以,这种通信要比普通模式和安全连接模式都快很多。缺点就是,某些网络环境下可能会连接失败。
4. 仪表盘
frps是有一个web仪表盘,可以在web页面上查看frps上面的一些信息。只需要在frps.ini中配置一下:
[common]
...
dashboard_port = 7071 # 端口号
dashboard_user = admin # 用户名
dashboard_pwd = 1qaz-7ujm # 密码
...
然后,就可以在浏览器中查看frps中的各种端口信息了。