FRP (Fast Reverse Proxy) 是一个使用 Go 语言开发的高性能的反向代理应用,可以轻松地进行内网穿透,对外网提供服务。FRP 支持 TCP、UDP、HTTP、HTTPS等协议类型,并且支持 Web 服务根据域名进行路由转发。
FRP 采用 Go 语言开发,支持 Windows、Linux、MacOS、ARM等多平台部署。FRP 安装非常容易,只需下载对应系统平台的软件包,并解压就可用。
可以根据对应的操作系统及架构,从 GitHub 页面下载最新版本的程序,并解压。
将 frps 及 frps.ini 放到具有公网 IP 的机器上。
将 frpc 及 frpc.ini 放到处于内网环境的机器上。
配置 FRP 服务端的前提条件是需要一台具有 公网 IP 的设备,得益于 Frp 是 Go 语言开发的,具有良好的跨平台特性。可以在 Windows、Linux、MacOS、ARM等几乎任何可联网设备上部署。
这里以 Linux 为例,Frp 默认给出两个服务端配置文件,一个是简版的 frps.ini,另一个是完整版本 frps_full.ini。
先来看看简版的 frps.ini,通过这个配置可以快速的搭建起一个 FRP 服务端:
# frps.ini
[common]
bind_port = 7000
# 默认配置中监听的是 7000 端口,可根据自己实际情况修改
启动frps:
$ ./frps -c ./frps.ini
通过上面简单的两步就可以成功启动一个监听在 7000 端口的 FRP 服务端
和 FRP 服务端类似,FRP 默认也给出两个客户端配置文件,一个是简版的 frpc.ini,另一个是完整版本 frpc_full.ini。
这里同样以简版的 frpc.ini 文件为例,假设 FRP 服务端所在服务器的公网 IP 为 X.X.X.X:
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
启动frpc:
$ ./frpc -c ./frpc.ini
这样就可以成功在 FRP 服务端上成功建立一个客户端连接,当然现在还并不能对外提供任何内网机器上的服务,因为我们并还没有在 FRP 服务端注册任何内网服务的端口。
# frps.ini
[common]
bind_port = 7000
./frps -c ./frps.ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
./frpc -c ./frpc.ini
ssh -oPort=6000 test@x.x.x.x
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP,无法将域名解析到本地的机器,通过 frp 就可以实现这一功能,以下示例为 http 服务,https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port, type 设置为 https 即可。
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
./frps -c ./frps.ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[web]
type = http
local_port = 80
custom_domains = www.yourdomain.com
4. 启动 frpc:
./frpc -c ./frpc.ini
5. 将 www.yourdomain.com 的域名 A 记录解析到 IP x.x.x.x,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。
6. 通过浏览器访问 http://www.yourdomain.com:8080 即可访问到处于内网机器上的 web 服务。
# frps.ini
[common]
bind_port = 7000
./frps -c ./frps.ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[web]
type = tcp
local_port = 80
remote_port = 8081
./frpc -c ./frpc.ini
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。使用 stcp(secret tcp) 类型的代理可以避免让任何人都能访问到要穿透的服务,只有自己才可访问,但是访问者也需要运行另外一个 frpc。
以下示例将会创建一个只有自己能访问到的 ssh 穿透服务代理。
[common]
bind_port = 7000
bind_udp_port = 7001
dashboard_port = 7500
log_file = /root/frp/frps.log
log_level = info
log_max_days = 3
authentication_timeout = 0
token = mobisys
max_pool_count = 50
./frps -c ./frps.ini
[common]
server_addr = x.x.x.x
server_port = 7000
token = mobisys
[client_A]
type = stcp
#SK跟访问者一致才能连通,可以自己随便设
sk = abcdefg
local_ip = 127.0.0.1
local_port = 80
在要访问这个服务的机器上启动另外一个 frpc,配置如下:
要访问对方:
frpc.ini客户端配置
[common]
server_addr = x.x.x.x
server_port = 7000
token = mobisys
[client_A_visitor]
type = stcp
role = visitor
#server_name同上面[client_A]一致,待访问端和访问者client_A字段可以 #替换成自己想要的
server_name = client_A
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 2222
DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。
# frps.ini
[common]
bind_port = 7000
./frps -c ./frps.ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[dns]
type = udp
local_ip = 8.8.8.8
local_port = 53
remote_port = 6000
./frpc -c ./frpc.ini
dig @x.x.x.x -p 6000 www.google.com
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
frps 的部署步骤同上。
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[unix_domain_socket]
type = tcp
remote_port = 6000
plugin = unix_domain_socket
plugin_unix_path = /var/run/docker.sock
curl http://x.x.x.x:6000/version
通过 static_file 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
frps 的部署步骤同上。
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[test_static_file]
type = tcp
remote_port = 6000
plugin = static_file
# 要对外暴露的文件目录
plugin_local_path = /tmp/file
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
通过 https2http 插件可以让本地 HTTP 服务转换成 HTTPS 服务对外提供。
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[test_htts2http]
type = https
custom_domains = test.yourdomain.com
plugin = https2http
plugin_local_addr = 127.0.0.1:80
# HTTPS 证书相关的配置
plugin_crt_path = ./server.crt
plugin_key_path = ./server.key
plugin_host_header_rewrite = 127.0.0.1
plugin_header_X-From-Where = frp
frp 提供了一种新的代理类型 xtcp 用于应对在希望传输大量数据且流量不经过服务器的场景。
使用方式同 stcp 类似,需要在两边都部署上 frpc 用于建立直接的连接。
目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 stcp 的方式。
bind_udp_port = 7001
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh]
type = xtcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh_visitor]
type = xtcp
# xtcp 的访问者
role = visitor
# 要访问的 xtcp 代理的名字
server_name = p2p_ssh
sk = abcdefg
# 绑定本地端口用于访问 ssh 服务
bind_addr = 127.0.0.1
bind_port = 6000
ssh -oPort=6000 test@127.0.0.1
由于 frp 目前支持的功能和配置项较多,可以从完整的示例配置文件中发现。
配置文件支持使用系统环境变量进行模版渲染,模版格式采用 Go 的标准格式。
示例配置如下:
# frpc.ini
[common]
server_addr = {{ .Envs.FRP_SERVER_ADDR }}
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
启动 frpc 程序:
export FRP_SERVER_ADDR="x.x.x.x"
export FRP_SSH_REMOTE_PORT="6000"
./frpc -c ./frpc.ini
frpc 会自动使用环境变量渲染配置文件模版,所有环境变量需要以 .Envs 为前缀。
通过浏览器查看 frp 的状态以及代理统计信息展示。
注:Dashboard 尚未针对大量的 proxy 数据展示做优化,如果出现 Dashboard 访问较慢的情况,请不要启用此功能。
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
[common]
dashboard_port = 7500
# dashboard 用户名密码,默认都为 admin
dashboard_user = admin
dashboard_pwd = admin
打开浏览器通过 http://[server_addr]:7500 访问 dashboard 界面,用户名密码默认为 admin。
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
[common]
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
打开浏览器通过 http://127.0.0.1:7400 访问 Admin UI,用户名密码默认为 admin。
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
服务端和客户端的 common 配置中的 token 参数一致则身份验证通过。
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy:
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 use_encryption = true,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
如果传输的报文长度较长,通过设置 use_compression = true 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 frpc.ini 的 common 中配置 tls_enable = true 来启用此功能,安全性更高。
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。
当修改了 frpc 中的代理配置,可以通过 frpc reload 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
# frpc.ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
之后执行重启命令:
frpc reload -c ./frpc.ini
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
需要注意的是,[common] 中的参数除了 start 外目前无法被修改。
frpc 支持通过 frpc status -c ./frpc.ini 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 allow_ports 来指定:
# frps.ini
[common]
allow_ports = 2000-3000,3001,3003,4000-50000
allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 , 分隔,指定的范围以 - 分隔。
目前 frps 中的 vhost_http_port 和 vhost_https_port 支持配置成和 bind_port 为同一个端口,frps 会对连接的协议进行分析,之后进行不同的处理。
例如在某些限制较严格的网络环境中,可以将 bind_port 和 vhost_https_port 都设置为 443。
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致:
# frps.ini 和 frpc.ini 中
[common]
tcp_mux = false
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
开启 kcp 协议支持:
# frps.ini
[common]
bind_port = 7000
# kcp 绑定的是 udp 端口,可以和 bind_port 一样
kcp_bind_port = 7000
# frpc.ini
[common]
server_addr = x.x.x.x
# server_port 指定为 frps 的 kcp_bind_port
server_port = 7000
protocol = kcp
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
这一功能比较适合有大量短连接请求时开启。
# frps.ini
[common]
max_pool_count = 5
# frpc.ini
[common]
pool_count = 1
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
目前只支持 TCP 和 HTTP 类型的 proxy。
# frpc.ini
[test1]
type = tcp
local_port = 8080
remote_port = 80
group = web
group_key = 123
[test2]
type = tcp
local_port = 8081
remote_port = 80
group = web
group_key = 123
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
TCP 类型代理要求 group_key 相同,做权限验证,且 remote_port 相同。
HTTP 类型代理要求 group_key, custom_domains 或 subdomain 和 locations 相同。
通过给 proxy 加上健康检查的功能,可以在要反向代理的服务出现故障时,将这个服务从 frps 中摘除,搭配负载均衡的功能,可以用来实现高可用的架构,避免服务单点故障。
在每一个 proxy 的配置下加上 health_check_type = {type} 来启用健康检查功能。
type 目前可选 tcp 和 http。
tcp 只要能够建立连接则认为服务正常,http 会发送一个 http 请求,服务需要返回 2xx 的状态码才会被认为正常。
tcp 示例配置如下:
# frpc.ini
[test1]
type = tcp
local_port = 22
remote_port = 6000
# 启用健康检查,类型为 tcp
health_check_type = tcp
# 建立连接超时时间为 3 秒
health_check_timeout_s = 3
# 连续 3 次检查失败,此 proxy 会被摘除
health_check_max_failed = 3
# 每隔 10 秒进行一次健康检查
health_check_interval_s = 10
http 示例配置如下:
# frpc.ini
[web]
type = http
local_ip = 127.0.0.1
local_port = 80
custom_domains = test.yourdomain.com
# 启用健康检查,类型为 http
health_check_type = http
# 健康检查发送 http 请求的 url,后端服务需要返回 2xx 的 http 状态码
health_check_url = /status
health_check_interval_s = 10
health_check_max_failed = 3
health_check_timeout_s = 3
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
原来 http 请求中的 host 字段 test.yourdomain.com 转发到后端服务时会被替换为 dev.yourdomain.com。
对于 type = http 的代理,可以设置在转发中动态添加的 header 参数。
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
header_X-From-Where = frp
对于参数配置中所有以 header_ 开头的参数(支持同时配置多个),都会被添加到 http 请求的 header 中,根据如上的配置,会在请求的 header 中加上 X-From-Where: frp。
目前只有 http 类型的代理支持这一功能,可以通过用户请求的 header 中的 X-Forwarded-For 来获取用户真实 IP,默认启用。
frp 支持通过 Proxy Protocol 协议来传递经过 frp 代理的请求的真实 IP,此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
Proxy Protocol 功能启用后,frpc 在和本地服务建立连接后,会先发送一段 Proxy Protocol 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 Proxy Protocol 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
这里以 https 类型为例:
# frpc.ini
[web]
type = https
local_port = 443
custom_domains = test.yourdomain.com
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
proxy_protocol_version = v2
只需要在代理配置中增加一行 proxy_protocol_version = v2 即可开启此功能。
本地的 https 服务可以通过在 nginx 的配置中启用 Proxy Protocol 的解析并将结果设置在 X-Real-IP 这个 Header 中就可以在自己的 Web 服务中通过 X-Real-IP 获取到用户的真实 IP。
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通过用户名和密码才能访问到你的服务。
该功能目前仅限于 http 类型的代理,需要在 frpc 的代理配置中添加用户名和密码的设置。
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
http_user = abc
http_pwd = abc
通过浏览器访问 http://test.yourdomain.com,需要输入配置的用户名和密码才能访问。
在多人同时使用一个 frps 时,通过自定义二级域名的方式来使用会更加方便。
通过在 frps 的配置文件中配置 subdomain_host,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 custom_domains,而是配置一个 subdomain 参数。
只需要将 *.{subdomain_host} 解析到 frps 所在服务器。之后用户可以通过 subdomain 自行指定自己的 web 服务所需要使用的二级域名,通过 {subdomain}.{subdomain_host} 来访问自己的 web 服务。
# frps.ini
[common]
subdomain_host = frps.com
将泛域名 *.frps.com 解析到 frps 所在服务器的 IP 地址。
# frpc.ini
[web]
type = http
local_port = 80
subdomain = test
frps 和 frpc 都启动成功后,通过 test.frps.com 就可以访问到内网的 web 服务。
注:如果 frps 配置了 subdomain_host,则 custom_domains 中不能是属于 subdomain_host 的子域名或者泛域名。
同一个 http 或 https 类型的代理中 custom_domains 和 subdomain 可以同时配置。
frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
通过配置文件中的 locations 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 locations = /news,则所有 URL 以 /news 开头的请求都会被转发到这个服务。
# frpc.ini
[web01]
type = http
local_port = 80
custom_domains = web.yourdomain.com
locations = /
[web02]
type = http
local_port = 81
custom_domains = web.yourdomain.com
locations = /news,/about
按照上述的示例配置后,web.yourdomain.com 这个域名下所有以 /news 以及 /about 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。
在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。
可以通过设置 HTTP_PROXY 系统环境变量或者通过在 frpc 的配置文件中设置 http_proxy 参数来使用此功能。
仅在 protocol = tcp 时生效。
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
http_proxy = http://user:pwd@192.168.1.128:8080
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
这一功能通过 range: 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy,每一个 proxy 以数字为后缀命名。
例如要映射本地 6000-6005, 6007 这6个端口,主要配置如下:
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
实际连接成功后会创建 8 个 proxy,命名为 test_tcp_0, test_tcp_1 … test_tcp_7。
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 unix_domain_socket、http_proxy、socks5、static_file。
通过 plugin 指定需要使用的插件,插件的配置参数都以 plugin_ 开头。使用插件后 local_ip 和 local_port 不再需要配置。
使用 http_proxy 插件的示例:
# frpc.ini
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = abc
plugin_http_user 和 plugin_http_passwd 即为 http_proxy 插件可选的配置参数。