公网访问内网机器:基于frp的内网穿透


因为经常需要远程访问自己的机器,所以写一个博客记录一下

frp项目Github仓库

公网访问内网机器:基于frp的内网穿透

从公网中访问自己的私有设备向来是一件难事儿。

1. 为什么需要内网穿透?

A. 计算机网络

如何在自己的机器上访问另外一台机器?一台机器本身是一个独立的整体,可以直接访问本身所存储的文件。而如果现在有两台机器的话,机器A想要访问机器B上的文件,该怎么做?

事实上,当两台、乃至多台机器在一起的时候,就构成了一个以单台计算机为节点,以计算机和计算机之间的链接为边的网络,即计算机网络。我们访问另外一台计算机文件,就需要通过计算机网络来实现。

计算机网络中有一个比较重要的问题,就是如何标识每一台计算机。作为人,我们能很简单的知道,这是Jack Wang的Mac、那是Sarah的Windows、那是课题组公用的Linux,但是对于计算机来说,这是很难的。

所以类似于身份证能够识别不同的人一样,给组成计算机网络中的每一台设备一个编号,这个编号就作为每一台机器在网络中的标识,利用这个编号,能够找到需要访问的计算机。这个编号称为IP地址

所以,想要访问另外一台电脑,就需要知道对方的IP地址,然后才能访问。

B. 私网间互相访问

IP地址根据最初的设定,分为ABC几类。直白的来说就是有一些IP地址是保留给局域网使用(私网),剩下的IP地址全球共享(公网)

  • 公网IP地址是不能重复的。
  • 保留给局域网使用的IP地址在不同的局域网间是可以重复的。例如:你家和我家可能都有192.168.0.1这个IP地址,而使用这个IP地址的设备在你的局域网中可能是你的手机,而在我的局域网中可能是我的PC。但是,保留给局域网使用的IP地址在同一个局域网内是不能重复的。例如:我家的192.168.0.1这个IP地址只可能被一部设备使用,要么是我的手机,要么是我的PC。

因此,不同局域网内的IP地址可重复这一性质就导致了两台处于不同局域内机器互相连接可能存在问题:

  • 我们在办公室中使用的PC连接了办公室的网络,因此具有一个私网的IP地址,可能是192.168.0.1;我们的手机连接办公室的网络,因此也具有一个私网地址,可能是192.168.0.2
  • 我们需要连接的在家里的PC连接了家里的网络,因此具有一个私网的IP地址,可能是192.168.0.2

那么我们使用在办公室内的机器连接192.168.0.2这个地址的时候,我们本来想连接家里的PC,但是由于私网的IP地址可重复,我们可能会连接到同一局域网下的另外一台机器。

C. 借助公网设备作为跳板

解决问题的办法就是获得一个公网IP。因为世界上所有的网络在一起,构成了互联网,或者公网。公网IP在公网中是唯一的。所以不管我们处于哪个局域网,都可以访问到具有公网IP的机器。

所以解决方案就是:

  • 机器A和机器B都先连接到公网机器。
  • 因为连接在建立后通信是双向的,所以在机器A、B连接到公网机器后,可以和公网机器双向通信
  • 而后机器A把需要访问的机器B上的文件路径传给公网机器
  • 公网机器把文件路径通知给机器B
  • 机器B把文件传输给公网机器
  • 公网机器再把文件传输给机器A

这样利用公网机器作为跳板,就实现了处于两个私网的机器间的访问。这种解决方案称为内网穿透,即借助一个具有公网IP的服务器作为跳板,来让处于其他私网的设备访问到内网中的机器。这样做类似于把内网穿透到公网上,因此称为内网穿透.

D. 哪来的公网IP?

上面解决两个私网机器互相访问最核心的,就是要有一个具有公网IP的机器。而由于中国加入到互联网的时间比较晚,所以分到的公网IP就比较少。原来很早的时候,打电话免费就可以申请得到一个公网IP,但现在运营商因为IP地址短缺不给你分配公网IP地址。

如果花钱买的话,目前市场中常用的网络有移动宽带、联通宽带、电信宽带,不同的网络运营商提供业务办理不同,收取的费用标准也存在一定的不同。一般情况,申请公网IP大概需要1000-2000元,具体的收费标准还需要以当地各大网络运营商提供的具体解决方案为准。所以就很贵。

