使用 frp 实现内网穿透/远程管理/文件服务器
首先判断自己网络 IP 地址是固定 IP/动态 IP/内网 IP,通过站长工具可以查询,或者终端执行如下指令查看外网看本地 IP 地址:
curl https://info.niekun.net/ip
然后从本地通过 ipconfig(win)/ifconfig(linux) 来查看网络路径,如果本地 IP 和上面检测出的一致,那就是有外网 IP,如果不一样则很可能自己分配到的是一个运营商内网。如下是我本地 Windows 电脑的结果,确认是内网:
对于有外网 IP 但是是动态的,每隔一段时间自动变化的情况,可以使用 DDNS 来处理。
对于大多数家庭网络用户,都没有固定的 IP 地址或者也没有动态的 IP 地址,在本地搭建网络服务无法直接在外网访问,使用 frp 可以实现这一需求。
使用场景:路由器远程访问 远程访问/控制电脑 网搭建网站公网访问
项目地址:https://github.com/fatedier/frp
使用手册:https://gofrp.org/docs/
Windows 脚本参考:https://sspai.com/post/52523
环境需求:
一台有独立 IP 的服务器
可安装 frp 的本地主机或路由器(windows/linux)
安装
从 release 页面分别下载合适版本的最新程序到客户端和独立 IP 的服务端。
程序内包含服务端程序(frps)及客户端程序(frpc),以及对应配置文件。
使用
服务器端
修改 frps.toml 文件,示例:
# 服务端本地地址和客户端握手端口,bindAddr 设置同 bindPort
bindAddr = "0.0.0.0"
bindPort = 6000
# kcp 协议端口,可以和 bindAddr 一样,不设置的话不开启
kcpBindPort = 6000
# Pool count in each proxy will keep no more than maxPoolCount.
# 每个代理最大连接数
transport.maxPoolCount = 5
# If tcp stream multiplexing is used, default is true
transport.tcpMux = true
# Specify keep alive interval for tcp mux.
# only valid if tcpMux is true.
transport.tcpMuxKeepaliveInterval = 60
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
# If negative, keep-alive probes are disabled.
transport.tcpKeepalive = 7200
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
# 是否只接受 tls 连接
transport.tls.force = false
# http 和 https web 服务访问端口,如:test.com:8080,即可访问内网 web 页面
vhostHTTPPort = 6002
vhostHTTPSPort = 6003
# frp状态面板端口,用来显示一些连接状态的 web 页面
webServer.addr = "127.0.0.1"
webServer.port = 6004
# 设置面板的账户密码访问,不设置默认为 admin
webServer.user = "admin"
webServer.password = "464116963"
# Enable golang pprof handlers in dashboard listener.
# Dashboard port must be set first
webServer.pprofEnable = false
# enablePrometheus will export prometheus metrics on webServer in /metrics api.
enablePrometheus = true
# 日志记录文件定义,不写则不记录
log.to = "/opt/frp/frps.log"
log.level = "info"
log.maxDays = 3
log.disablePrintColor = false
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
detailedErrorsToClient = true
# 定义 frpc 和 frps 连接的验证方式
# If "token" is specified - token will be read into login message.
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
auth.method = "token"
# 服务端和客户端的 common 配置中的 token 参数一致则身份验证通过
auth.token = "123456789"
# 为给客户端开放的端口,用于和客户端数据交互,如不设置此条目,则默认开放所有端口
allowPorts = [
{ start = 6010, end = 6100 }
]
# Max ports can be used for each client, default value is 0 means no limit
maxPortsPerClient = 0
subDomainHost = "mydomain.net"
执行以下命令启动:
path/to/frps -c path/to/frps.ini
客户端
修改 frpc.toml 文件,示例:
# 客户端名称标识 proxy 会显示为 {user}.{proxy}
user = "user1"
# 远程独立 IP 地址和端口 必须和服务端设置一致
serverAddr = "xxx.xxx.xxx.xxx" # 也可以使用 "http://your.domain"
serverPort = 6000
# 第一次启动失败是否直接退出
loginFailExit = false
# log 设置
log.to = "./frpc.log"
log.level = "info"
log.maxDays = 3
log.disablePrintColor = false
# 客户端和服务端验证方式,需要和 frps.toml 设置一致
auth.method = "token"
# 服务端和客户端的 common 配置中的 token 参数一致则身份验证通过
auth.token = "123456789"
# 控制 frpc 客户端一些动作的地址,比如重启/关闭等
webServer.addr = "127.0.0.1"
webServer.port = 6005
webServer.user = "admin"
webServer.password = "admin"
# Enable golang pprof handlers in admin listener.
webServer.pprofEnable = false
# 最大连接数
transport.poolCount = 5
# 是否启用 tcpmux 必须和服务端一致
transport.tcpMux = true
# Specify keep alive interval for tcp mux.
# only valid if tcpMux is enabled.
transport.tcpMuxKeepaliveInterval = 60
# 定义链接服务器的协议,支持 tcp and kcp and websocket, default is tcp
transport.protocol = "tcp"
# set client binding ip when connect server, default is empty.
# only when protocol = tcp or websocket, the value will be used.
transport.connectServerLocalIP = "0.0.0.0"
#加密连接服务端
transport.tls.enable = true
# 想要启动的 proxy 名称,默认为空则表示启动所有下面配置好的 proxy
# Default is empty, means all proxies.
# start = ["windows_rdp", "aria2_windows"]
# 代理
[[proxies]]
# ssh 是自定义代理块标记,可以是任意的名称
name = "ssh"
# tcp | udp | http | https | stcp | xtcp, default is tcp
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
# 远程端口号 如果定义为 0 则会动态分配一个端口
remotePort = 6006
# frps 对同一个 group 里的 proxies 会负载均衡
loadBalancer.group = "group1"
# group 需要有相同的 key
loadBalancer.groupKey = "123456"
# 加密传输
transport.useEncryption = true
# 压缩流量
transport.useCompression = true
# 带宽限制, unit is KB and MB
# transport.bandwidthLimit = "1MB"
# 限制哪一端, can be 'client' or 'server', default is 'client'
# transport.bandwidthLimitMode = "client"
# 本地 smb 服务转发给外网
[[proxies]]
name = "h99-smb-share"
type = "tcp"
localIP = "192.168.122.124"
localPort = 445
remotePort = 6056
# remote disktop 远程桌面设置
[[proxies]]
name = "windows_rdp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3389
remotePort = 6010
transport.useEncryption = true
transport.useCompression = true
# web 服务
[[proxies]]
name = "web01"
# 支持 http https
type = "http"
localIP = "127.0.0.1"
localPort = 80
transport.useEncryption = true
transport.useCompression = true
# 如果服务端域名为 niekun.net, 可以通过链接访问 web01: http://web01.niekun.net:8080,此子域名需要在域名服务商处进行 DNS 解析
subdomain = "web01"
# custom_domains 为 域名/服务器 IP 如果上面定义了 subdomain 此处可以不要
#customDomains = ["web01.niekun.net"]
# 如访问 web 页面是不需要密码则去掉 访问用户名及密码
httpUser = "admin"
httpPassword = "admin"
# 对外提供简单的文件访问服务
[[proxies]]
name = "plugin_static_file"
type = "tcp"
remotePort = 6020
[proxies.plugin]
type = "static_file"
# 要对外暴露的文件目录
localPath = "/tmp/file"
# 用户访问 URL 的 location 路径
stripPrefix = "static"
httpUser = "abc"
httpPassword = "abc"
# 转发 DNS 查询请求
[[proxies]]
name = "dns"
type = "udp"
# 指定 DNS 服务器地址
localIP = "114.114.114.114"
localPort = 53
remotePort = 60
# 将内网机器作为 HTTP 代理暴露给其他服务,可以通过此代理访问到此内网机器能够访问到的其他服务
[[proxies]]
name = "http_proxy"
type = "tcp"
remotePort = 6034
[proxies.plugin]
type = "http_proxy"
httpUser = "admin"
httpPassword = "admin"
# 将本地 http 服务转换为 https 服务连接到服务器,后续可以通过 https 访问本地 http 服务,这里需要提供外网域名的证书信息,也可以通过 nginx 反代的 frps 自动实现此功能
[[proxies]]
name = "plugin_https2http"
type = "https"
subdomain = "test"
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:80"
crtPath = "./server.crt"
keyPath = "./server.key"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"
# 将本地 https 服务模拟为服务端的 https 服务给外网访问,这里需要提供外网域名的证书信息
[[proxies]]
name = "plugin_https2https"
type = "https"
subdomain = "test"
[proxies.plugin]
type = "https2https"
localAddr = "127.0.0.1:443"
crtPath = "./server.crt"
keyPath = "./server.key"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"
# 将本地的 https 服务转换为 http 连接到服务器,此时服务端 frps 认为本地是 http 服务,使用此插件可以将 openwrt 的 luci web 界面转为 http 然后转发到 frps 供外网访问。默认 luci 为 https 服务
[[proxies]]
name = "plugin_http2https"
type = "http"
subdomain = "test"
[proxies.plugin]
type = "http2https"
localAddr = "127.0.0.1:443"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"
# 点对点内外穿透,不经过服务端 需要访问和被访问两台设备都安装 frp 并配置 frpc
# 客户端1-配置内网需要被访问的服务
name = "secret_tcp"
type = "stcp"
secretKey = "abcdefg" # 配置访问密钥
localIP = "127.0.0.1"
localPort = 22 # 需要转发的本地服务端口
# 如果不为空 则指定的用户才可以连接 否则只有同一个用户才可以连接. '*' 表示开放给所有用户.
allowUsers = ["user1", "user2"]
# 点对点内外穿透
# 客户端2-访问客户端1的服务
[[visitors]]
name = "secret_tcp_visitor"
type = "stcp"
serverUser = "user1" # 需要和客户端1定义的用户名称一致
serverName = "secret_tcp" # 需要和客户端1对应代理的名称一致
secretKey = "abcdefg" # 需要和客户端1配置的密钥一致
bindAddr = "127.0.0.1"
bindPort = 9000 # 定义一个本地端口,后期可以通过访问这个本地端口访问客户端1的服务
执行以下命令启动:
path/to/frpc -c path/to/frpc.toml
测试
访问内网 ssh
ssh -oPort=6006 [email protected]
IP 为远程服务器的 IP,username 为内网设备的用户名。输入后提示密码,也为内网设备 ssh 密码。
web 服务
http://web01.niekun.net:6002
如果设置有用户名密码,会提示口令,然后进入 web 页面。
frps 控制面板
http://xxx.xxx.xxx.xxx:6004
文件服务器
通过浏览器访问 http://xxx.xxx.xxx.xxx:6020/static/
来查看位于 /tmp/file
目录下的文件,会要求输入已设置好的用户名和密码。
转发 DNS 查询请求
通过 dig 测试 UDP 包转发是否成功,预期会返回 www.baidu.com 域名的解析结果:
dig @xxx.xxx.xxx.xxx -p 6033 www.baidu.com
代理服务器
可以将内网设备作为 http 或 socks 代理服务器,这样外网设备就可以通过这个代理服务器访问到这台内网服务器能够访问到的服务了,如打印机等。
如果内网设备可以访问内部网页:http://10.10.1.100
测试将外网设备设置 http 代理:
http_proxy=http://xxx.xxx.xxx.xxx:6034
设置完成后外网设备测试在浏览器直接访问这个内部网页:http://10.10.1.100
服务端 nginx 代理 frp
我服务器上使用 nginx 管理 http 请求,可以使用 nginx 代理 frp 的 http 服务。这样可以实现不带端口访问 frp 和使用 nginx 上已经设置的 https 加密而不需要单独给 frp 配置 https。
客户端和服务端使用上面示例的配置,web01 页面可通过:http://web01.niekun.net:6002 访问。下面我们设置 nginx 代理服务:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name frp.niekun.net;
include my-server/ssl; ssl 配置文件
location / {
proxy_pass http://127.0.0.1:6004;
proxy_redirect off;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name web01.niekun.net;
include my-server/ssl;
location / {
proxy_pass http://127.0.0.1:6002;
proxy_redirect off;
proxy_set_header Host $http_host;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
}
}
我的 https 配置文件放在 my-server/ssl 文件内。
第一个 server 配置了 frp.niekun.net 域名转发给 frps 控制面板端口 6004,这样访问:https://frp.niekun.net 就可以访问 frps 控制面板。
第二个 server 配置了 web01.niekun.net 域名转发给 frps 监听的 http 端口 6002,访问:https://web01.niekun.net 就可以访问内网的 web01 页面了。proxy_set_header 设置转发给 frps 的请求头,有些网页如果缺少一些请求头信息会报错。
更详细的 nginx 代理服务器的使用参考我的文章:NGINX Reverse Proxy 反向代理的使用
注意:由于 rdp 是 tcp 协议,所以不能通过 nginx 代理使用域名进行访问。
nginx 和 frp 的更多配合方式参考示例:https://github.com/raymond9zhou/frps-nginx-https
客户端 frpc Windows 运行脚本及自启动
Windows 下可以写一个 bat 脚本,双击直接后台运行命令:
@echo off
if "%1" == "h" goto begin
mshta vbscript:createobject("wscript.shell").run("""%~nx0"" h",0)(window.close)&&exit
:begin
REM
cd C:\frp
frpc -c frpc.toml
exit
上面的 frpc 地址修改为实际地址,程序会在后台运行,想要关闭可以在任务管理器中结束进程: frpc.exe
将脚本快捷方式放到开机启动文件夹可以实现开机自启动,Windows 10 自启文件夹地址是:%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
linux systemd service 脚本
通过 systemd service 脚本可以方便的启动,关闭 frp 服务,也可以设置开机自启。
在 /usr/lib/systemd/system
目录下新建文件 frp.service 内容如下:
[Unit]
Description=frp Service
After=network.target nss-lookup.target
[Service]
User=root
ExecStart=/opt/frp/frps -c /opt/frp/frps.toml
Restart=on-failure
[Install]
WantedBy=multi-user.target
注意如果是服务端需要启动 frps 程序,客户端启动 frpc 程序,根据实际修改其中的配置文件路径。
重新加载配置文件:
sudo systemctl daemon-reload
启动 frp 服务:
sudo systemctl start frp.service
设置开机自启动:
sudo systemctl enable frp.service
以上就是 frp 的简单使用,高级用法参考官方手册。
详细配置文件:
frps:
https://github.com/fatedier/frp/blob/dev/conf/frps_full_example.toml
frpc:
https://github.com/fatedier/frp/blob/dev/conf/frpc_full_example.toml
可用代理类型:
https://gofrp.org/docs/reference/proxy/
可用插件:
https://gofrp.org/docs/features/common/client-plugin/