Skip to content

使用 FRP 进行内网穿透

1. FRP 简介

FRP 是一个由 Go 编写的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等协议。它可以轻松地进行内网穿透,对外网提供服务。其 GitHub 仓库为 fatedier/frp

FRP 分为客户端和服务端分别称为 FRPC 和 FRPS,客户端用于连接内网的应用程序,服务端用于提供代理服务。

FRP 还提供管理界面,可以通过浏览器访问管理界面,查看代理服务的状态,可以查看每日的流量情况。

大多数情况下我们只需要 TCP 代理即可,HTTP/HTTPS 代理可以支持更多功能,但是需要配置更多参数。

2. Docker 部署

比较流行的 FRP 镜像是由 @snowdreamtech 提供的,包括 snowdreamtech/frpcsnowdreamtech/frps 两个镜像。

拉取镜像:

bash
docker pull snowdreamtech/frpc:0.61.1
docker pull snowdreamtech/frps:0.61.1

配置文件内可以使用类似大括号加 .Envs.NAME 的格式可以读取环境变量。

2.1 客户端配置

docker-compose.yml 配置如下:

yaml
services:
  frpc:
    image: snowdreamtech/frpc:0.61.1
    container_name: frpc
    restart: unless-stopped
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./frpc.toml:/etc/frp/frpc.toml:ro
    environment:
      FRP_SERVER_ADDR: ${FRP_SERVER_ADDR}
      FRP_SERVER_PORT: ${FRP_SERVER_PORT}
      FRP_SERVER_TOKEN: ${FRP_SERVER_TOKEN}

以 SSH 服务穿透为例,新建 frpc.toml 配置文件,内容如下:

toml
[common]
server_addr = {{ .Envs.FRP_SERVER_ADDR }}
server_port = {{ .Envs.FRP_SERVER_PORT }}
token       = {{ .Envs.FRP_SERVER_TOKEN }}

[app_22]
type = "tcp"
remote_port = 222             # 服务端端口
local_ip    = 192.168.xxx.xxx # 本地 IP
local_port  = 22              # 本地端口

配置远程 FRPS 服务地址到 .env 文件中:

properties
FRP_SERVER_ADDR=frps.example.com
FRP_SERVER_PORT=9870
FRP_SERVER_TOKEN=password

启动服务,即可代理 192.168.10.100:22FRP_SERVER_ADDR:222

bash
docker compose up -d

2.2 服务端配置

docker-compose.yml 配置如下:

yaml
services:
  frps:
    image: snowdreamtech/frps:0.61.1
    container_name: frps
    restart: unless-stopped
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./frps.toml:/etc/frp/frps.toml
    ports:
      - 7890:7890
      - 9870:9870
    environment:
      FRP_SERVER_PORT: 9870
      FRP_SERVER_TOKEN: ${FRP_SERVER_TOKEN}
      FRP_ADMIN_PORT: 7890
      FRP_ADMIN_USER: ${FRP_ADMIN_USER}
      FRP_ADMIN_PASS: ${FRP_ADMIN_PASS}

新建 frps.toml 配置文件,内容如下:

toml
[common]
bind_port = {{ .Envs.FRP_SERVER_PORT }}
token     = {{ .Envs.FRP_SERVER_TOKEN }}

dashboard_addr = 0.0.0.0
dashboard_port = {{ .Envs.FRP_ADMIN_PORT }}
dashboard_user = {{ .Envs.FRP_ADMIN_USER }}
dashboard_pwd  = {{ .Envs.FRP_ADMIN_PASS }}

配置密钥等信息到 .env 文件中:

properties
FRP_SERVER_TOKEN=password
FRP_ADMIN_USER=admin
FRP_ADMIN_PASS=password

启动服务,即可代理客户端请求,注意需要将客户端的端口映射出来。可以通过 HOST 网络或共享网络的方式共享服务。

使用如下方法启用 HOST 模式:

yaml
services:
  frps:
    network_mode: host

如果端口确定,建议不使用 HOST 模式,使用端口映射确保主机安全。

3. FRP 获取真实 IP

3.1 HTTP 代理

使用 HTTP 代理时,可以通过 X-Forwarded-For 头部获取真实 IP。但是由于 HTTP 代理不够灵活,我们一般直接使用 TCP 代理。

3.2 TCP 代理

FRP 支持通过 Proxy Protocol 协议来传递经过 FRP 代理的请求的真实 IP,此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。[1]

通过指定 proxyProtocolVersion 可以开启 Proxy Protocol,下面是配置示例:

toml
serverAddr  = {{ .Envs.FRP_SERVER_ADDR }}
serverPort  = {{ .Envs.FRP_SERVER_PORT }}
token       = {{ .Envs.FRP_SERVER_TOKEN }}

[app]
type        = "tcp"
remotePort  = 4090
localIp     = {{ .Envs.APP_HOST }}
localPort   = 4090
proxyProtocolVersion = "v2"

Nginx 支持 Proxy Protocol,可以通过 proxy_protocol 指令来开启。

listen 指令中添加 proxy_protocol 参数,然后在 location 指令中添加 proxy_set_header 指令来设置 X-Real-IPX-Forwarded-For 头部。

nginx
server {
    listen      4090 proxy_protocol;
    server_name www.xxxxxx.com;
    client_max_body_size 128m;

    location / {
        root   /usr/share/nginx/html;
        try_files $uri $uri/ @router;
        index  index.html index.htm;
    }

    location @router {
        rewrite ^.*$ /index.html last;
    }

    location ^~ /api/ {
        proxy_pass  http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $proxy_protocol_addr;
        proxy_set_header X-Forwarded-For $proxy_protocol_addr;
    }
}

  1. 获取用户真实 IP,FRP 文档,https://gofrp.org/zh-cn/docs/features/common/realip/ ↩︎