所以就出现了一些服务商,他们购买了公网IP,然后架设了公网机器,我们只需要购买这些服务商提供的跳板服务/穿透服务就行了。但是这样功能非常有限,只有跳板服务,可玩性和功能性不高。

因此,我们不如直接租一个具有公网IP的服务器,然后自己架设跳板服务/穿透服务即可

2. frp搭建内网穿透

A. 什么是frp

既然我们需要进行内网穿透,肯定就需要有一个程序帮助我们去完成公网、私网机器连接的建立,公网机器上消息的转发等等功能,这个软件就是frp

简单地说,frp就是一个反向代理软件,它体积轻量但功能很强大。利用他就可以实现反向穿透,即可以使处于内网或防火墙后的设备对外界提供服务。

它支持HTTPTCPUDP等众多协议。我们今天使用的仅限于TCPUDP,所以使用frp是足够了

frpc项目的Github主页

B. 机器配置信息

既然frp需要建立公网机器和私网机器之间的连接,因此frp实际上是两个程序:

  • frpc:即frp客户端(client),运行在需要被穿透到公网上的机器,即将来被远程访问的机器
  • frps:即frp服务端(server),运行在具有公网IP的机器上

而在运行的时候,frp是根据配置文件中的描述来运行的。

因此,我们未来需要在公网机器和被远程访问的机器上分别下载frp程序,并编写配置文件。

这里我的配置是:

  • 公网机器是一台腾讯云服务器,后面就简称为服务器
    • 公网地址是81.68.123.84
    • 用户名是lighthouse
    • 主机名是VM-4-7-ubuntu
  • 需要被穿透的机器(被其他私网机器访问的机器,即家里的机器)称为PC端
    • 私网IP地址不重要
    • 用户名是jack
    • 主机名是jack-Alienware-Aurora-R13

C. 下载frp

1) 服务器下载frps

首先连接进服务器下载frp服务端

ssh lighthouse@81.68.123.84
mkdir -p ~/opt/frps
cd opt/frps
wget -c https://github.com/fatedier/frp/releases/download/v0.51.0/frp_0.51.0_linux_amd64.tar.gz

注意,不同CPU架构下载的链接是不同的,我的服务器是IntelCPU,所以是x86_64架构,下载amd64版本的程序。你需要根据自己的服务器的架构去下载。

所有版本的下载地址:https://github.com/fatedier/frp/releases

下载完成

然后解压

tar xzvf frp_0.51.0_linux_amd64.tar.gz
ls -al frp_0.51.0_linux_amd64

可以看到,下载的程序是包含客户端和服务端的

因为目前是在服务器上配置frp服务端,因此删去不需要的客户端程序:

rm frp_0.51.0_linux_amd64/frpc*
ls -al frp_0.51.0_linux_amd64

服务器上只剩下frp服务端

2) PC端下载frpc

步骤和服务器上下载frps基本是一样的,这不过这里要删除的是frp服务端

mkdir -p ~/opt/frpc
cd ~/opt/frpc
wget -c https://github.com/fatedier/frp/releases/download/v0.51.0/frp_0.51.0_linux_amd64.tar.gz
tar xzvf frp_0.51.0_linux_amd64.tar.gz
rm frp_0.51.0_linux_amd64/frps*
ls -al frp_0.51.0_linux_amd64

PC端只剩下frp客户端

D. 配置frp

类似于下载,配置frp也是分为PC端配置和服务器配置。

frp工作流程:以SSH登录为例

frp的工作流程如上图。假设现在远程主机需要远程登录PC机,那么实际上就是远程主机使用PC机上的SSH程序,即访问PC机上的22端口。

所以,当远程主机的一个ssh请求来了之后:

  • 远程主机的ssh请求打到服务器上的10001端口
  • 10001端口被frps监听,检测到有一个ssh请求后,将其通过本机的6006端口发出去
  • PC端的frpc接收到远程主机发来的ssh请求后,将其转发到本机的22端口
  • 远程主机的ssh请求最终被监听PC端22端口的服务程序处理

因此:

  • PC端(frp客户端)需要指定,将自己的22号端口映射为服务器(frp服务端)的10001端口,或者说需要将服务器的10001端口的流量转发到PC端的22端口
  • 服务器(frp服务端)需要指定PC机(frp客户端)需要通过哪个端口和服务器(frp服务端)通信

