前言
下个月就要回家过年了。为了在家也能用 SSH 访问实验室服务器,我决定尝试用 frp 做内网穿透。
frp(fast reverse proxy)是一个专注内网穿透的开源应用,包含客户端和服务端。内网穿透需要一台拥有公网 IP 的服务器运行 frp 服务端来作为中转站,并在一台内网设备上运行 frp 客户端;此时公网中的设备就可以通过访问中转站来与内网通信。在热心同门的指导下,我通过学生认证申请了一台阿里云的 ECS 云服务器作为中转服务器。
服务端
下载 frp
首先获取最新版本的 frp:
1 2 3
| wget https://github.com/fatedier/frp/releases/download/v0.66.0/frp_0.66.0_linux_amd64.tar.gz tar -zxvf frp_0.66.0_linux_amd64.tar.gz mv frp_0.66.0_linux_amd64 frp
|
下载并解压完成后,可以看到 frpc、frpc.toml、frps、frps.toml,分别是 frp 的客户端程序、客户端配置、服务端程序、服务端配置。
自 v0.52.0 版本起,frp 支持 .toml、.yaml 和 .json 格式的配置文件,而旧版常见的 .ini 文件将被逐渐弃用。
配置 frps
以 .toml 为例,我们编辑服务端配置文件 frps.toml:
1 2 3 4 5 6 7 8 9 10 11 12
| bindAddr = "0.0.0.0" bindPort = *****
auth.token = "******"
webServer.addr = "0.0.0.0" webServer.port = 7500 webServer.user = "*****" webServer.password = "*****"
|
完成配置以后,还需要注意在防火墙上开放端口,并在阿里云的控制台上设置安全组规则。
最后直接后台运行 frps 即可:
1
| nohup ./frps -c frps.toml &
|
以系统服务方式运行 frps
为了长期使用方便,还可以将 frps 添加为系统级别 systemd 服务,实现开机自启。创建 /etc/systemd/system/frps.service:
1 2 3 4 5 6 7 8 9 10 11 12
| [Unit] Description=FRP Server Service After=network.target
[Service] Type=simple ExecStart=/root/frp/frps -c /root/frp/frps.toml Restart=on-failure RestartSec=5s
[Install] WantedBy=multi-user.target
|
然后重载配置,设置自启动并激活服务:
1 2 3 4
| sudo systemctl daemon-reload sudo systemctl enable frps sudo systemctl start frps sudo systemctl status frps
|
如果之后修改了相关配置,需要重启服务:
1
| sudo systemctl restart frps
|
客户端
下载 frp
同样地,先获取最新版本的 frp:
1 2 3
| wget https://github.com/fatedier/frp/releases/download/v0.66.0/frp_0.66.0_linux_arm64.tar.gz tar -zxvf frp_0.66.0_linux_arm64.tar.gz mv frp_0.66.0_linux_arm64 frp
|
配置 frpc
同样地,编辑 frpc.toml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| serverAddr = "***.***.**.***" serverPort = ***** auth.token = "**********"
[[proxies]] name = "ssh-spark" type = "tcp" localIP = "127.0.0.1" localPort = 25252 remotePort = 10517 transport.useEncryption = true transport.useCompression = true
[[proxies]] name = "ssh-223" type = "tcp" localIP = "10.82.1.223" localPort = 8864 remotePort = 10223 transport.useEncryption = true transport.useCompression = true
|
配置完成后,同样后台运行即可:
1
| nohup ./frpc -c ./frpc.toml &
|
以系统服务方式运行 frpc
同样地,我们可以将 frpc 添加为系统级别 systemd 服务。
如果只添加为用户级别服务,服务有可能会在用户所有会话都退出时被终止。幸运的是,我在内网中恰巧有一台拥有 sudo 权限的设备。
创建 /etc/systemd/system/frpc.service:
1 2 3 4 5 6 7 8 9 10 11 12
| [Unit] Description=FRP Client Service After=network.target
[Service] Type=simple ExecStart=/home/omniai-1/frp/frpc -c /home/omniai-1/frp/frpc.toml Restart=on-failure RestartSec=5s
[Install] WantedBy=multi-user.target
|
与 frps 同样的操作:
1 2 3 4
| sudo systemctl daemon-reload sudo systemctl enable frpc sudo systemctl start frpc sudo systemctl status frpc
|
至此,就可以通过访问中转服务器的不同端口来访问内网的各个设备了!
安全措施
然而,进行内网穿透必须考虑到安全性——将内网设备服务端口暴露在公网中是一个有风险的行为。我意识到,虽然我的 frp 客户端设备已经设置了 SSH 仅密钥登录,但其代理的另一台共享服务器的 SSH 配置是不受我控制的,所以依然是存在一定风险的。
目前的问题是,如果我在公网中的 IP 不固定,无法在 frps 上设置 IP 白名单,frps 就无法识别连接是来自我本人的还是来自攻击者的。针对这个问题,最理想的解决方案是采用 sTCP(secret TCP)模式。该模式的核心逻辑是:不在公网服务器上直接开放服务端口,而是通过一个独立的加密控制通道进行连接验证;只有运行着 visitor 模式的 frpc 并配置了正确密码的设备才能与 frps 建立连接,然后将服务端口映射到自身的端口上。
采用 sTCP 模式后,frp 服务端配置不需要进行修改,只需要修改客户端配置 frpc.toml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| serverAddr = "***.***.***.***" serverPort = ***** auth.token = "**********"
[[proxies]] name = "ssh-spark" type = "stcp" secretKey = "**********" localIP = "127.0.0.1" localPort = 25252
transport.useEncryption = true transport.useCompression = true
[[proxies]] name = "ssh-223" type = "stcp" secretKey = "**********" localIP = "10.82.1.223" localPort = 8864
transport.useEncryption = true transport.useCompression = true
|
然后重启 frpc 服务。
对于需要访问内网的公网设备,需要下载 frp,并配置 frpc.toml 为 visitor 模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| serverAddr = "***.***.***.***" serverPort = ***** auth.token = "**********"
[[visitors]] name = "visitor-spark" type = "stcp" serverName = "ssh-spark" secretKey = "**********" bindAddr = "127.0.0.1" bindPort = 10517 transport.useEncryption = true transport.useCompression = true
[[visitors]] name = "visitor-223" type = "stcp" serverName = "ssh-223" secretKey = "**********" bindAddr = "127.0.0.1" bindPort = 10223 transport.useEncryption = true transport.useCompression = true
|
然后运行 frpc,即可直接在本地端口上访问 frp 代理的服务。