跳转至

使用 frp 进行内网穿透

预计阅读时长 : 6 分钟

简介

frp ⧉ 是一个简单、高效的内网穿透工具,支持多种代理类型以及 P2P 通信,为不同场景下的需求提供丰富的解决方案。

frp 采用 C/S 模式,将服务端部署在具有公网 IP 的机器上,客户端部署在内网或防火墙内的机器上,通过访问暴露在服务器上的端口,反向代理到处于内网的服务。 在此基础上,frp 支持 TCP, UDP, HTTP, HTTPS 等多种协议,提供了加密、压缩,身份认证,代理限速,负载均衡等众多能力。此外,还可以通过 xtcp 实现 P2P 通信。

安装

frp 支持多种平台,包括 Linux, Windows, Mac, FreeBSD 等,同时提供了多种安装方式,包括二进制包、OpenWrt、Docker等。

Binary

直接运行二进制包是官方推荐的使用方式,需要先从 GitHub fatedier/frp 的 Release ⧉ 页面中下载最新版本的客户端和服务器二进制文件。所有文件都打包在一个压缩包中,还包含了一份完整的配置参数说明。

然后,解压下载的压缩包,将 frpc 复制到内网服务所在的机器上,以及将 frps 复制到拥有公网 IP 地址的机器上,并将它们放在任意目录。

接着,编写配置文件,目前支持的文件格式包括 TOML/YAML/JSON。具体的配置文件格式可以参考 配置文件说明 ⧉

最后,启动 frps 和 frpc。启动命令如下:

  • 使用以下命令启动服务器:./frps -c ./frps.toml。
  • 使用以下命令启动客户端:./frpc -c ./frpc.toml。
  • 如果需要在后台长期运行,建议结合其他工具,如 systemd ⧉

OpenWrt

OpenWrt 上也有相应的 IPK 安装包,能够通过图形化的界面来方便的进行设定,更适合对命令行命令不太熟悉的用户。

首先,需要安装 frp 的 IPK 安装包,然后安装 luci-app-frpc 和 luci-i18n-frpc-zh-cn 两个插件,最后重启路由器即可。注意:按照顺序进行安装,否则可能会出现报错。

安装完成后,就可以在路由器的 LuCI 界面中看到 frp 的配置界面了。具体的配置项的说明同样参考 配置文件说明 ⧉

Docker

对于 VPS 和 NAS 系统,更建议通过 Docker 来安装 frp 并实现后台的持续静默运行。Docker Hub 上有多个 frp 的镜像,可以根据自己的需求来选择。

下面的客户端 Docker-compose 脚本使用的是 snowdreamtech/frpc ⧉ 镜像。

1
2
3
4
5
6
7
8
9
version: "3.8"
services:
  frpc:
    image: snowdreamtech/frpc
    container_name: frpc
    restart: always
    network_mode: host
    volumes:
      - ./:/etc/frp #(1)
  1. 将配置文件 frpc.toml 复制到当前目录下,然后挂载到容器的 /etc/frp 目录下。

设置宿主机地址

使用 Docker 部署 Frp 客户端服务时,网络配置建议使用 host 模式,以便容器自动处理端口映射。如果要设置成 bridge 模式,需要注意将 frpc.toml 配置文件中的 localIP 设置为 host.docker.internal 或者 0.0.0.0,否则会出现无法连接的问题。

同样的,我们使用 snowdreamtech/frps ⧉ 镜像来搭建服务器端服务。

1
2
3
4
5
6
7
8
9
version: "3.8"
services:
  frpc:
    image: snowdreamtech/frps
    container_name: frps
    restart: always
    network_mode: host
    volumes:
      - ./:/etc/frp #(1)
  1. 将配置文件 frps.toml 复制到当前目录下,然后挂载到容器的 /etc/frp 目录下。

配置

frp 的安装总体来说相对简单,但是配置需要注意的细节就比较多了。

首先,配置文件的格式在最近的升级中发生很大的变化,目前支持的文件格式包括 TOML/YAML/JSON。建议优先使用 TOML 格式,因为官网的配置示例和配置详细说明都使用的是这个格式。

其次,配置文件中很多参数的名称在最近的升级中也发生了变化,同时增加了不少新的参数支持扩充功能。因此,强烈建议在进行配置之前,花点时间再仔细阅读一遍相应的配置文件说明。

最后,官网给出的 示例 ⧉ 配置极为精简,可以参考下面我的实际使用配置,会更容易理解一些。

服务端

服务端的配置文件中,最重要的是 bindPortvhostHTTPPort 两个参数,前者是服务器监听的端口,后者是 HTTP 类型代理监听的端口。

注意端口

需要特别提醒注意的是,vhostHTTPPort 参数对应的端口号在进行后续的 Nginx 配置的时候也会用到,主要是在反向代理 HTTP 服务的时候 proxy_pass 要指向 vhostHTTPPort 提供的 HTTP 类型代理监听端口。

bindAddr = "0.0.0.0"
bindPort = 7000 #(1)
kcpBindPort = 7000
vhostHTTPPort = 9090 #(2)
vhostHTTPSPort = 9443 #(3)

webServer.addr = "0.0.0.0"  #(4)
webServer.port = 7500
webServer.user = "admin"
webServer.password = "xxxxxxxxxxxxx"

auth.method = "token" #(5)
auth.token = "xxxxxxxxxxxxxxx"

subDomainHost = "xxx.icu" #(6)

