frp+go-mmproxy 实现透明代理的内网穿透
frp+go-mmproxy 实现透明代理的内网穿透
事前声明:该方法只在 Linux 系统有效,并且需要 linux 内核 2.6.28 或更高版本
参考:
[Feature Request] Make client IP accessible to upstream by spoofing source IP · 议题 #4184 · fatedier/frp
mmproxy - Creative Linux routing to preserve client IP addresses in L7 proxies
frp 是一个常用的内网穿透软件,支持多种协议,可以让 NAT 环境下的服务暴露到公网指定端口,以穿透 sshd
为例,配置 frpc.toml:
[[proxies]]
name= "ssh"
type="tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 23456
然后用 frps
对应公网的 ip 和端口访问:在内网机的 var/log/auth.log
中查看登录记录:
Accepted publickey for fsj2009yx from 127.0.0.1 port 38866 ssh2:
可以看到经过 frpc 代理转发后,源客户端 IP 被隐藏了,变成配置中的 localIP
地址
在某些业务或服务中,我们需要获取到客户端的源 IP,例如
- Web 服务限流:根据客户端真实 IP 做访问频率限制
- SSH 安全审计:通过登录日志统计远程来源 IP,进行异常检测。
- 防火墙规则:限制特定公网 IP 段才能访问服务。
如果源 IP 丢失,上述场景都会失效。
获取真实 ip
frp 提供了两种获取真实 ip 的方式:HTTP X-Forwarded-For
和 proxy protocol
(参考 获取用户真实 IP | frp)
前者在 web 开发中很常见,即配置 nginx 的 X-Forwarded-For
选项,但只局限于 http/https
协议。
proxy protocol
支持任意 TCP/UDP 协议,但是需要上游应用服务支持解析该协议,部分软件并没有原生支持该协议的版本,典型例子是 sshd
——它不支持 PROXY 协议,添加支持比较困难,导致某些需要真实 ip 的服务不能正常运行,例如 fail2ban
。
go-mmproxy
go-mmproxy
是 mmproxy
的 golang 实现版本, mmproxy
是一个 PROXY 协议网关,底层依赖于 linux 的 TProxy
(透明代理)技术
mmproxy
侦听来自应用程序级负载均衡器(如 Spectrum)的远程连接。然后,它读取 PROXY 协议标头,打开与目标应用程序的本地主机连接,并适当地代理数据进出。
关键在于,mmproxy 可以通过欺骗客户端 IP 地址的方式,访问上游应用程序,这样看上去就和直接连接到应用程序的真实连接没有区别
项目地址:path-network/go-mmproxy: Golang implementation of MMProxy
软件配置
环境要求(内网主机):
- 需要
go sdk
版本1.21
及以上(如果了解交叉编译可不配置) - linux 系统,内核版本
2.6.28
或更高 go-mmproxy
通过环回接口转发流量,因此它必须与 目标应用程序 在同一台机器上运行。
下载 go-mmproxy
执行命令:
go install github.com/path-network/go-mmproxy@latest
下载完成后,会把可执行文件 go-mmproxy
安装到 GOPATH/bin
。
如果你没有显式设置 GOPATH
,默认是:
- Linux/macOS:
$HOME/go/bin
配置 frpc
假设将端口 22222 的服务穿透到 frps
主机的 23456 端口上:
[[proxies]]
name= "ssh"
type="tcp"
localIP = "127.0.0.1"
localPort = 22222
remotePort = 23456transport.proxyProtocolVersion = "v2"
这里 proxy protocol 配置一定要带上,否则 go-mmproxy 无法解析到 Proxy 头
配置路由
在 root
或 sudo
权限下执行以下命令:
ip rule add from 127.0.0.1/8 iif lo table 123
ip route add local 0.0.0.0/0 dev lo table 123#ipv6 可选
ip -6 rule add from ::1/128 iif lo table 123
ip -6 route add local ::/0 dev lo table 123
-
让本机回环接口发出的流量,根据表 123 决定路由
-
匹配表 123 的流量都会直接回环,而不会被系统当作普通输出路由出去
运行 go-mmproxy
在 go-mmproxy
同级目录下创建文件 path-prefixes.txt
:
192.168.0.0/16
10.0.0.0/8
127.0.0.1/8
表示只有这些网段的 ip 才能使用 go-mmproxy
进行转发,避免被外部利用
然后执行命令运行 go-mmproxy
:
sudo ./go-mmproxy -l 0.0.0.0:22222 -4 127.0.0.1:22 -6 [::1]:22 -p tcp --allowed-subnets ./path-prefixes.txt
之后通过公网 ip+端口访问 ssh,访问成功
在内网主机的 var/log/auth.log
记录如下:
Accepted publickey for fsj2009yx from 117.150.164.176 port 45423 ssh2:
客户端源 ip 显示在日志文件上,证明透明代理实现成功