看看frp源码(一)
frp介绍
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。
阅读目标
frp在github上非常热门,有多达30k+个star,这边我们尝试看一下frp的源码,主要目的看看frp对tcp和http进行反向代理的实现,顺便进一步熟悉tcp,http协议的一些实现细节。
frp启动
frp在服务端和客户端分为frps和frpc两个程序,我们这边以tcp代理ssh端口和代理http协议为例,在frps所在的外网服务器编写配置文件frps.ini
[common]
bind_port = 8886
vhost_http_port = 80
其中 * bind_port表示frps在本地进行监听通信的端口 * vhost_http_port表示frps在本地监听的外部http访问端口。
在frpc所在的内网服务器编写配置文件frpc.ini
[common]
server_addr = frps.ser.ver.ip
server_port = 8886
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 8888
[web]
type = http
local_port = 8080
custom_domains = www.domain.com
其中 * server_addr表示frps所在服务器的ip地址 * server_port表示frps.ini中的通信监听端口,要和frps.ini中的配置一致 * [ssh]中的配置表示将外部的tcp端口8888的请求发送到内网服务器本机127.0.0.1的22端口,实现了外网ssh服务的转发。 * [web]中的配置表示将外部对frps的vhost_http_port=80,域名为custom_domains的http请求发送到本地8080端口,实现外网http服务转发。
对于tcp应用,这里我们使用ssh -p 8888 frps.ser.ver.ip
就可以访问到内网服务器的ssh服务
相应的http应用,我们访问www.domain.com:80就可以将http请求转发到内网服务器的8080端口,实现http服务的内网穿透
入口源码解析
首先先看一下frps服务的具体实现
首先找到frps的入口函数在cmd/frps/main.go中
追查 Execute()函数看到具体的实现在cmd/frps/root.go中
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
这里rootCmd是由cobra构建的,cobra是一个命令行构建工具,可以方便的构建多级多命令的命令行程序,docker,k8s等都使用了cobra,使用起来十分强大。
var rootCmd = &cobra.Command{
Use: "frps",
Short: "frps is the server of frp (https://github.com/fatedier/frp)",
RunE: func(cmd *cobra.Command, args []string) error {
// RunE run with error return
if showVersion {
fmt.Println(version.Full())
return nil
}
// use config to parse ini args
// 2 method FromFile, FromCmd
var cfg config.ServerCommonConf
var err error
if cfgFile != "" {
var content string
content, err = config.GetRenderedConfFromFile(cfgFile)
if err != nil {
return err
}
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
} else {
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
}
if err != nil {
return err
}
// run!
err = runServer(cfg)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
这里RunE对应正常cobra文档中的Run,运行时会额外返回一个error,此外没有区别。
frps使用cobra构建了多个可以输入的参数,这里可以看到通过两种方式对参数进行解析
- 当指定参数文件时,打开文件并进行
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
解析 - 未指定参数文件时,将命令行输入的参数解析入cfg中
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
最后使用cfg启动服务err = runServer(cfg)
正式启动frps服务