可以看到,frp其实最重要的就是转发了远程主机发来的数据包,因此frp实际上工作在传输层。稍后我们在配置中就能看到,在frp的配置文件中,我们需要指定服务器的哪个端口的哪种数据包需要转发到PC端的哪一个端口。

针对上面举的SSH的例子,SSH服务本质上是一个文本传输服务,只不过进行了加密,因此SSH的数据包使用的是TCP协议。后面穿透不同的服务的时候需要指定数据包的类型

1) 服务器配置

服务器端要配置的,实际上就只有bind_port。因为listen_port是需要frp客户端指定的

ssh lighthouse@81.68.123.84
cd ~/opt/frps/frp_0.51.0_linux_amd64
vim frps.ini

将其中的bind_port修改为6006,这里是为了和上面的图配合起来,你也可以改成自己喜欢的。

cat frps.ini

frp服务端将监听服务器的6006端口来接收frp客户端的消息

服务器端的配置就结束了,最后得到的配置文件为:

# frps.ini,即服务端上的配置
[common]
bind_port = 6006

2) PC端配置

客户端的配置比服务端配置要复杂些,因为要指定远程端口的某种流量转发到本地的某个端口。

cd ~/opt/frpc/frp_0.51.0_linux_amd64
vim frpc.ini

按照上面介绍的

  • 首先设置PC端的frp客户端连接到的frp服务端的IP地址为服务器地址,即设置server_addr=81.68.123.84
  • 然后设置PC端的frp客户端连接道frp服务端监听的端口,即设置server_port=6006

接着编写一个新的规则

  • 规则名字可以随便给,这里直接就是Aurora-SSH
  • 服务端需要监听服务端的10001端口
  • 服务端转发的数据包类型就是tcp
  • 服务端转发的数据包的目的地址就是本机地址127.0.0.1
  • 服务端转发的数据包的目的端口就是22端口

最后得到的总配置文件为

注意,为了避免你的内网机器被穿透到我的服务器上,你需要修改一下server_addr,免得到时候我能登录你的内网机器\^_\^

# frpc.ini,即PC端上的配置
[common]
server_addr = 81.68.123.84
server_port = 6006

[Aurora-SSH]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 10001

所以总的来说就是:将81.68.123.84服务器上的10001端口接收到的tcp数据包转发到本地的22端口,并且通过服务器的6006端口进行通信

服务端配置文件

E. 启动frp

首先启动服务器端的frps

ssh lighthouse@81.68.123.84
cd ~/opt/frps/frp_0.51.0_linux_amd64
./frps -c frps.ini| tee -a frps.log

这里把命令行输出到信息备份一份到frps.log文件中

启动frp服务端

然后启动PC端的frpc

cd ~/opt/frps/frp_0.51.0_linux_amd64
./frpc -c frpc.ini | tess -a frpc.log

启动frp客户端

启动成功后,再检查一下服务器上的frps,发现已经接收到了frpc客户端发来的规则,开始监听端口10001

服务器上的frps开始监听端口10001

F. 验证

最后验证一下能否远程登录到PC端。

这里直接就在PC端下SSH连接服务器的10001端口,因为登录的是PC端的用户,所以用户名还需要是PC端的用户名,只不过此时SSH服务器地址已经变成了服务器的地址

ssh jack@81.68.123.84 -p 10001

成功登陆到PC端

3. 番外:PC端开机自运行frpc

最后,服务器因为我们随时可以链接进去,所以使用tmux开一个session,保持frps运行即可。但是PC端则需要我们事先手动运行frpc

如果我们运行了一些命令导致PC端重启了,那么再次启动的时候就会导致服务丢失。所以,我们希望PC端在重新启动的时候能够自动运行frpc

为此,我们使用Systemd来新建一个服务。关于Systemd后面会写点文章介绍,这里就先用吧。

Systemd介绍:

Systemd是一个用于Linux系统的系统和服务管理器。它被设计为替代传统的SysV初始化系统(init)和System V启动脚本,并提供了更先进的功能和特性。

Systemd的目标是改进系统的启动速度、效率和可靠性,并提供更好的系统管理和服务控制功能。它引入了一种并行启动机制,可以同时启动系统中的多个服务,加快系统启动时间。此外,Systemd还支持基于套接字激活的服务,可以在需要时按需启动或停止服务,提高系统资源利用率。