1. 服务器 TCP/KCP 类型代理监听端口,必须要配置
2. HTTP 类型代理监听端口,配置了才能代理 HTTP 类服务
3. HTTP 类型代理监听端口,配置了才能代理 HTTP 类服务
4. 配置控制面板访问地址,建议这里允许所有 IP 访问,之后再通过 Nginx 进行限制
5. 配置认证方式和令牌,增强服务的安全性
6. 配置子域名的根域名,在客户端配置文件中就可以只需配置子域名

1. 通用绑定设置:

    * bindAddr: 服务器监听的 IP 地址。"0.0.0.0" 表示监听所有地址。
    * bindPort: 服务器监听的端口,这里是 7000。
    * kcpBindPort: 使用 KCP 协议的 UDP 端口,可以与 bindPort 相同,若未设置则 KCP 协议被禁用。

2. QUIC 协议设置:

    * quicBindPort: 使用 QUIC 协议的端口,若未设置则 QUIC 协议被禁用。
    * transport.quic.*: QUIC 相关的详细设置,例如 keepalivePeriod, maxIdleTimeout, 和 maxIncomingStreams。

3. 心跳配置:

    * transport.heartbeatTimeout: 心跳超时时间,默认是 90 秒。

4. 连接池配置:

    * transport.maxPoolCount: 代理在每个连接中保持的最大连接池数量。

5. TCP 多路复用:

    * transport.tcpMux: 指定是否使用 TCP 流多路复用,默认为 true。

6. TLS 设置:

    * tls.force: 是否强制只接受 TLS 加密连接,默认为 false。

7. 虚拟主机设置:

    * vhostHTTPPort 和 vhostHTTPSPort: 用于 HTTP 和 HTTPS 的虚拟主机端口。

8. Web 服务器和控制面板配置:

    * webServer.addr 和 webServer.port: 控制面板的监听地址和端口。
    * webServer.user 和 webServer.password: 控制面板登录的用户名和密码。

9. 日志设置:

    * log.to: 日志输出位置。
    * log.level: 日志级别。
    * log.maxDays: 日志保留的最大天数。

10. 认证和权限设置:

    * auth.method: 认证方式,这里是 "token"。
    * auth.token: 用于客户端和服务器端认证的令牌。

11. 端口白名单:

    * allowPorts: 允许客户端绑定的端口范围。

12. 子域名设置:

    * subDomainHost: 如果设置了,可以在客户端配置文件中设置子域名。

13. 其他设置:

     * udpPacketSize: UDP 数据包大小。
     * natholeAnalysisDataReserveHours: NAT 穿透数据保留时间。

客户端

客户端的配置相对简单一些,主要是不同类型的代理,通过 type 限定类型后,在属性项上会有一些差异。

HTTP类型

HTTP 类型的代理不需要指定远程端口,因为会统一通过 vhostHTTPPort 对应的端口进行监听。但是需要注意的是,HTTP 类型的代理只能代理 HTTP 类型的服务,不能代理 HTTPS 类型的服务。

对于需要使用 HTTPS 的服务,建议在客户端转发时还是选择 HTTP 类型,然后在服务端使用 Nginx 来反向代理,这样可以更好的控制证书的管理。

1
2
3
4
5
serverAddr = "x.x.x.x" #(1)
serverPort = 7000

auth.method = "token" #(2)
auth.token = "xxxxxxxxxxxxxxx"
  1. 服务器地址
  2. 认证方式和令牌,和服务器端配置一致
1
2
3
4
5
6
[[proxies]]
name = "1002"
type = "http"
localIP = "127.0.0.1"
localPort = 80
subdomain = "1002" #(1)
  1. 子域名,和服务器端配置的根域名组合成完整的域名
1
2
3
4
5
6
[[proxies]]
name = "mc"
type = "tcp"
localIP = "127.0.0.1"
localPort = 38773  #(1)
remotePort = 38773 #(2)
  1. 本地端口和远程服务器端口直接一对一穿透
  2. 建议通过 Nginx 反向代理远程服务器端口提供域名访问
1
2
3
4
5
6
[[proxies]]
name = "dns"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 6000

Nginx 协同

虽然 frp 本身就是一个反向代理工具,但是它的功能还是比较有限的。因此在实际使用中,配合 Nginx 一起使用能实现更多的功能。

特别是如果使用 Nginx 作为反向代理,在遇到将本地 http 服务转换为 https 服务的类似需求时,就不再需要使用 https2http ⧉ 插件,大大减少了在客户端配置 SSL 证书的麻烦。

至于 TCP 服务也可以使用 Nginx 来实现通过域名访问,相对于直接使用 IP + 端口的方式,这样的体验显然要更加优雅一些。

server{
    server_name xxxx.10k.icu;
    listen 443 ssl;
    ssl_certificate /etc/nginx/ssl/*.10k.icu.crt;
    ssl_certificate_key /etc/nginx/ssl/*.10k.icu.key;
    ssl_trusted_certificate /etc/nginx/ssl/*.10k.icu.ca.crt;

    location / {
            proxy_pass http://127.0.0.1:9090; #(1)
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_ssl_server_name on;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            }
}
  1. 指向 frp 服务器的 HTTP 代理端口,即 vhostHTTPPort 对应的端口
server{
    server_name xxx.10k.icu;
    listen 443 ssl;
    ssl_certificate /etc/nginx/ssl/*.10k.icu.crt;
    ssl_certificate_key /etc/nginx/ssl/*.10k.icu.key;
    ssl_trusted_certificate /etc/nginx/ssl/*.10k.icu.ca.crt;

    location / {
            proxy_pass http://127.0.0.1:38773; #(1)
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_ssl_server_name on;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            }
}
  1. 指向 frp 服务器的 TCP 远程端口