frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
本文是对frp v0.1.0 版本源码阅读后所做的分析,后续的版本大体上也是这个架构。frp v0.1.0 git分支d0a5400
frpc和frps之间的控制流
frpc和frps之间维持了一个长tcp连接,通过这个tcp连接进行控制指令的传递
frpc请求包2种类型:
- frpc启动时,发送请求启动proxy server监听端口包
- 当收到frps命令时,请求建立工作tcp连接包,该工作tcp连接用于传输用户数据
frps控制包1种类型:
- 给frpc发送指令,让frpc建立一条向frps指定端口的工作tcp,并在这个tcp上发送请求建立工作tcp连接包,以便frps能够识别出这个工作tcp,并将该工作tcp与用户tcp绑定
用户、frps、frpc、目标地址之间的数据流
工作步骤示意图
-
用户访问frps指定端口
-
通过控制tcp连接 frps向frpc发送建立一个新的工作tcp指令
-
收到frps指令后 frpc新建一条本地tcp和工作tcp,并将这两个tcp绑定。之后通过工作tcp发送该工作tcp所属的代理名至frps,一边frps识别。
-
收到工作tcp上的请求后,通过代理名找到对应的用户tcp,将工作tcp与用户请求tcp绑定。此时,用户和目标地址之间就建立起如下所示的tcp链路,就可以开始互相传输数据啦!
实现细节
frp绑定两个tcp连接的函数
go语言
// will block until connection close func Join(c1 *Conn, c2 *Conn) { var wait sync.WaitGroup pipe := func(to *Conn, from *Conn) { defer to.Close() defer from.Close() defer wait.Done() var err error _, err = io.Copy(to.TcpConn, from.TcpConn) if err != nil { log.Warn("join conns error, %v", err) } } wait.Add(2) go pipe(c1, c2) go pipe(c2, c1) wait.Wait() return }