Systemd还引入了一种统一的单元文件(unit files)格式,用于描述系统服务、设备、挂载点等。这些单元文件位于/etc/systemd/system//usr/lib/systemd/system/等目录下,通过这些单元文件,可以对系统的各个方面进行配置和管理。

Systemd提供了一系列的命令行工具,用于管理和控制系统和服务,例如systemctl命令用于启动、停止、重启和管理系统服务,journalctl命令用于查看系统日志。

总的来说,Systemd是一个现代化的系统和服务管理器,它在Linux系统中扮演着重要的角色,改进了系统的启动和管理方式,提供了更好的性能和功能。它已经成为许多主流Linux发行版的默认初始化系统。

A. 编写启动脚本

首先编写一个脚本来运行frpc

cd ~/opt/frpc
# 创建一个软连接,方便后面更新版本
ln -s frp_0.51.0_linux_amd64 bin
vim start_frpc.sh

内容如下:

#! /bin/bash

run_folder=$(cd $(dirname $0) && pwd || exit)
bin_folder="${run_folder}/bin"

cd ${bin_folder} && ${bin_folder}/frpc -c ${bin_folder}/frpc.ini  2>&1 | tee -a ${run_folder}/frpc.log

运行这个脚本就可以一键化直接运行frpc

chmox 755 start_frpc.sh
./start_frpc.sh

脚本启动frpc

B. 开机运行启动脚本

编写完了开机脚本还不行,还需要开机就运行这个脚本。后面就是通过Systemd来设置开机运行这个脚本了。开机自运行的Systemd任务需要放在/usr/lib/systemd/system目录下

首先新建一个Systemd任务,名字为frpc-client,而后创建软连接方便修改

cd ~/opt/frpc
sudo touch /etc/systemd/system/frp-client.service
ln -s /etc/systemd/system/frp-client.service ./
sudo vim frp-client.service

然后编写内容如下:

# 这个部分定义了服务的基本信息, 这里是一个启动frpc的服务
[Unit]
Description=开机自动运行frpc任务
# 必须要在网络启动之后才能运行, 因为Systemd是多线程运行服务的
After=network.target
# 同时该任务以来网络, 因此声明启动网络服务是该服务的依赖
Wants=network.target



# 这个部分定义了服务的运行参数和行为
[Service]
# 指定了服务的类型,这里是simple,表示是一个简单的服务,即执行指定的命令或脚本。
Type=simple
# 指定了运行服务的用户,这里是jack,表示以jack的身份运行服务
User=jack
# 无论是由于错误、异常退出还是手动停止,只要服务退出,它都应该立即重新启动。这确保了服务的持续性,即使出现问题导致服务停止,Systemd也会自动重新启动它,以恢复服务的正常运行。指定为always确保了下面的脚本会一直运行
Restart=always
# 指定了服务在重启之前的等待时间
RestartSec=5s
# 指定了服务可以打开的最大文件描述符数,这里是1048576
LimitNOFILE=1048576
# 指定了要运行的命令或脚本的绝对路径
ExecStart=/bin/bash /home/jack/opt/frpc/start_frpc.sh
# 服务重新加载(reload)时执行的命令或脚本。
ExecReload=/bin/bash /home/jack/opt/frpc/start_frpc.sh

# 这个部分定义了服务的安装和启动配置
[Install]
# 下面这段不能少, 强制要求了在用户登录前运行启动脚本
WantedBy = multi-user.target

然后把这个启用这个任务

# 因为新建了一个开机启动任务,所以重新加载一下systemd守护进程
sudo systemctl daemon-reload
sudo systemctl enable frp-client.service

启用frpc任务

然后查看一下这个任务的状态

sudo systemctl status frp-client.service

因为目前只是加载了这个任务(表示启动时将会运行这个任务),而由于上次启动时还没运行这个任务,所以显示的是dead。

查看frpc任务

紧着这,启动一下这个服务

sudo systemctl start frpc-client.service
sudo systemctl status frpc-client.service

启动frpc任务

接下来重启一下PC端就行了,此时这个任务就会被运行

sudo reboot
# 重启后检查一下任务状态
sudo systemctl status frp-client.service

开机运行frpc,赢!


文章作者: Jack Wang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jack Wang !
  目录