├── .gitignore ├── Makefile ├── README.md ├── assets ├── assets.go ├── frpc │ ├── embed.go │ └── static │ │ └── 1.txt └── frps │ ├── embed.go │ └── static │ ├── favicon.ico │ ├── index-82-40HIG.js │ ├── index-rzPDshRD.css │ └── index.html ├── bin ├── frpc.toml └── frps.toml ├── client ├── admin_api.go ├── connector.go ├── control.go ├── event │ └── event.go ├── health │ └── health.go ├── proxy │ ├── general_tcp.go │ ├── proxy.go │ ├── proxy_manager.go │ ├── proxy_wrapper.go │ ├── sudp.go │ ├── udp.go │ └── xtcp.go ├── service.go └── visitor │ ├── stcp.go │ ├── sudp.go │ ├── visitor.go │ ├── visitor_manager.go │ └── xtcp.go ├── cmd ├── frpc │ ├── main.go │ └── sub │ │ ├── admin.go │ │ ├── nathole.go │ │ ├── proxy.go │ │ ├── root.go │ │ └── verify.go └── frps │ ├── encryption.go │ ├── main.go │ ├── root.go │ └── verify.go ├── cmd1 └── frpc │ └── main.txt ├── go.mod ├── go.sum ├── img ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png └── logo.png ├── pkg ├── auth │ ├── auth.go │ ├── legacy │ │ └── legacy.go │ ├── oidc.go │ ├── pass.go │ └── token.go ├── config │ ├── flags.go │ ├── legacy │ │ ├── README.md │ │ ├── client.go │ │ ├── conversion.go │ │ ├── parse.go │ │ ├── proxy.go │ │ ├── server.go │ │ ├── utils.go │ │ ├── value.go │ │ └── visitor.go │ ├── load.go │ ├── load_test.go │ ├── template.go │ ├── types │ │ ├── types.go │ │ └── types_test.go │ └── v1 │ │ ├── api.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── common.go │ │ ├── plugin.go │ │ ├── proxy.go │ │ ├── proxy_test.go │ │ ├── server.go │ │ ├── server_test.go │ │ ├── validation │ │ ├── client.go │ │ ├── common.go │ │ ├── plugin.go │ │ ├── proxy.go │ │ ├── server.go │ │ ├── validation.go │ │ └── visitor.go │ │ └── visitor.go ├── errors │ └── errors.go ├── metrics │ ├── aggregate │ │ └── server.go │ ├── mem │ │ ├── server.go │ │ └── types.go │ ├── metrics.go │ └── prometheus │ │ └── server.go ├── msg │ ├── ctl.go │ ├── handler.go │ └── msg.go ├── nathole │ ├── analysis.go │ ├── classify.go │ ├── controller.go │ ├── discovery.go │ ├── nathole.go │ └── utils.go ├── plugin │ ├── client │ │ ├── http2https.go │ │ ├── http_proxy.go │ │ ├── https2http.go │ │ ├── https2https.go │ │ ├── plugin.go │ │ ├── socks5.go │ │ ├── static_file.go │ │ └── unix_domain_socket.go │ └── server │ │ ├── http.go │ │ ├── manager.go │ │ ├── plugin.go │ │ ├── tracer.go │ │ └── types.go ├── proto │ └── udp │ │ ├── udp.go │ │ └── udp_test.go ├── sdk │ └── client │ │ └── client.go ├── ssh │ ├── gateway.go │ ├── server.go │ └── terminal.go ├── transport │ ├── message.go │ └── tls.go ├── util │ ├── http │ │ ├── http.go │ │ └── server.go │ ├── limit │ │ ├── reader.go │ │ └── writer.go │ ├── log │ │ └── log.go │ ├── metric │ │ ├── counter.go │ │ ├── counter_test.go │ │ ├── date_counter.go │ │ ├── date_counter_test.go │ │ └── metrics.go │ ├── myutil │ │ ├── aes.go │ │ └── myutil.go │ ├── net │ │ ├── conn.go │ │ ├── dial.go │ │ ├── dns.go │ │ ├── http.go │ │ ├── kcp.go │ │ ├── listener.go │ │ ├── tls.go │ │ ├── udp.go │ │ └── websocket.go │ ├── system │ │ ├── system.go │ │ └── system_android.go │ ├── tcpmux │ │ └── httpconnect.go │ ├── util │ │ ├── types.go │ │ ├── util.go │ │ └── util_test.go │ ├── version │ │ └── version.go │ ├── vhost │ │ ├── http.go │ │ ├── https.go │ │ ├── https_test.go │ │ ├── resource.go │ │ ├── router.go │ │ └── vhost.go │ ├── wait │ │ └── backoff.go │ └── xlog │ │ ├── ctx.go │ │ └── xlog.go └── virtual │ └── client.go └── server ├── control.go ├── controller └── resource.go ├── dashboard_api.go ├── group ├── group.go ├── http.go ├── tcp.go └── tcpmux.go ├── metrics └── metrics.go ├── ports └── ports.go ├── proxy ├── http.go ├── https.go ├── proxy.go ├── stcp.go ├── sudp.go ├── tcp.go ├── tcpmux.go ├── udp.go └── xtcp.go ├── service.go └── visitor └── visitor.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Self 27 | test/ 28 | bin/frps 29 | bin/frpc 30 | bin/frpc_encrypt.txt 31 | packages/ 32 | release/ 33 | test/bin/ 34 | vendor/ 35 | lastversion/ 36 | dist/ 37 | .idea/ 38 | .vscode/ 39 | .autogen_ssh_key 40 | client.crt 41 | client.key 42 | 43 | # Cache 44 | *.swp 45 | .DS_Store 46 | **/.DS_Store -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | frps-linux: 2 | env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frps -o bin/frps ./cmd/frps 3 | 4 | frps-darwin: 5 | env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frps -o bin/frps ./cmd/frps 6 | 7 | frps-windows: 8 | env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frps -o bin/frps.exe ./cmd/frps 9 | 10 | frpc-darwin: 11 | env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frpc -o bin/frpc ./cmd1/frpc 12 | 13 | frpc-linux: 14 | env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frpc -o bin/frpc ./cmd1/frpc 15 | 16 | frpc-windows: 17 | env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frpc -o bin/frpc.exe ./cmd1/frpc 18 | 19 | frpc-windows-x: 20 | env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 garble build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frpc -o bin/frpc.exe ./cmd1/frpc 21 | 22 | frpc-linux-x: 23 | env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 garble build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frpc -o bin/frpc ./cmd1/frpc 24 | 25 | frpc-darwin-x: 26 | env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 garble build -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -tags frpc -o bin/frpc ./cmd1/frpc 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 团队介绍 2 | > **重庆九宫格安全团队**致力于分享开源安全工具、研究方向覆盖红蓝对抗、Web安全、移动安全、安全开发、企业安全建设、物联网/工控安全/AI/量子安全等多个领域,对安全感兴趣的小伙伴可以关注我们。 3 | 4 | ![img.png](./img/logo.png) 5 | 6 | 7 | # frp-0.58.1魔改功能介绍 8 | 9 | ## 功能介绍 10 | - 默认配置tls加密传输,默认模板为socks5隧道 11 | - 随机生成socks5账户和密码 12 | - frpc去掉-h帮助,执行无输出,需要指定参数运行 13 | - frps钉钉通知上线,输出信息 14 | - frps钉钉掉线通知 15 | - 去掉frpc静态web资源、删除不必要的模块、替换frpc默认包名、替换frp关键字,编译器进行混淆编译(静态免杀) 16 | - 关闭tls 0x17流量特征(frp新版自带功能) 17 | - socks5隧道时,remotePort端口在frps端指定的端口范围内随机选择(默认40000-50000端口)、随机插件name值(hostname+随机字符串) 18 | > 执行./frpc后,remotePort端口将在指定的端口范围内随机选择一个端口,若端口被占用,将自动重新随机选择,直到选择到没有被占用的端口 19 | 20 | > 好处是:**一次编译,到处运行**。不用每次重新编辑frpc.toml指定remotePort和name值;方便在攻防比赛中,多台服务器上线 21 | 22 | - frpc客户端运行时从oss读取AES加密后的frpc配置内容,且通过域前置方式读取,无法抓包得到oss域名 23 | > 攻防演练结束后直接删除oss配置文件,无留痕 24 | 25 | - 再次加密frpc关键信息(oss域名、ossIP、frpc解密密钥等),防止静态分析出关键信息 26 | 27 | ## 保姆级-食用指南 28 | > 安装并配置go、make(可省略)环境 29 | 30 | ### 一、开通OSS存储桶 31 | > 权限设置为oss公共可读 32 | 33 | ### 加密frpc配置内容上传到oss 34 | 35 | > 根据自己电脑操作系统类型,查看**Makefile**文件,编译**frps**,若没有相应的操作系统请自行增加 36 | 37 | > 以mac arm架构为例, 执行 `make frps-darwin` 将在bin目录下生成frps 38 | 39 | 配置**bin/frpc.toml**内容,默认只需更改serverAddr、serverPort和token值即可 40 | 41 | ![img.png](./img/1.png) 42 | 43 | > 运行 **frps encrypt** 命令 加密frpc配置文件frpc.toml,生成配置加密文件:frpc_encrypt.txt 44 | 45 | -d:你注册的oss域名地址 46 | 47 | -u:oss域名对应的 IP 地址(阿里云官方服务器) 48 | 49 | /frpc.txt: oss存储对应位置文件 50 | 51 | ```shell 52 | ./frps encrypt --frpcFile frpc.toml -d xxxxoss.aliyuncs.com -u https://阿里云ossIP地址/frpc.txt 53 | ``` 54 | 55 | 将会在同级目录下生成frpc_encrypt.txt文件: 56 | ![img.png](./img/2.png) 57 | 58 | 将加密后的文本上传到oss上,注意权限公共可读 59 | ![img.png](./img/3.png) 60 | 61 | 62 | ### 配置frps.toml 63 | > 配置frps.toml,其中webhook为钉钉webhook通知; 64 | > webhookFlag为true时,将开启钉钉通知 65 | 66 | ![img.png](./img/4.png) 67 | 68 | 69 | ### 编译frpc客户端 70 | > 执行./frps encrypt命令时,已自动把相关frpc配置加密信息填充到frpc模板中,其中`root@pts/0`为启动命令参数,可自行修改 71 | 72 | ![img.png](./img/5.png) 73 | 74 | > 这里只需要执行相应客户端编译命令即可, 请查看Makefile文件内容 75 | 76 | > 执行 `make frpc-darwin`命令,将在bin目录生成frpc客户端 77 | 78 | > 若需要混淆编译,请使用`garble`进行编译 79 | 80 | ### 运行效果图 81 | 82 | 可多次执行frpc客户端,钉钉通知随机生成的socks5账户密码及端口 83 | ![img.png](./img/6.png) 84 | 85 | ![img.png](./img/7.png) 86 | 87 | 88 | ### oss域前置抓包效果 89 | 如果直接填写oss域名, https抓包将会在SNI显示oss域名 90 | 91 | ![img.png](./img/8.png) 92 | 93 | 如果通过配置 host访问,将无法得到oss域名,还有一个好处是服务器若没配置dns,直接写oss域名将无法解析 94 | 95 | ![img.png](./img/9.png) 96 | 97 | ## -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package assets 16 | 17 | import ( 18 | "io/fs" 19 | "net/http" 20 | ) 21 | 22 | var ( 23 | // read-only filesystem created by "embed" for embedded files 24 | content fs.FS 25 | 26 | FileSystem http.FileSystem 27 | 28 | // if prefix is not empty, we get file content from disk 29 | prefixPath string 30 | ) 31 | 32 | // if path is empty, load assets in memory 33 | // or set FileSystem using disk files 34 | func Load(path string) { 35 | prefixPath = path 36 | if prefixPath != "" { 37 | FileSystem = http.Dir(prefixPath) 38 | } else { 39 | FileSystem = http.FS(content) 40 | } 41 | } 42 | 43 | func Register(fileSystem fs.FS) { 44 | subFs, err := fs.Sub(fileSystem, "static") 45 | if err == nil { 46 | content = subFs 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assets/frpc/embed.go: -------------------------------------------------------------------------------- 1 | package frpc 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/xx/xxx/assets" 7 | ) 8 | 9 | //go:embed static/* 10 | var content embed.FS 11 | 12 | func init() { 13 | assets.Register(content) 14 | } 15 | -------------------------------------------------------------------------------- /assets/frpc/static/1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/assets/frpc/static/1.txt -------------------------------------------------------------------------------- /assets/frps/embed.go: -------------------------------------------------------------------------------- 1 | package frpc 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/xx/xxx/assets" 7 | ) 8 | 9 | //go:embed static/* 10 | var content embed.FS 11 | 12 | func init() { 13 | assets.Register(content) 14 | } 15 | -------------------------------------------------------------------------------- /assets/frps/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/assets/frps/static/favicon.ico -------------------------------------------------------------------------------- /assets/frps/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frps dashboard 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bin/frpc.toml: -------------------------------------------------------------------------------- 1 | serverAddr = "127.0.0.1" 2 | serverPort = 9000 3 | auth.method = "token" 4 | auth.token = "82cc921c6a5c6707e1d6e6862ba3201a" 5 | transport.tls.enable = true 6 | disable_custom_tls_first_byte = true 7 | 8 | [[proxies]] 9 | type = "tcp" 10 | name = "{test}" 11 | remotePort = 9010 12 | transport.useEncryption = true 13 | transport.useCompression = true 14 | [proxies.plugin] 15 | type = "socks5" 16 | username = "{username}" 17 | password = "{password}" -------------------------------------------------------------------------------- /bin/frps.toml: -------------------------------------------------------------------------------- 1 | bindAddr = "127.0.0.1" 2 | bindPort = 9000 3 | auth.method = "token" 4 | auth.token = "82cc921c6a5c6707e1d6e6862ba3201a" 5 | transport.tls.force = true 6 | webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxxxx" 7 | webhookFlag = true -------------------------------------------------------------------------------- /client/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/xx/xxx/pkg/msg" 7 | ) 8 | 9 | var ErrPayloadType = errors.New("error payload type") 10 | 11 | type Handler func(payload interface{}) error 12 | 13 | type StartProxyPayload struct { 14 | NewProxyMsg *msg.NewProxy 15 | } 16 | 17 | type CloseProxyPayload struct { 18 | CloseProxyMsg *msg.CloseProxy 19 | } 20 | -------------------------------------------------------------------------------- /client/proxy/general_tcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | 20 | v1 "github.com/xx/xxx/pkg/config/v1" 21 | ) 22 | 23 | func init() { 24 | pxyConfs := []v1.ProxyConfigurer{ 25 | &v1.TCPProxyConfig{}, 26 | &v1.HTTPProxyConfig{}, 27 | &v1.HTTPSProxyConfig{}, 28 | &v1.STCPProxyConfig{}, 29 | &v1.TCPMuxProxyConfig{}, 30 | } 31 | for _, cfg := range pxyConfs { 32 | RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy) 33 | } 34 | } 35 | 36 | // GeneralTCPProxy 是针对TCP协议的Proxy接口的通用实现。 37 | // 如果默认的GeneralTCPProxy不能满足要求,可以自定义 38 | // Proxy接口的实现。 39 | type GeneralTCPProxy struct { 40 | *BaseProxy 41 | } 42 | 43 | func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy { 44 | return &GeneralTCPProxy{ 45 | BaseProxy: baseProxy, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/visitor/visitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package visitor 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "sync" 21 | 22 | v1 "github.com/xx/xxx/pkg/config/v1" 23 | "github.com/xx/xxx/pkg/transport" 24 | netpkg "github.com/xx/xxx/pkg/util/net" 25 | "github.com/xx/xxx/pkg/util/xlog" 26 | ) 27 | 28 | // Helper wraps some functions for visitor to use. 29 | type Helper interface { 30 | // ConnectServer directly connects to the frp server. 31 | ConnectServer() (net.Conn, error) 32 | // TransferConn transfers the connection to another visitor. 33 | TransferConn(string, net.Conn) error 34 | // MsgTransporter returns the message transporter that is used to send and receive messages 35 | // to the frp server through the controller. 36 | MsgTransporter() transport.MessageTransporter 37 | // RunID returns the run id of current controller. 38 | RunID() string 39 | } 40 | 41 | // Visitor is used for forward traffics from local port tot remote service. 42 | type Visitor interface { 43 | Run() error 44 | AcceptConn(conn net.Conn) error 45 | Close() 46 | } 47 | 48 | func NewVisitor( 49 | ctx context.Context, 50 | cfg v1.VisitorConfigurer, 51 | clientCfg *v1.ClientCommonConfig, 52 | helper Helper, 53 | ) (visitor Visitor) { 54 | xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name) 55 | baseVisitor := BaseVisitor{ 56 | clientCfg: clientCfg, 57 | helper: helper, 58 | ctx: xlog.NewContext(ctx, xl), 59 | internalLn: netpkg.NewInternalListener(), 60 | } 61 | switch cfg := cfg.(type) { 62 | case *v1.STCPVisitorConfig: 63 | visitor = &STCPVisitor{ 64 | BaseVisitor: &baseVisitor, 65 | cfg: cfg, 66 | } 67 | case *v1.XTCPVisitorConfig: 68 | visitor = &XTCPVisitor{ 69 | BaseVisitor: &baseVisitor, 70 | cfg: cfg, 71 | startTunnelCh: make(chan struct{}), 72 | } 73 | case *v1.SUDPVisitorConfig: 74 | visitor = &SUDPVisitor{ 75 | BaseVisitor: &baseVisitor, 76 | cfg: cfg, 77 | checkCloseCh: make(chan struct{}), 78 | } 79 | } 80 | return 81 | } 82 | 83 | type BaseVisitor struct { 84 | clientCfg *v1.ClientCommonConfig 85 | helper Helper 86 | l net.Listener 87 | internalLn *netpkg.InternalListener 88 | 89 | mu sync.RWMutex 90 | ctx context.Context 91 | } 92 | 93 | func (v *BaseVisitor) AcceptConn(conn net.Conn) error { 94 | return v.internalLn.PutConn(conn) 95 | } 96 | 97 | func (v *BaseVisitor) Close() { 98 | if v.l != nil { 99 | v.l.Close() 100 | } 101 | if v.internalLn != nil { 102 | v.internalLn.Close() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /cmd/frpc/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | _ "github.com/xx/xxx/assets/frpc" 19 | "github.com/xx/xxx/cmd/frpc/sub" 20 | "github.com/xx/xxx/pkg/util/system" 21 | ) 22 | 23 | func main() { 24 | system.EnableCompatibilityMode() 25 | sub.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /cmd/frpc/sub/admin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strings" 21 | 22 | "github.com/rodaine/table" 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/xx/xxx/pkg/config" 26 | v1 "github.com/xx/xxx/pkg/config/v1" 27 | clientsdk "github.com/xx/xxx/pkg/sdk/client" 28 | ) 29 | 30 | func init() { 31 | rootCmd.AddCommand(NewAdminCommand( 32 | "reload", 33 | "Hot-Reload frpc configuration", 34 | ReloadHandler, 35 | )) 36 | 37 | rootCmd.AddCommand(NewAdminCommand( 38 | "status", 39 | "Overview of all proxies status", 40 | StatusHandler, 41 | )) 42 | 43 | rootCmd.AddCommand(NewAdminCommand( 44 | "stop", 45 | "Stop the running frpc", 46 | StopHandler, 47 | )) 48 | } 49 | 50 | func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command { 51 | return &cobra.Command{ 52 | Use: name, 53 | Short: short, 54 | Run: func(cmd *cobra.Command, args []string) { 55 | cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode) 56 | if err != nil { 57 | fmt.Println(err) 58 | os.Exit(1) 59 | } 60 | if cfg.WebServer.Port <= 0 { 61 | fmt.Println("web server port should be set if you want to use this feature") 62 | os.Exit(1) 63 | } 64 | 65 | if err := handler(cfg); err != nil { 66 | fmt.Println(err) 67 | os.Exit(1) 68 | } 69 | }, 70 | } 71 | } 72 | 73 | func ReloadHandler(clientCfg *v1.ClientCommonConfig) error { 74 | client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port) 75 | client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password) 76 | if err := client.Reload(strictConfigMode); err != nil { 77 | return err 78 | } 79 | fmt.Println("reload success") 80 | return nil 81 | } 82 | 83 | func StatusHandler(clientCfg *v1.ClientCommonConfig) error { 84 | client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port) 85 | client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password) 86 | res, err := client.GetAllProxyStatus() 87 | if err != nil { 88 | return err 89 | } 90 | 91 | fmt.Printf("Proxy Status...\n\n") 92 | for _, typ := range proxyTypes { 93 | arrs := res[string(typ)] 94 | if len(arrs) == 0 { 95 | continue 96 | } 97 | 98 | fmt.Println(strings.ToUpper(string(typ))) 99 | tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") 100 | for _, ps := range arrs { 101 | tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) 102 | } 103 | tbl.Print() 104 | fmt.Println("") 105 | } 106 | return nil 107 | } 108 | 109 | func StopHandler(clientCfg *v1.ClientCommonConfig) error { 110 | client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port) 111 | client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password) 112 | if err := client.Stop(); err != nil { 113 | return err 114 | } 115 | fmt.Println("stop success") 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /cmd/frpc/sub/nathole.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | 23 | "github.com/xx/xxx/pkg/config" 24 | v1 "github.com/xx/xxx/pkg/config/v1" 25 | "github.com/xx/xxx/pkg/nathole" 26 | ) 27 | 28 | var ( 29 | natHoleSTUNServer string 30 | natHoleLocalAddr string 31 | ) 32 | 33 | func init() { 34 | rootCmd.AddCommand(natholeCmd) 35 | natholeCmd.AddCommand(natholeDiscoveryCmd) 36 | 37 | natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "", "STUN server address for nathole") 38 | natholeCmd.PersistentFlags().StringVarP(&natHoleLocalAddr, "nat_hole_local_addr", "l", "", "local address to connect STUN server") 39 | } 40 | 41 | var natholeCmd = &cobra.Command{ 42 | Use: "nathole", 43 | Short: "Actions about nathole", 44 | } 45 | 46 | var natholeDiscoveryCmd = &cobra.Command{ 47 | Use: "discover", 48 | Short: "Discover nathole information from stun server", 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | // ignore error here, because we can use command line pameters 51 | cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode, false) 52 | if err != nil { 53 | cfg = &v1.ClientCommonConfig{} 54 | cfg.Complete() 55 | } 56 | if natHoleSTUNServer != "" { 57 | cfg.NatHoleSTUNServer = natHoleSTUNServer 58 | } 59 | 60 | if err := validateForNatHoleDiscovery(cfg); err != nil { 61 | fmt.Println(err) 62 | os.Exit(1) 63 | } 64 | 65 | addrs, localAddr, err := nathole.Discover([]string{cfg.NatHoleSTUNServer}, natHoleLocalAddr) 66 | if err != nil { 67 | fmt.Println("discover error:", err) 68 | os.Exit(1) 69 | } 70 | if len(addrs) < 2 { 71 | fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addrs) 72 | os.Exit(1) 73 | } 74 | 75 | localIPs, _ := nathole.ListLocalIPsForNatHole(10) 76 | 77 | natFeature, err := nathole.ClassifyNATFeature(addrs, localIPs) 78 | if err != nil { 79 | fmt.Println("classify nat feature error:", err) 80 | os.Exit(1) 81 | } 82 | fmt.Println("STUN server:", cfg.NatHoleSTUNServer) 83 | fmt.Println("Your NAT type is:", natFeature.NatType) 84 | fmt.Println("Behavior is:", natFeature.Behavior) 85 | fmt.Println("External address is:", addrs) 86 | fmt.Println("Local address is:", localAddr.String()) 87 | fmt.Println("Public Network:", natFeature.PublicNetwork) 88 | return nil 89 | }, 90 | } 91 | 92 | func validateForNatHoleDiscovery(cfg *v1.ClientCommonConfig) error { 93 | if cfg.NatHoleSTUNServer == "" { 94 | return fmt.Errorf("nat_hole_stun_server can not be empty") 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /cmd/frpc/sub/proxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "slices" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/xx/xxx/pkg/config" 25 | v1 "github.com/xx/xxx/pkg/config/v1" 26 | "github.com/xx/xxx/pkg/config/v1/validation" 27 | ) 28 | 29 | var proxyTypes = []v1.ProxyType{ 30 | v1.ProxyTypeTCP, 31 | v1.ProxyTypeUDP, 32 | v1.ProxyTypeTCPMUX, 33 | v1.ProxyTypeHTTP, 34 | v1.ProxyTypeHTTPS, 35 | v1.ProxyTypeSTCP, 36 | v1.ProxyTypeSUDP, 37 | v1.ProxyTypeXTCP, 38 | } 39 | 40 | var visitorTypes = []v1.VisitorType{ 41 | v1.VisitorTypeSTCP, 42 | v1.VisitorTypeSUDP, 43 | v1.VisitorTypeXTCP, 44 | } 45 | 46 | func init() { 47 | for _, typ := range proxyTypes { 48 | c := v1.NewProxyConfigurerByType(typ) 49 | if c == nil { 50 | panic("proxy type: " + typ + " not support") 51 | } 52 | 53 | clientCfg := v1.ClientCommonConfig{} 54 | cmd := NewProxyCommand(string(typ), c, &clientCfg) 55 | config.RegisterClientCommonConfigFlags(cmd, &clientCfg) 56 | config.RegisterProxyFlags(cmd, c) 57 | 58 | // add sub command for visitor 59 | if slices.Contains(visitorTypes, v1.VisitorType(typ)) { 60 | vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ)) 61 | if vc == nil { 62 | panic("visitor type: " + typ + " not support") 63 | } 64 | visitorCmd := NewVisitorCommand(string(typ), vc, &clientCfg) 65 | config.RegisterVisitorFlags(visitorCmd, vc) 66 | cmd.AddCommand(visitorCmd) 67 | } 68 | rootCmd.AddCommand(cmd) 69 | } 70 | } 71 | 72 | func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command { 73 | return &cobra.Command{ 74 | Use: name, 75 | Short: fmt.Sprintf("Run frpc with a single %s proxy", name), 76 | Run: func(cmd *cobra.Command, args []string) { 77 | clientCfg.Complete() 78 | if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil { 79 | fmt.Println(err) 80 | os.Exit(1) 81 | } 82 | 83 | c.Complete(clientCfg.User) 84 | c.GetBaseConfig().Type = name 85 | if err := validation.ValidateProxyConfigurerForClient(c); err != nil { 86 | fmt.Println(err) 87 | os.Exit(1) 88 | } 89 | err := startService(clientCfg, []v1.ProxyConfigurer{c}, nil, "") 90 | if err != nil { 91 | fmt.Println(err) 92 | os.Exit(1) 93 | } 94 | }, 95 | } 96 | } 97 | 98 | func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command { 99 | return &cobra.Command{ 100 | Use: "visitor", 101 | Short: fmt.Sprintf("Run frpc with a single %s visitor", name), 102 | Run: func(cmd *cobra.Command, args []string) { 103 | clientCfg.Complete() 104 | if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil { 105 | fmt.Println(err) 106 | os.Exit(1) 107 | } 108 | 109 | c.Complete(clientCfg) 110 | c.GetBaseConfig().Type = name 111 | if err := validation.ValidateVisitorConfigurer(c); err != nil { 112 | fmt.Println(err) 113 | os.Exit(1) 114 | } 115 | err := startService(clientCfg, nil, []v1.VisitorConfigurer{c}, "") 116 | if err != nil { 117 | fmt.Println(err) 118 | os.Exit(1) 119 | } 120 | }, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /cmd/frpc/sub/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | 23 | "github.com/xx/xxx/pkg/config" 24 | "github.com/xx/xxx/pkg/config/v1/validation" 25 | ) 26 | 27 | func init() { 28 | rootCmd.AddCommand(verifyCmd) 29 | } 30 | 31 | var verifyCmd = &cobra.Command{ 32 | Use: "verify", 33 | Short: "Verify that the configures is valid", 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | if cfgFile == "" { 36 | fmt.Println("frpc: the configuration file is not specified") 37 | return nil 38 | } 39 | 40 | cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode, false) 41 | if err != nil { 42 | fmt.Println(err) 43 | os.Exit(1) 44 | } 45 | warning, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs) 46 | if warning != nil { 47 | fmt.Printf("WARNING: %v\n", warning) 48 | } 49 | if err != nil { 50 | fmt.Println(err) 51 | os.Exit(1) 52 | } 53 | 54 | fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile) 55 | return nil 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /cmd/frps/encryption.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "github.com/xx/xxx/pkg/util/myutil" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | rootCmd.AddCommand(encrypt) 17 | } 18 | 19 | var encrypt = &cobra.Command{ 20 | Use: "encrypt", 21 | Short: "Encrypt frpc.toml", 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | if frpcFile == "" { 24 | fmt.Println("frpc.toml: the configuration file is not specified") 25 | return nil 26 | } 27 | if ossUrl == "" || ossDomain == "" { 28 | fmt.Println("Incomplete parameters: ossUrl or ossDomain") 29 | return nil 30 | } 31 | content, err := os.ReadFile(frpcFile) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | frpcDataStr := string(content) 36 | key := myutil.GenerateAESKey() 37 | encrypted := myutil.AesEncryptECB([]byte(frpcDataStr), []byte(key)) 38 | encryptedB64 := base64.StdEncoding.EncodeToString(encrypted) 39 | outStr := fmt.Sprintf("%s", encryptedB64) 40 | if err := os.WriteFile("frpc_encrypt.txt", []byte(outStr), 0666); err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | key1 := []byte(myutil.GenerateAESKey()) // 加密的密钥 45 | url1 := base64.StdEncoding.EncodeToString(myutil.AesEncryptECB([]byte(ossUrl), key1)) 46 | domain := base64.StdEncoding.EncodeToString(myutil.AesEncryptECB([]byte(ossDomain), key1)) 47 | key2 := base64.StdEncoding.EncodeToString(myutil.AesEncryptECB([]byte(key), key1)) 48 | bodyStr, err := ReadFile("../cmd1/frpc/main.txt") 49 | if err != nil { 50 | log.Fatal(err) 51 | return nil 52 | } 53 | bodyStr = strings.Replace(bodyStr, "{key}", key2, 1) 54 | bodyStr = strings.Replace(bodyStr, "{url}", url1, 1) 55 | bodyStr = strings.Replace(bodyStr, "{domain}", domain, 1) 56 | bodyStr = strings.Replace(bodyStr, "{key1}", string(key1), 1) 57 | err = ioutil.WriteFile("../cmd1/frpc/main.go", []byte(bodyStr), 0644) 58 | if err != nil { 59 | log.Fatal(err) 60 | return nil 61 | } 62 | log.Printf("[+] The frpc configuration file is encrypted successfully, please check the frpc_encrypt.txt file") 63 | return nil 64 | }, 65 | } 66 | 67 | func ReadFile(filePath string) (string, error) { 68 | content, err := ioutil.ReadFile(filePath) 69 | if err != nil { 70 | return "", err 71 | } 72 | return string(content), nil 73 | } 74 | -------------------------------------------------------------------------------- /cmd/frps/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | _ "github.com/xx/xxx/assets/frps" 19 | _ "github.com/xx/xxx/pkg/metrics" 20 | "github.com/xx/xxx/pkg/util/system" 21 | ) 22 | 23 | func main() { 24 | system.EnableCompatibilityMode() 25 | Execute() 26 | } 27 | -------------------------------------------------------------------------------- /cmd/frps/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/xx/xxx/pkg/config" 25 | v1 "github.com/xx/xxx/pkg/config/v1" 26 | "github.com/xx/xxx/pkg/config/v1/validation" 27 | "github.com/xx/xxx/pkg/util/log" 28 | "github.com/xx/xxx/pkg/util/version" 29 | "github.com/xx/xxx/server" 30 | ) 31 | 32 | var ( 33 | cfgFile string 34 | showVersion bool 35 | strictConfigMode bool 36 | frpcFile string 37 | ossUrl string 38 | ossDomain string 39 | 40 | serverCfg v1.ServerConfig 41 | ) 42 | 43 | func init() { 44 | rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps") 45 | rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps") 46 | rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause errors") 47 | rootCmd.PersistentFlags().StringVarP(&frpcFile, "frpcFile", "f", "", "frpc.toml file") 48 | rootCmd.PersistentFlags().StringVarP(&ossUrl, "ossUrl", "u", "", "ossurl") 49 | rootCmd.PersistentFlags().StringVarP(&ossDomain, "ossDomain", "d", "", "ossDomain") 50 | 51 | config.RegisterServerConfigFlags(rootCmd, &serverCfg) 52 | } 53 | 54 | var rootCmd = &cobra.Command{ 55 | Use: "frps", 56 | Short: "frps is the server of frp (https://github.com/xx/xxx)", 57 | RunE: func(cmd *cobra.Command, args []string) error { 58 | if showVersion { 59 | fmt.Println(version.Full()) 60 | return nil 61 | } 62 | 63 | var ( 64 | svrCfg *v1.ServerConfig 65 | isLegacyFormat bool 66 | err error 67 | ) 68 | if cfgFile != "" { 69 | svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile, strictConfigMode) 70 | if err != nil { 71 | fmt.Println(err) 72 | os.Exit(1) 73 | } 74 | if isLegacyFormat { 75 | fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " + 76 | "please use yaml/json/toml format instead!\n") 77 | } 78 | } else { 79 | serverCfg.Complete() 80 | svrCfg = &serverCfg 81 | } 82 | 83 | warning, err := validation.ValidateServerConfig(svrCfg) 84 | if warning != nil { 85 | fmt.Printf("WARNING: %v\n", warning) 86 | } 87 | if err != nil { 88 | fmt.Println(err) 89 | os.Exit(1) 90 | } 91 | 92 | if err := runServer(svrCfg); err != nil { 93 | fmt.Println(err) 94 | os.Exit(1) 95 | } 96 | return nil 97 | }, 98 | } 99 | 100 | func Execute() { 101 | rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc) 102 | if err := rootCmd.Execute(); err != nil { 103 | os.Exit(1) 104 | } 105 | } 106 | 107 | func runServer(cfg *v1.ServerConfig) (err error) { 108 | log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor) 109 | 110 | if cfgFile != "" { 111 | log.Infof("frps uses config file: %s", cfgFile) 112 | } else { 113 | log.Infof("frps uses command line arguments for config") 114 | } 115 | 116 | svr, err := server.NewService(cfg) 117 | if err != nil { 118 | return err 119 | } 120 | log.Infof("frps started successfully") 121 | svr.Run(context.Background()) 122 | return 123 | } 124 | -------------------------------------------------------------------------------- /cmd/frps/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | 23 | "github.com/xx/xxx/pkg/config" 24 | "github.com/xx/xxx/pkg/config/v1/validation" 25 | ) 26 | 27 | func init() { 28 | rootCmd.AddCommand(verifyCmd) 29 | } 30 | 31 | var verifyCmd = &cobra.Command{ 32 | Use: "verify", 33 | Short: "Verify that the configures is valid", 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | if cfgFile == "" { 36 | fmt.Println("frps: the configuration file is not specified") 37 | return nil 38 | } 39 | svrCfg, _, err := config.LoadServerConfig(cfgFile, strictConfigMode) 40 | if err != nil { 41 | fmt.Println(err) 42 | os.Exit(1) 43 | } 44 | 45 | warning, err := validation.ValidateServerConfig(svrCfg) 46 | if warning != nil { 47 | fmt.Printf("WARNING: %v\n", warning) 48 | } 49 | if err != nil { 50 | fmt.Println(err) 51 | os.Exit(1) 52 | } 53 | fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile) 54 | return nil 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xx/xxx 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 7 | github.com/coreos/go-oidc/v3 v3.10.0 8 | github.com/fatedier/golib v0.5.0 9 | github.com/gorilla/mux v1.8.1 10 | github.com/hashicorp/yamux v0.1.1 11 | github.com/pelletier/go-toml/v2 v2.2.0 12 | github.com/pion/stun/v2 v2.0.0 13 | github.com/pires/go-proxyproto v0.7.0 14 | github.com/prometheus/client_golang v1.19.0 15 | github.com/quic-go/quic-go v0.42.0 16 | github.com/rodaine/table v1.2.0 17 | github.com/samber/lo v1.39.0 18 | github.com/spf13/cobra v1.8.0 19 | github.com/spf13/pflag v1.0.5 20 | github.com/stretchr/testify v1.9.0 21 | github.com/xtaci/kcp-go/v5 v5.6.8 22 | golang.org/x/crypto v0.22.0 23 | golang.org/x/net v0.24.0 24 | golang.org/x/oauth2 v0.16.0 25 | golang.org/x/sync v0.6.0 26 | golang.org/x/time v0.5.0 27 | gopkg.in/ini.v1 v1.67.0 28 | k8s.io/apimachinery v0.28.8 29 | ) 30 | 31 | require ( 32 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect 33 | github.com/beorn7/perks v1.0.1 // indirect 34 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 35 | github.com/davecgh/go-spew v1.1.1 // indirect 36 | github.com/go-jose/go-jose/v4 v4.0.1 // indirect 37 | github.com/go-logr/logr v1.4.1 // indirect 38 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 39 | github.com/golang/protobuf v1.5.4 // indirect 40 | github.com/golang/snappy v0.0.4 // indirect 41 | github.com/google/go-cmp v0.6.0 // indirect 42 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 43 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 44 | github.com/klauspost/cpuid/v2 v2.2.6 // indirect 45 | github.com/klauspost/reedsolomon v1.12.0 // indirect 46 | github.com/kr/text v0.2.0 // indirect 47 | github.com/pion/dtls/v2 v2.2.7 // indirect 48 | github.com/pion/logging v0.2.2 // indirect 49 | github.com/pion/transport/v2 v2.2.1 // indirect 50 | github.com/pion/transport/v3 v3.0.1 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/pmezard/go-difflib v1.0.0 // indirect 53 | github.com/prometheus/client_model v0.5.0 // indirect 54 | github.com/prometheus/common v0.48.0 // indirect 55 | github.com/prometheus/procfs v0.12.0 // indirect 56 | github.com/rogpeppe/go-internal v1.11.0 // indirect 57 | github.com/templexxx/cpu v0.1.0 // indirect 58 | github.com/templexxx/xorsimd v0.4.2 // indirect 59 | github.com/tidwall/match v1.1.1 // indirect 60 | github.com/tidwall/pretty v1.2.0 // indirect 61 | github.com/tjfoc/gmsm v1.4.1 // indirect 62 | go.uber.org/mock v0.4.0 // indirect 63 | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect 64 | golang.org/x/mod v0.14.0 // indirect 65 | golang.org/x/sys v0.19.0 // indirect 66 | golang.org/x/text v0.14.0 // indirect 67 | golang.org/x/tools v0.17.0 // indirect 68 | google.golang.org/appengine v1.6.8 // indirect 69 | google.golang.org/protobuf v1.33.0 // indirect 70 | gopkg.in/yaml.v2 v2.4.0 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 73 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 74 | sigs.k8s.io/yaml v1.3.0 // indirect 75 | ) 76 | 77 | // TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository. 78 | replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d 79 | -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/4.png -------------------------------------------------------------------------------- /img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/5.png -------------------------------------------------------------------------------- /img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/6.png -------------------------------------------------------------------------------- /img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/7.png -------------------------------------------------------------------------------- /img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/8.png -------------------------------------------------------------------------------- /img/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/9.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSecurityTeam/frp/7be178acb6b9f9175b09b1275cb2e5ee61425cfa/img/logo.png -------------------------------------------------------------------------------- /pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 guylewin, guy@lewin.co.il 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "fmt" 19 | 20 | v1 "github.com/xx/xxx/pkg/config/v1" 21 | "github.com/xx/xxx/pkg/msg" 22 | ) 23 | 24 | type Setter interface { 25 | SetLogin(*msg.Login) error 26 | SetPing(*msg.Ping) error 27 | SetNewWorkConn(*msg.NewWorkConn) error 28 | } 29 | 30 | func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) { 31 | switch cfg.Method { 32 | case v1.AuthMethodToken: 33 | authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) 34 | case v1.AuthMethodOIDC: 35 | authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC) 36 | default: 37 | panic(fmt.Sprintf("wrong method: '%s'", cfg.Method)) 38 | } 39 | return authProvider 40 | } 41 | 42 | type Verifier interface { 43 | VerifyLogin(*msg.Login) error 44 | VerifyPing(*msg.Ping) error 45 | VerifyNewWorkConn(*msg.NewWorkConn) error 46 | } 47 | 48 | func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) { 49 | switch cfg.Method { 50 | case v1.AuthMethodToken: 51 | authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) 52 | case v1.AuthMethodOIDC: 53 | authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC) 54 | } 55 | return authVerifier 56 | } 57 | -------------------------------------------------------------------------------- /pkg/auth/pass.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "github.com/xx/xxx/pkg/msg" 19 | ) 20 | 21 | var AlwaysPassVerifier = &alwaysPass{} 22 | 23 | var _ Verifier = &alwaysPass{} 24 | 25 | type alwaysPass struct{} 26 | 27 | func (*alwaysPass) VerifyLogin(*msg.Login) error { return nil } 28 | 29 | func (*alwaysPass) VerifyPing(*msg.Ping) error { return nil } 30 | 31 | func (*alwaysPass) VerifyNewWorkConn(*msg.NewWorkConn) error { return nil } 32 | -------------------------------------------------------------------------------- /pkg/auth/token.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 guylewin, guy@lewin.co.il 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "fmt" 19 | "slices" 20 | "time" 21 | 22 | v1 "github.com/xx/xxx/pkg/config/v1" 23 | "github.com/xx/xxx/pkg/msg" 24 | "github.com/xx/xxx/pkg/util/util" 25 | ) 26 | 27 | type TokenAuthSetterVerifier struct { 28 | additionalAuthScopes []v1.AuthScope 29 | token string 30 | } 31 | 32 | func NewTokenAuth(additionalAuthScopes []v1.AuthScope, token string) *TokenAuthSetterVerifier { 33 | return &TokenAuthSetterVerifier{ 34 | additionalAuthScopes: additionalAuthScopes, 35 | token: token, 36 | } 37 | } 38 | 39 | func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) error { 40 | loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp) 41 | return nil 42 | } 43 | 44 | func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error { 45 | if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) { 46 | return nil 47 | } 48 | 49 | pingMsg.Timestamp = time.Now().Unix() 50 | pingMsg.PrivilegeKey = util.GetAuthKey(auth.token, pingMsg.Timestamp) 51 | return nil 52 | } 53 | 54 | func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error { 55 | if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) { 56 | return nil 57 | } 58 | 59 | newWorkConnMsg.Timestamp = time.Now().Unix() 60 | newWorkConnMsg.PrivilegeKey = util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) 61 | return nil 62 | } 63 | 64 | func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error { 65 | if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) { 66 | return fmt.Errorf("token in login doesn't match token from configuration") 67 | } 68 | return nil 69 | } 70 | 71 | func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error { 72 | if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) { 73 | return nil 74 | } 75 | 76 | if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) { 77 | return fmt.Errorf("token in heartbeat doesn't match token from configuration") 78 | } 79 | return nil 80 | } 81 | 82 | func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error { 83 | if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) { 84 | return nil 85 | } 86 | 87 | if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) { 88 | return fmt.Errorf("token in NewWorkConn doesn't match token from configuration") 89 | } 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /pkg/config/legacy/README.md: -------------------------------------------------------------------------------- 1 | So far, there is no mature Go project that does well in parsing `*.ini` files. 2 | 3 | By comparison, we have selected an open source project: `https://github.com/go-ini/ini`. 4 | 5 | This library helped us solve most of the key-value matching, but there are still some problems, such as not supporting parsing `map`. 6 | 7 | We add our own logic on the basis of this library. In the current situationwhich, we need to complete the entire `Unmarshal` in two steps: 8 | 9 | * Step#1, use `go-ini` to complete the basic parameter matching; 10 | * Step#2, parse our custom parameters to realize parsing special structure, like `map`, `array`. 11 | 12 | Some of the keywords in `tag`(like inline, extends, etc.) may be different from standard libraries such as `json` and `protobuf` in Go. For details, please refer to the library documentation: https://ini.unknwon.io/docs/intro. -------------------------------------------------------------------------------- /pkg/config/legacy/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package legacy 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | ) 23 | 24 | func ParseClientConfig(filePath string) ( 25 | cfg ClientCommonConf, 26 | proxyCfgs map[string]ProxyConf, 27 | visitorCfgs map[string]VisitorConf, 28 | err error, 29 | ) { 30 | var content []byte 31 | content, err = GetRenderedConfFromFile(filePath) 32 | if err != nil { 33 | return 34 | } 35 | configBuffer := bytes.NewBuffer(nil) 36 | configBuffer.Write(content) 37 | 38 | // Parse common section. 39 | cfg, err = UnmarshalClientConfFromIni(content) 40 | if err != nil { 41 | return 42 | } 43 | if err = cfg.Validate(); err != nil { 44 | err = fmt.Errorf("parse config error: %v", err) 45 | return 46 | } 47 | 48 | // Aggregate proxy configs from include files. 49 | var buf []byte 50 | buf, err = getIncludeContents(cfg.IncludeConfigFiles) 51 | if err != nil { 52 | err = fmt.Errorf("getIncludeContents error: %v", err) 53 | return 54 | } 55 | configBuffer.WriteString("\n") 56 | configBuffer.Write(buf) 57 | 58 | // Parse all proxy and visitor configs. 59 | proxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start) 60 | if err != nil { 61 | return 62 | } 63 | return 64 | } 65 | 66 | // getIncludeContents renders all configs from paths. 67 | // files format can be a single file path or directory or regex path. 68 | func getIncludeContents(paths []string) ([]byte, error) { 69 | out := bytes.NewBuffer(nil) 70 | for _, path := range paths { 71 | absDir, err := filepath.Abs(filepath.Dir(path)) 72 | if err != nil { 73 | return nil, err 74 | } 75 | if _, err := os.Stat(absDir); os.IsNotExist(err) { 76 | return nil, err 77 | } 78 | files, err := os.ReadDir(absDir) 79 | if err != nil { 80 | return nil, err 81 | } 82 | for _, fi := range files { 83 | if fi.IsDir() { 84 | continue 85 | } 86 | absFile := filepath.Join(absDir, fi.Name()) 87 | if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched { 88 | tmpContent, err := GetRenderedConfFromFile(absFile) 89 | if err != nil { 90 | return nil, fmt.Errorf("render extra config %s error: %v", absFile, err) 91 | } 92 | out.Write(tmpContent) 93 | out.WriteString("\n") 94 | } 95 | } 96 | } 97 | return out.Bytes(), nil 98 | } 99 | -------------------------------------------------------------------------------- /pkg/config/legacy/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package legacy 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string { 22 | m := make(map[string]string) 23 | 24 | for key, value := range set { 25 | if strings.HasPrefix(key, prefix) { 26 | m[strings.TrimPrefix(key, prefix)] = value 27 | } 28 | } 29 | 30 | if len(m) == 0 { 31 | return nil 32 | } 33 | 34 | return m 35 | } 36 | 37 | func GetMapByPrefix(set map[string]string, prefix string) map[string]string { 38 | m := make(map[string]string) 39 | 40 | for key, value := range set { 41 | if strings.HasPrefix(key, prefix) { 42 | m[key] = value 43 | } 44 | } 45 | 46 | if len(m) == 0 { 47 | return nil 48 | } 49 | 50 | return m 51 | } 52 | -------------------------------------------------------------------------------- /pkg/config/legacy/value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package legacy 16 | 17 | import ( 18 | "bytes" 19 | "os" 20 | "strings" 21 | "text/template" 22 | ) 23 | 24 | var glbEnvs map[string]string 25 | 26 | func init() { 27 | glbEnvs = make(map[string]string) 28 | envs := os.Environ() 29 | for _, env := range envs { 30 | pair := strings.SplitN(env, "=", 2) 31 | if len(pair) != 2 { 32 | continue 33 | } 34 | glbEnvs[pair[0]] = pair[1] 35 | } 36 | } 37 | 38 | type Values struct { 39 | Envs map[string]string // environment vars 40 | } 41 | 42 | func GetValues() *Values { 43 | return &Values{ 44 | Envs: glbEnvs, 45 | } 46 | } 47 | 48 | func RenderContent(in []byte) (out []byte, err error) { 49 | tmpl, errRet := template.New("frp").Parse(string(in)) 50 | if errRet != nil { 51 | err = errRet 52 | return 53 | } 54 | 55 | buffer := bytes.NewBufferString("") 56 | v := GetValues() 57 | err = tmpl.Execute(buffer, v) 58 | if err != nil { 59 | return 60 | } 61 | out = buffer.Bytes() 62 | return 63 | } 64 | 65 | func GetRenderedConfFromFile(path string) (out []byte, err error) { 66 | var b []byte 67 | b, err = os.ReadFile(path) 68 | if err != nil { 69 | return 70 | } 71 | 72 | out, err = RenderContent(b) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /pkg/config/template.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/xx/xxx/pkg/util/util" 21 | ) 22 | 23 | type NumberPair struct { 24 | First int64 25 | Second int64 26 | } 27 | 28 | func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]NumberPair, error) { 29 | firstRangeNumbers, err := util.ParseRangeNumbers(firstRangeStr) 30 | if err != nil { 31 | return nil, err 32 | } 33 | secondRangeNumbers, err := util.ParseRangeNumbers(secondRangeStr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if len(firstRangeNumbers) != len(secondRangeNumbers) { 38 | return nil, fmt.Errorf("first and second range numbers are not in pairs") 39 | } 40 | pairs := make([]NumberPair, 0, len(firstRangeNumbers)) 41 | for i := 0; i < len(firstRangeNumbers); i++ { 42 | pairs = append(pairs, NumberPair{ 43 | First: firstRangeNumbers[i], 44 | Second: secondRangeNumbers[i], 45 | }) 46 | } 47 | return pairs, nil 48 | } 49 | 50 | func parseNumberRange(firstRangeStr string) ([]int64, error) { 51 | return util.ParseRangeNumbers(firstRangeStr) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/config/types/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package types 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | type Wrap struct { 25 | B BandwidthQuantity `json:"b"` 26 | Int int `json:"int"` 27 | } 28 | 29 | func TestBandwidthQuantity(t *testing.T) { 30 | require := require.New(t) 31 | 32 | var w Wrap 33 | err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w) 34 | require.NoError(err) 35 | require.EqualValues(1*KB, w.B.Bytes()) 36 | 37 | buf, err := json.Marshal(&w) 38 | require.NoError(err) 39 | require.Equal(`{"b":"1KB","int":5}`, string(buf)) 40 | } 41 | 42 | func TestPortsRangeSlice2String(t *testing.T) { 43 | require := require.New(t) 44 | 45 | ports := []PortsRange{ 46 | { 47 | Start: 1000, 48 | End: 2000, 49 | }, 50 | { 51 | Single: 3000, 52 | }, 53 | } 54 | str := PortsRangeSlice(ports).String() 55 | require.Equal("1000-2000,3000", str) 56 | } 57 | 58 | func TestNewPortsRangeSliceFromString(t *testing.T) { 59 | require := require.New(t) 60 | 61 | ports, err := NewPortsRangeSliceFromString("1000-2000,3000") 62 | require.NoError(err) 63 | require.Equal([]PortsRange{ 64 | { 65 | Start: 1000, 66 | End: 2000, 67 | }, 68 | { 69 | Single: 3000, 70 | }, 71 | }, ports) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/config/v1/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | type APIMetadata struct { 18 | Version string `json:"version"` 19 | } 20 | -------------------------------------------------------------------------------- /pkg/config/v1/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/samber/lo" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestClientConfigComplete(t *testing.T) { 25 | require := require.New(t) 26 | c := &ClientConfig{} 27 | c.Complete() 28 | 29 | require.EqualValues("token", c.Auth.Method) 30 | require.Equal(true, lo.FromPtr(c.Transport.TCPMux)) 31 | require.Equal(true, lo.FromPtr(c.LoginFailExit)) 32 | require.Equal(true, lo.FromPtr(c.Transport.TLS.Enable)) 33 | require.Equal(true, lo.FromPtr(c.Transport.TLS.DisableCustomTLSFirstByte)) 34 | require.NotEmpty(c.NatHoleSTUNServer) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/config/v1/proxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestUnmarshalTypedProxyConfig(t *testing.T) { 25 | require := require.New(t) 26 | proxyConfigs := struct { 27 | Proxies []TypedProxyConfig `json:"proxies,omitempty"` 28 | }{} 29 | 30 | strs := `{ 31 | "proxies": [ 32 | { 33 | "type": "tcp", 34 | "localPort": 22, 35 | "remotePort": 6000 36 | }, 37 | { 38 | "type": "http", 39 | "localPort": 80, 40 | "customDomains": ["www.example.com"] 41 | } 42 | ] 43 | }` 44 | err := json.Unmarshal([]byte(strs), &proxyConfigs) 45 | require.NoError(err) 46 | 47 | require.IsType(&TCPProxyConfig{}, proxyConfigs.Proxies[0].ProxyConfigurer) 48 | require.IsType(&HTTPProxyConfig{}, proxyConfigs.Proxies[1].ProxyConfigurer) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/config/v1/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/samber/lo" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestServerConfigComplete(t *testing.T) { 25 | require := require.New(t) 26 | c := &ServerConfig{} 27 | c.Complete() 28 | 29 | require.EqualValues("token", c.Auth.Method) 30 | require.Equal(true, lo.FromPtr(c.Transport.TCPMux)) 31 | require.Equal(true, lo.FromPtr(c.DetailedErrorsToClient)) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "fmt" 19 | "slices" 20 | 21 | v1 "github.com/xx/xxx/pkg/config/v1" 22 | ) 23 | 24 | func validateWebServerConfig(c *v1.WebServerConfig) error { 25 | if c.TLS != nil { 26 | if c.TLS.CertFile == "" { 27 | return fmt.Errorf("tls.certFile must be specified when tls is enabled") 28 | } 29 | if c.TLS.KeyFile == "" { 30 | return fmt.Errorf("tls.keyFile must be specified when tls is enabled") 31 | } 32 | } 33 | 34 | return ValidatePort(c.Port, "webServer.port") 35 | } 36 | 37 | // ValidatePort checks that the network port is in range 38 | func ValidatePort(port int, fieldPath string) error { 39 | if 0 <= port && port <= 65535 { 40 | return nil 41 | } 42 | return fmt.Errorf("%s: port number %d must be in the range 0..65535", fieldPath, port) 43 | } 44 | 45 | func validateLogConfig(c *v1.LogConfig) error { 46 | if !slices.Contains(SupportedLogLevels, c.Level) { 47 | return fmt.Errorf("invalid log level, optional values are %v", SupportedLogLevels) 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "errors" 19 | 20 | v1 "github.com/xx/xxx/pkg/config/v1" 21 | ) 22 | 23 | func ValidateClientPluginOptions(c v1.ClientPluginOptions) error { 24 | switch v := c.(type) { 25 | case *v1.HTTP2HTTPSPluginOptions: 26 | return validateHTTP2HTTPSPluginOptions(v) 27 | case *v1.HTTPS2HTTPPluginOptions: 28 | return validateHTTPS2HTTPPluginOptions(v) 29 | case *v1.HTTPS2HTTPSPluginOptions: 30 | return validateHTTPS2HTTPSPluginOptions(v) 31 | case *v1.StaticFilePluginOptions: 32 | return validateStaticFilePluginOptions(v) 33 | case *v1.UnixDomainSocketPluginOptions: 34 | return validateUnixDomainSocketPluginOptions(v) 35 | } 36 | return nil 37 | } 38 | 39 | func validateHTTP2HTTPSPluginOptions(c *v1.HTTP2HTTPSPluginOptions) error { 40 | if c.LocalAddr == "" { 41 | return errors.New("localAddr is required") 42 | } 43 | return nil 44 | } 45 | 46 | func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error { 47 | if c.LocalAddr == "" { 48 | return errors.New("localAddr is required") 49 | } 50 | return nil 51 | } 52 | 53 | func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) error { 54 | if c.LocalAddr == "" { 55 | return errors.New("localAddr is required") 56 | } 57 | return nil 58 | } 59 | 60 | func validateStaticFilePluginOptions(c *v1.StaticFilePluginOptions) error { 61 | if c.LocalPath == "" { 62 | return errors.New("localPath is required") 63 | } 64 | return nil 65 | } 66 | 67 | func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions) error { 68 | if c.UnixPath == "" { 69 | return errors.New("unixPath is required") 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "fmt" 19 | "slices" 20 | 21 | "github.com/samber/lo" 22 | 23 | v1 "github.com/xx/xxx/pkg/config/v1" 24 | ) 25 | 26 | func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) { 27 | var ( 28 | warnings Warning 29 | errs error 30 | ) 31 | if !slices.Contains(SupportedAuthMethods, c.Auth.Method) { 32 | errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods)) 33 | } 34 | if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) { 35 | errs = AppendError(errs, fmt.Errorf("invalid auth additional scopes, optional values are %v", SupportedAuthAdditionalScopes)) 36 | } 37 | 38 | if err := validateLogConfig(&c.Log); err != nil { 39 | errs = AppendError(errs, err) 40 | } 41 | 42 | if err := validateWebServerConfig(&c.WebServer); err != nil { 43 | errs = AppendError(errs, err) 44 | } 45 | 46 | errs = AppendError(errs, ValidatePort(c.BindPort, "bindPort")) 47 | errs = AppendError(errs, ValidatePort(c.KCPBindPort, "kcpBindPort")) 48 | errs = AppendError(errs, ValidatePort(c.QUICBindPort, "quicBindPort")) 49 | errs = AppendError(errs, ValidatePort(c.VhostHTTPPort, "vhostHTTPPort")) 50 | errs = AppendError(errs, ValidatePort(c.VhostHTTPSPort, "vhostHTTPSPort")) 51 | errs = AppendError(errs, ValidatePort(c.TCPMuxHTTPConnectPort, "tcpMuxHTTPConnectPort")) 52 | 53 | for _, p := range c.HTTPPlugins { 54 | if !lo.Every(SupportedHTTPPluginOps, p.Ops) { 55 | errs = AppendError(errs, fmt.Errorf("invalid http plugin ops, optional values are %v", SupportedHTTPPluginOps)) 56 | } 57 | } 58 | return warnings, errs 59 | } 60 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "errors" 19 | 20 | v1 "github.com/xx/xxx/pkg/config/v1" 21 | splugin "github.com/xx/xxx/pkg/plugin/server" 22 | ) 23 | 24 | var ( 25 | SupportedTransportProtocols = []string{ 26 | "tcp", 27 | "kcp", 28 | "quic", 29 | "websocket", 30 | "wss", 31 | } 32 | 33 | SupportedAuthMethods = []v1.AuthMethod{ 34 | "token", 35 | "oidc", 36 | } 37 | 38 | SupportedAuthAdditionalScopes = []v1.AuthScope{ 39 | "HeartBeats", 40 | "NewWorkConns", 41 | } 42 | 43 | SupportedLogLevels = []string{ 44 | "trace", 45 | "debug", 46 | "info", 47 | "warn", 48 | "error", 49 | } 50 | 51 | SupportedHTTPPluginOps = []string{ 52 | splugin.OpLogin, 53 | splugin.OpNewProxy, 54 | splugin.OpCloseProxy, 55 | splugin.OpPing, 56 | splugin.OpNewWorkConn, 57 | splugin.OpNewUserConn, 58 | } 59 | ) 60 | 61 | type Warning error 62 | 63 | func AppendError(err error, errs ...error) error { 64 | if len(errs) == 0 { 65 | return err 66 | } 67 | return errors.Join(append([]error{err}, errs...)...) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/visitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "slices" 21 | 22 | v1 "github.com/xx/xxx/pkg/config/v1" 23 | ) 24 | 25 | func ValidateVisitorConfigurer(c v1.VisitorConfigurer) error { 26 | base := c.GetBaseConfig() 27 | if err := validateVisitorBaseConfig(base); err != nil { 28 | return err 29 | } 30 | 31 | switch v := c.(type) { 32 | case *v1.STCPVisitorConfig: 33 | case *v1.SUDPVisitorConfig: 34 | case *v1.XTCPVisitorConfig: 35 | return validateXTCPVisitorConfig(v) 36 | default: 37 | return errors.New("unknown visitor config type") 38 | } 39 | return nil 40 | } 41 | 42 | func validateVisitorBaseConfig(c *v1.VisitorBaseConfig) error { 43 | if c.Name == "" { 44 | return errors.New("name is required") 45 | } 46 | 47 | if c.ServerName == "" { 48 | return errors.New("server name is required") 49 | } 50 | 51 | if c.BindPort == 0 { 52 | return errors.New("bind port is required") 53 | } 54 | return nil 55 | } 56 | 57 | func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error { 58 | if !slices.Contains([]string{"kcp", "quic"}, c.Protocol) { 59 | return fmt.Errorf("protocol should be kcp or quic") 60 | } 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | var ( 22 | ErrMsgType = errors.New("message type error") 23 | ErrCtlClosed = errors.New("control is closed") 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/metrics/aggregate/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package aggregate 16 | 17 | import ( 18 | "github.com/xx/xxx/pkg/metrics/mem" 19 | "github.com/xx/xxx/pkg/metrics/prometheus" 20 | "github.com/xx/xxx/server/metrics" 21 | ) 22 | 23 | // EnableMem start to mark metrics to memory monitor system. 24 | func EnableMem() { 25 | sm.Add(mem.ServerMetrics) 26 | } 27 | 28 | // EnablePrometheus start to mark metrics to prometheus. 29 | func EnablePrometheus() { 30 | sm.Add(prometheus.ServerMetrics) 31 | } 32 | 33 | var sm = &serverMetrics{} 34 | 35 | func init() { 36 | metrics.Register(sm) 37 | } 38 | 39 | type serverMetrics struct { 40 | ms []metrics.ServerMetrics 41 | } 42 | 43 | func (m *serverMetrics) Add(sm metrics.ServerMetrics) { 44 | m.ms = append(m.ms, sm) 45 | } 46 | 47 | func (m *serverMetrics) NewClient() { 48 | for _, v := range m.ms { 49 | v.NewClient() 50 | } 51 | } 52 | 53 | func (m *serverMetrics) CloseClient() { 54 | for _, v := range m.ms { 55 | v.CloseClient() 56 | } 57 | } 58 | 59 | func (m *serverMetrics) NewProxy(name string, proxyType string) { 60 | for _, v := range m.ms { 61 | v.NewProxy(name, proxyType) 62 | } 63 | } 64 | 65 | func (m *serverMetrics) CloseProxy(name string, proxyType string) { 66 | for _, v := range m.ms { 67 | v.CloseProxy(name, proxyType) 68 | } 69 | } 70 | 71 | func (m *serverMetrics) OpenConnection(name string, proxyType string) { 72 | for _, v := range m.ms { 73 | v.OpenConnection(name, proxyType) 74 | } 75 | } 76 | 77 | func (m *serverMetrics) CloseConnection(name string, proxyType string) { 78 | for _, v := range m.ms { 79 | v.CloseConnection(name, proxyType) 80 | } 81 | } 82 | 83 | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) { 84 | for _, v := range m.ms { 85 | v.AddTrafficIn(name, proxyType, trafficBytes) 86 | } 87 | } 88 | 89 | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) { 90 | for _, v := range m.ms { 91 | v.AddTrafficOut(name, proxyType, trafficBytes) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pkg/metrics/mem/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mem 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/xx/xxx/pkg/util/metric" 21 | ) 22 | 23 | const ( 24 | ReserveDays = 7 25 | ) 26 | 27 | type ServerStats struct { 28 | TotalTrafficIn int64 29 | TotalTrafficOut int64 30 | CurConns int64 31 | ClientCounts int64 32 | ProxyTypeCounts map[string]int64 33 | } 34 | 35 | type ProxyStats struct { 36 | Name string 37 | Type string 38 | TodayTrafficIn int64 39 | TodayTrafficOut int64 40 | LastStartTime string 41 | LastCloseTime string 42 | CurConns int64 43 | } 44 | 45 | type ProxyTrafficInfo struct { 46 | Name string 47 | TrafficIn []int64 48 | TrafficOut []int64 49 | } 50 | 51 | type ProxyStatistics struct { 52 | Name string 53 | ProxyType string 54 | TrafficIn metric.DateCounter 55 | TrafficOut metric.DateCounter 56 | CurConns metric.Counter 57 | LastStartTime time.Time 58 | LastCloseTime time.Time 59 | } 60 | 61 | type ServerStatistics struct { 62 | TotalTrafficIn metric.DateCounter 63 | TotalTrafficOut metric.DateCounter 64 | CurConns metric.Counter 65 | 66 | // counter for clients 67 | ClientCounts metric.Counter 68 | 69 | // counter for proxy types 70 | ProxyTypeCounts map[string]metric.Counter 71 | 72 | // statistics for different proxies 73 | // key is proxy name 74 | ProxyStatistics map[string]*ProxyStatistics 75 | } 76 | 77 | type Collector interface { 78 | GetServer() *ServerStats 79 | GetProxiesByType(proxyType string) []*ProxyStats 80 | GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats 81 | GetProxyTraffic(name string) *ProxyTrafficInfo 82 | ClearOfflineProxies() (int, int) 83 | } 84 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metrics 16 | 17 | import ( 18 | "github.com/xx/xxx/pkg/metrics/aggregate" 19 | ) 20 | 21 | var ( 22 | EnableMem = aggregate.EnableMem 23 | EnablePrometheus = aggregate.EnablePrometheus 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/metrics/prometheus/server.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | 6 | "github.com/xx/xxx/server/metrics" 7 | ) 8 | 9 | const ( 10 | namespace = "frp" 11 | serverSubsystem = "server" 12 | ) 13 | 14 | var ServerMetrics metrics.ServerMetrics = newServerMetrics() 15 | 16 | type serverMetrics struct { 17 | clientCount prometheus.Gauge 18 | proxyCount *prometheus.GaugeVec 19 | connectionCount *prometheus.GaugeVec 20 | trafficIn *prometheus.CounterVec 21 | trafficOut *prometheus.CounterVec 22 | } 23 | 24 | func (m *serverMetrics) NewClient() { 25 | m.clientCount.Inc() 26 | } 27 | 28 | func (m *serverMetrics) CloseClient() { 29 | m.clientCount.Dec() 30 | } 31 | 32 | func (m *serverMetrics) NewProxy(_ string, proxyType string) { 33 | m.proxyCount.WithLabelValues(proxyType).Inc() 34 | } 35 | 36 | func (m *serverMetrics) CloseProxy(_ string, proxyType string) { 37 | m.proxyCount.WithLabelValues(proxyType).Dec() 38 | } 39 | 40 | func (m *serverMetrics) OpenConnection(name string, proxyType string) { 41 | m.connectionCount.WithLabelValues(name, proxyType).Inc() 42 | } 43 | 44 | func (m *serverMetrics) CloseConnection(name string, proxyType string) { 45 | m.connectionCount.WithLabelValues(name, proxyType).Dec() 46 | } 47 | 48 | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) { 49 | m.trafficIn.WithLabelValues(name, proxyType).Add(float64(trafficBytes)) 50 | } 51 | 52 | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) { 53 | m.trafficOut.WithLabelValues(name, proxyType).Add(float64(trafficBytes)) 54 | } 55 | 56 | func newServerMetrics() *serverMetrics { 57 | m := &serverMetrics{ 58 | clientCount: prometheus.NewGauge(prometheus.GaugeOpts{ 59 | Namespace: namespace, 60 | Subsystem: serverSubsystem, 61 | Name: "client_counts", 62 | Help: "The current client counts of frps", 63 | }), 64 | proxyCount: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 65 | Namespace: namespace, 66 | Subsystem: serverSubsystem, 67 | Name: "proxy_counts", 68 | Help: "The current proxy counts", 69 | }, []string{"type"}), 70 | connectionCount: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 71 | Namespace: namespace, 72 | Subsystem: serverSubsystem, 73 | Name: "connection_counts", 74 | Help: "The current connection counts", 75 | }, []string{"name", "type"}), 76 | trafficIn: prometheus.NewCounterVec(prometheus.CounterOpts{ 77 | Namespace: namespace, 78 | Subsystem: serverSubsystem, 79 | Name: "traffic_in", 80 | Help: "The total in traffic", 81 | }, []string{"name", "type"}), 82 | trafficOut: prometheus.NewCounterVec(prometheus.CounterOpts{ 83 | Namespace: namespace, 84 | Subsystem: serverSubsystem, 85 | Name: "traffic_out", 86 | Help: "The total out traffic", 87 | }, []string{"name", "type"}), 88 | } 89 | prometheus.MustRegister(m.clientCount) 90 | prometheus.MustRegister(m.proxyCount) 91 | prometheus.MustRegister(m.connectionCount) 92 | prometheus.MustRegister(m.trafficIn) 93 | prometheus.MustRegister(m.trafficOut) 94 | return m 95 | } 96 | -------------------------------------------------------------------------------- /pkg/msg/ctl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package msg 16 | 17 | import ( 18 | "io" 19 | 20 | jsonMsg "github.com/fatedier/golib/msg/json" 21 | ) 22 | 23 | type Message = jsonMsg.Message 24 | 25 | var msgCtl *jsonMsg.MsgCtl 26 | 27 | func init() { 28 | msgCtl = jsonMsg.NewMsgCtl() 29 | for typeByte, msg := range msgTypeMap { 30 | msgCtl.RegisterMsg(typeByte, msg) 31 | } 32 | } 33 | 34 | func ReadMsg(c io.Reader) (msg Message, err error) { 35 | return msgCtl.ReadMsg(c) 36 | } 37 | 38 | func ReadMsgInto(c io.Reader, msg Message) (err error) { 39 | return msgCtl.ReadMsgInto(c, msg) 40 | } 41 | 42 | func WriteMsg(c io.Writer, msg interface{}) (err error) { 43 | return msgCtl.WriteMsg(c, msg) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/msg/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package msg 16 | 17 | import ( 18 | "io" 19 | "reflect" 20 | ) 21 | 22 | func AsyncHandler(f func(Message)) func(Message) { 23 | return func(m Message) { 24 | go f(m) 25 | } 26 | } 27 | 28 | // Dispatcher is used to send messages to net.Conn or register handlers for messages read from net.Conn. 29 | type Dispatcher struct { 30 | rw io.ReadWriter 31 | 32 | sendCh chan Message 33 | doneCh chan struct{} 34 | msgHandlers map[reflect.Type]func(Message) 35 | defaultHandler func(Message) 36 | } 37 | 38 | func NewDispatcher(rw io.ReadWriter) *Dispatcher { 39 | return &Dispatcher{ 40 | rw: rw, 41 | sendCh: make(chan Message, 100), 42 | doneCh: make(chan struct{}), 43 | msgHandlers: make(map[reflect.Type]func(Message)), 44 | } 45 | } 46 | 47 | // Run will block until io.EOF or some error occurs. 48 | func (d *Dispatcher) Run() { 49 | go d.sendLoop() 50 | go d.readLoop() 51 | } 52 | 53 | func (d *Dispatcher) sendLoop() { 54 | for { 55 | select { 56 | case <-d.doneCh: 57 | return 58 | case m := <-d.sendCh: 59 | _ = WriteMsg(d.rw, m) 60 | } 61 | } 62 | } 63 | 64 | func (d *Dispatcher) readLoop() { 65 | for { 66 | m, err := ReadMsg(d.rw) 67 | if err != nil { 68 | close(d.doneCh) 69 | return 70 | } 71 | 72 | if handler, ok := d.msgHandlers[reflect.TypeOf(m)]; ok { 73 | handler(m) 74 | } else if d.defaultHandler != nil { 75 | d.defaultHandler(m) 76 | } 77 | } 78 | } 79 | 80 | func (d *Dispatcher) Send(m Message) error { 81 | select { 82 | case <-d.doneCh: 83 | return io.EOF 84 | case d.sendCh <- m: 85 | return nil 86 | } 87 | } 88 | 89 | func (d *Dispatcher) SendChannel() chan Message { 90 | return d.sendCh 91 | } 92 | 93 | func (d *Dispatcher) RegisterHandler(msg Message, handler func(Message)) { 94 | d.msgHandlers[reflect.TypeOf(msg)] = handler 95 | } 96 | 97 | func (d *Dispatcher) RegisterDefaultHandler(handler func(Message)) { 98 | d.defaultHandler = handler 99 | } 100 | 101 | func (d *Dispatcher) Done() chan struct{} { 102 | return d.doneCh 103 | } 104 | -------------------------------------------------------------------------------- /pkg/nathole/classify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nathole 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "slices" 21 | "strconv" 22 | ) 23 | 24 | const ( 25 | EasyNAT = "EasyNAT" 26 | HardNAT = "HardNAT" 27 | 28 | BehaviorNoChange = "BehaviorNoChange" 29 | BehaviorIPChanged = "BehaviorIPChanged" 30 | BehaviorPortChanged = "BehaviorPortChanged" 31 | BehaviorBothChanged = "BehaviorBothChanged" 32 | ) 33 | 34 | type NatFeature struct { 35 | NatType string 36 | Behavior string 37 | PortsDifference int 38 | RegularPortsChange bool 39 | PublicNetwork bool 40 | } 41 | 42 | func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeature, error) { 43 | if len(addresses) <= 1 { 44 | return nil, fmt.Errorf("not enough addresses") 45 | } 46 | natFeature := &NatFeature{} 47 | ipChanged := false 48 | portChanged := false 49 | 50 | var baseIP, basePort string 51 | var portMax, portMin int 52 | for _, addr := range addresses { 53 | ip, port, err := net.SplitHostPort(addr) 54 | if err != nil { 55 | return nil, err 56 | } 57 | portNum, err := strconv.Atoi(port) 58 | if err != nil { 59 | return nil, err 60 | } 61 | if slices.Contains(localIPs, ip) { 62 | natFeature.PublicNetwork = true 63 | } 64 | 65 | if baseIP == "" { 66 | baseIP = ip 67 | basePort = port 68 | portMax = portNum 69 | portMin = portNum 70 | continue 71 | } 72 | 73 | if portNum > portMax { 74 | portMax = portNum 75 | } 76 | if portNum < portMin { 77 | portMin = portNum 78 | } 79 | if baseIP != ip { 80 | ipChanged = true 81 | } 82 | if basePort != port { 83 | portChanged = true 84 | } 85 | } 86 | 87 | switch { 88 | case ipChanged && portChanged: 89 | natFeature.NatType = HardNAT 90 | natFeature.Behavior = BehaviorBothChanged 91 | case ipChanged: 92 | natFeature.NatType = HardNAT 93 | natFeature.Behavior = BehaviorIPChanged 94 | case portChanged: 95 | natFeature.NatType = HardNAT 96 | natFeature.Behavior = BehaviorPortChanged 97 | default: 98 | natFeature.NatType = EasyNAT 99 | natFeature.Behavior = BehaviorNoChange 100 | } 101 | if natFeature.Behavior == BehaviorPortChanged { 102 | natFeature.PortsDifference = portMax - portMin 103 | if natFeature.PortsDifference <= 5 && natFeature.PortsDifference >= 1 { 104 | natFeature.RegularPortsChange = true 105 | } 106 | } 107 | return natFeature, nil 108 | } 109 | 110 | func ClassifyFeatureCount(features []*NatFeature) (int, int, int) { 111 | easyCount := 0 112 | hardCount := 0 113 | // for HardNAT 114 | portsChangedRegularCount := 0 115 | for _, feature := range features { 116 | if feature.NatType == EasyNAT { 117 | easyCount++ 118 | continue 119 | } 120 | 121 | hardCount++ 122 | if feature.RegularPortsChange { 123 | portsChangedRegularCount++ 124 | } 125 | } 126 | return easyCount, hardCount, portsChangedRegularCount 127 | } 128 | -------------------------------------------------------------------------------- /pkg/nathole/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nathole 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "net" 21 | "strconv" 22 | 23 | "github.com/fatedier/golib/crypto" 24 | "github.com/pion/stun/v2" 25 | 26 | "github.com/xx/xxx/pkg/msg" 27 | ) 28 | 29 | func EncodeMessage(m msg.Message, key []byte) ([]byte, error) { 30 | buffer := bytes.NewBuffer(nil) 31 | if err := msg.WriteMsg(buffer, m); err != nil { 32 | return nil, err 33 | } 34 | 35 | buf, err := crypto.Encode(buffer.Bytes(), key) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return buf, nil 40 | } 41 | 42 | func DecodeMessageInto(data, key []byte, m msg.Message) error { 43 | buf, err := crypto.Decode(data, key) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return msg.ReadMsgInto(bytes.NewReader(buf), m) 49 | } 50 | 51 | type ChangedAddress struct { 52 | IP net.IP 53 | Port int 54 | } 55 | 56 | func (s *ChangedAddress) GetFrom(m *stun.Message) error { 57 | a := (*stun.MappedAddress)(s) 58 | return a.GetFromAs(m, stun.AttrChangedAddress) 59 | } 60 | 61 | func (s *ChangedAddress) String() string { 62 | return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port)) 63 | } 64 | 65 | func ListAllLocalIPs() ([]net.IP, error) { 66 | addrs, err := net.InterfaceAddrs() 67 | if err != nil { 68 | return nil, err 69 | } 70 | ips := make([]net.IP, 0, len(addrs)) 71 | for _, addr := range addrs { 72 | ip, _, err := net.ParseCIDR(addr.String()) 73 | if err != nil { 74 | continue 75 | } 76 | ips = append(ips, ip) 77 | } 78 | return ips, nil 79 | } 80 | 81 | func ListLocalIPsForNatHole(max int) ([]string, error) { 82 | if max <= 0 { 83 | return nil, fmt.Errorf("max must be greater than 0") 84 | } 85 | 86 | ips, err := ListAllLocalIPs() 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | filtered := make([]string, 0, max) 92 | for _, ip := range ips { 93 | if len(filtered) >= max { 94 | break 95 | } 96 | 97 | // ignore ipv6 address 98 | if ip.To4() == nil { 99 | continue 100 | } 101 | // ignore localhost IP 102 | if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { 103 | continue 104 | } 105 | 106 | filtered = append(filtered, ip.String()) 107 | } 108 | return filtered, nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/plugin/client/http2https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "crypto/tls" 21 | "io" 22 | stdlog "log" 23 | "net" 24 | "net/http" 25 | "net/http/httputil" 26 | 27 | "github.com/fatedier/golib/pool" 28 | 29 | v1 "github.com/xx/xxx/pkg/config/v1" 30 | "github.com/xx/xxx/pkg/util/log" 31 | netpkg "github.com/xx/xxx/pkg/util/net" 32 | ) 33 | 34 | func init() { 35 | Register(v1.PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin) 36 | } 37 | 38 | type HTTP2HTTPSPlugin struct { 39 | opts *v1.HTTP2HTTPSPluginOptions 40 | 41 | l *Listener 42 | s *http.Server 43 | } 44 | 45 | func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) { 46 | opts := options.(*v1.HTTP2HTTPSPluginOptions) 47 | 48 | listener := NewProxyListener() 49 | 50 | p := &HTTP2HTTPSPlugin{ 51 | opts: opts, 52 | l: listener, 53 | } 54 | 55 | tr := &http.Transport{ 56 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 57 | } 58 | 59 | rp := &httputil.ReverseProxy{ 60 | Rewrite: func(r *httputil.ProxyRequest) { 61 | r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] 62 | r.Out.Header["X-Forwarded-Host"] = r.In.Header["X-Forwarded-Host"] 63 | r.Out.Header["X-Forwarded-Proto"] = r.In.Header["X-Forwarded-Proto"] 64 | req := r.Out 65 | req.URL.Scheme = "https" 66 | req.URL.Host = p.opts.LocalAddr 67 | if p.opts.HostHeaderRewrite != "" { 68 | req.Host = p.opts.HostHeaderRewrite 69 | } 70 | for k, v := range p.opts.RequestHeaders.Set { 71 | req.Header.Set(k, v) 72 | } 73 | }, 74 | Transport: tr, 75 | BufferPool: pool.NewBuffer(32 * 1024), 76 | ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), 77 | } 78 | 79 | p.s = &http.Server{ 80 | Handler: rp, 81 | ReadHeaderTimeout: 0, 82 | } 83 | 84 | go func() { 85 | _ = p.s.Serve(listener) 86 | }() 87 | 88 | return p, nil 89 | } 90 | 91 | func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 92 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 93 | _ = p.l.PutConn(wrapConn) 94 | } 95 | 96 | func (p *HTTP2HTTPSPlugin) Name() string { 97 | return v1.PluginHTTP2HTTPS 98 | } 99 | 100 | func (p *HTTP2HTTPSPlugin) Close() error { 101 | return p.s.Close() 102 | } 103 | -------------------------------------------------------------------------------- /pkg/plugin/client/https2http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "crypto/tls" 21 | "fmt" 22 | "io" 23 | stdlog "log" 24 | "net" 25 | "net/http" 26 | "net/http/httputil" 27 | "time" 28 | 29 | "github.com/fatedier/golib/pool" 30 | 31 | v1 "github.com/xx/xxx/pkg/config/v1" 32 | "github.com/xx/xxx/pkg/transport" 33 | "github.com/xx/xxx/pkg/util/log" 34 | netpkg "github.com/xx/xxx/pkg/util/net" 35 | ) 36 | 37 | func init() { 38 | Register(v1.PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin) 39 | } 40 | 41 | type HTTPS2HTTPPlugin struct { 42 | opts *v1.HTTPS2HTTPPluginOptions 43 | 44 | l *Listener 45 | s *http.Server 46 | } 47 | 48 | func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) { 49 | opts := options.(*v1.HTTPS2HTTPPluginOptions) 50 | listener := NewProxyListener() 51 | 52 | p := &HTTPS2HTTPPlugin{ 53 | opts: opts, 54 | l: listener, 55 | } 56 | 57 | rp := &httputil.ReverseProxy{ 58 | Rewrite: func(r *httputil.ProxyRequest) { 59 | r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] 60 | r.SetXForwarded() 61 | req := r.Out 62 | req.URL.Scheme = "http" 63 | req.URL.Host = p.opts.LocalAddr 64 | if p.opts.HostHeaderRewrite != "" { 65 | req.Host = p.opts.HostHeaderRewrite 66 | } 67 | for k, v := range p.opts.RequestHeaders.Set { 68 | req.Header.Set(k, v) 69 | } 70 | }, 71 | BufferPool: pool.NewBuffer(32 * 1024), 72 | ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), 73 | } 74 | 75 | var ( 76 | tlsConfig *tls.Config 77 | err error 78 | ) 79 | if opts.CrtPath != "" || opts.KeyPath != "" { 80 | tlsConfig, err = p.genTLSConfig() 81 | } else { 82 | tlsConfig, err = transport.NewServerTLSConfig("", "", "") 83 | tlsConfig.InsecureSkipVerify = true 84 | } 85 | if err != nil { 86 | return nil, fmt.Errorf("gen TLS config error: %v", err) 87 | } 88 | 89 | p.s = &http.Server{ 90 | Handler: rp, 91 | ReadHeaderTimeout: 60 * time.Second, 92 | TLSConfig: tlsConfig, 93 | } 94 | 95 | go func() { 96 | _ = p.s.ServeTLS(listener, "", "") 97 | }() 98 | return p, nil 99 | } 100 | 101 | func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) { 102 | cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | config := &tls.Config{Certificates: []tls.Certificate{cert}} 108 | return config, nil 109 | } 110 | 111 | func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) { 112 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 113 | if extra.SrcAddr != nil { 114 | wrapConn.SetRemoteAddr(extra.SrcAddr) 115 | } 116 | _ = p.l.PutConn(wrapConn) 117 | } 118 | 119 | func (p *HTTPS2HTTPPlugin) Name() string { 120 | return v1.PluginHTTPS2HTTP 121 | } 122 | 123 | func (p *HTTPS2HTTPPlugin) Close() error { 124 | return p.s.Close() 125 | } 126 | -------------------------------------------------------------------------------- /pkg/plugin/client/https2https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "crypto/tls" 21 | "fmt" 22 | "io" 23 | stdlog "log" 24 | "net" 25 | "net/http" 26 | "net/http/httputil" 27 | "time" 28 | 29 | "github.com/fatedier/golib/pool" 30 | 31 | v1 "github.com/xx/xxx/pkg/config/v1" 32 | "github.com/xx/xxx/pkg/transport" 33 | "github.com/xx/xxx/pkg/util/log" 34 | netpkg "github.com/xx/xxx/pkg/util/net" 35 | ) 36 | 37 | func init() { 38 | Register(v1.PluginHTTPS2HTTPS, NewHTTPS2HTTPSPlugin) 39 | } 40 | 41 | type HTTPS2HTTPSPlugin struct { 42 | opts *v1.HTTPS2HTTPSPluginOptions 43 | 44 | l *Listener 45 | s *http.Server 46 | } 47 | 48 | func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) { 49 | opts := options.(*v1.HTTPS2HTTPSPluginOptions) 50 | 51 | listener := NewProxyListener() 52 | 53 | p := &HTTPS2HTTPSPlugin{ 54 | opts: opts, 55 | l: listener, 56 | } 57 | 58 | tr := &http.Transport{ 59 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 60 | } 61 | 62 | rp := &httputil.ReverseProxy{ 63 | Rewrite: func(r *httputil.ProxyRequest) { 64 | r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] 65 | r.SetXForwarded() 66 | req := r.Out 67 | req.URL.Scheme = "https" 68 | req.URL.Host = p.opts.LocalAddr 69 | if p.opts.HostHeaderRewrite != "" { 70 | req.Host = p.opts.HostHeaderRewrite 71 | } 72 | for k, v := range p.opts.RequestHeaders.Set { 73 | req.Header.Set(k, v) 74 | } 75 | }, 76 | Transport: tr, 77 | BufferPool: pool.NewBuffer(32 * 1024), 78 | ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), 79 | } 80 | 81 | var ( 82 | tlsConfig *tls.Config 83 | err error 84 | ) 85 | if opts.CrtPath != "" || opts.KeyPath != "" { 86 | tlsConfig, err = p.genTLSConfig() 87 | } else { 88 | tlsConfig, err = transport.NewServerTLSConfig("", "", "") 89 | tlsConfig.InsecureSkipVerify = true 90 | } 91 | if err != nil { 92 | return nil, fmt.Errorf("gen TLS config error: %v", err) 93 | } 94 | 95 | p.s = &http.Server{ 96 | Handler: rp, 97 | ReadHeaderTimeout: 60 * time.Second, 98 | TLSConfig: tlsConfig, 99 | } 100 | 101 | go func() { 102 | _ = p.s.ServeTLS(listener, "", "") 103 | }() 104 | return p, nil 105 | } 106 | 107 | func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) { 108 | cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | config := &tls.Config{Certificates: []tls.Certificate{cert}} 114 | return config, nil 115 | } 116 | 117 | func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) { 118 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 119 | if extra.SrcAddr != nil { 120 | wrapConn.SetRemoteAddr(extra.SrcAddr) 121 | } 122 | _ = p.l.PutConn(wrapConn) 123 | } 124 | 125 | func (p *HTTPS2HTTPSPlugin) Name() string { 126 | return v1.PluginHTTPS2HTTPS 127 | } 128 | 129 | func (p *HTTPS2HTTPSPlugin) Close() error { 130 | return p.s.Close() 131 | } 132 | -------------------------------------------------------------------------------- /pkg/plugin/client/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "net" 21 | "sync" 22 | 23 | "github.com/fatedier/golib/errors" 24 | pp "github.com/pires/go-proxyproto" 25 | 26 | v1 "github.com/xx/xxx/pkg/config/v1" 27 | ) 28 | 29 | // Creators is used for create plugins to handle connections. 30 | var creators = make(map[string]CreatorFn) 31 | 32 | // params has prefix "plugin_" 33 | type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error) 34 | 35 | func Register(name string, fn CreatorFn) { 36 | if _, exist := creators[name]; exist { 37 | panic(fmt.Sprintf("plugin [%s] is already registered", name)) 38 | } 39 | creators[name] = fn 40 | } 41 | 42 | func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) { 43 | if fn, ok := creators[name]; ok { 44 | p, err = fn(options) 45 | } else { 46 | err = fmt.Errorf("plugin [%s] is not registered", name) 47 | } 48 | return 49 | } 50 | 51 | type ExtraInfo struct { 52 | ProxyProtocolHeader *pp.Header 53 | SrcAddr net.Addr 54 | DstAddr net.Addr 55 | } 56 | 57 | type Plugin interface { 58 | Name() string 59 | 60 | Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) 61 | Close() error 62 | } 63 | 64 | type Listener struct { 65 | conns chan net.Conn 66 | closed bool 67 | mu sync.Mutex 68 | } 69 | 70 | func NewProxyListener() *Listener { 71 | return &Listener{ 72 | conns: make(chan net.Conn, 64), 73 | } 74 | } 75 | 76 | func (l *Listener) Accept() (net.Conn, error) { 77 | conn, ok := <-l.conns 78 | if !ok { 79 | return nil, fmt.Errorf("listener closed") 80 | } 81 | return conn, nil 82 | } 83 | 84 | func (l *Listener) PutConn(conn net.Conn) error { 85 | err := errors.PanicToError(func() { 86 | l.conns <- conn 87 | }) 88 | return err 89 | } 90 | 91 | func (l *Listener) Close() error { 92 | l.mu.Lock() 93 | defer l.mu.Unlock() 94 | if !l.closed { 95 | close(l.conns) 96 | l.closed = true 97 | } 98 | return nil 99 | } 100 | 101 | func (l *Listener) Addr() net.Addr { 102 | return (*net.TCPAddr)(nil) 103 | } 104 | -------------------------------------------------------------------------------- /pkg/plugin/client/socks5.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "io" 21 | "log" 22 | "net" 23 | 24 | gosocks5 "github.com/armon/go-socks5" 25 | 26 | v1 "github.com/xx/xxx/pkg/config/v1" 27 | netpkg "github.com/xx/xxx/pkg/util/net" 28 | ) 29 | 30 | func init() { 31 | Register(v1.PluginSocks5, NewSocks5Plugin) 32 | } 33 | 34 | type Socks5Plugin struct { 35 | Server *gosocks5.Server 36 | } 37 | 38 | func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) { 39 | opts := options.(*v1.Socks5PluginOptions) 40 | 41 | cfg := &gosocks5.Config{ 42 | Logger: log.New(io.Discard, "", log.LstdFlags), 43 | } 44 | if opts.Username != "" || opts.Password != "" { 45 | cfg.Credentials = gosocks5.StaticCredentials(map[string]string{opts.Username: opts.Password}) 46 | } 47 | sp := &Socks5Plugin{} 48 | sp.Server, err = gosocks5.New(cfg) 49 | p = sp 50 | return 51 | } 52 | 53 | func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 54 | defer conn.Close() 55 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 56 | _ = sp.Server.ServeConn(wrapConn) 57 | } 58 | 59 | func (sp *Socks5Plugin) Name() string { 60 | return v1.PluginSocks5 61 | } 62 | 63 | func (sp *Socks5Plugin) Close() error { 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/plugin/client/static_file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "io" 21 | "net" 22 | "net/http" 23 | "time" 24 | 25 | "github.com/gorilla/mux" 26 | 27 | v1 "github.com/xx/xxx/pkg/config/v1" 28 | netpkg "github.com/xx/xxx/pkg/util/net" 29 | ) 30 | 31 | func init() { 32 | Register(v1.PluginStaticFile, NewStaticFilePlugin) 33 | } 34 | 35 | type StaticFilePlugin struct { 36 | opts *v1.StaticFilePluginOptions 37 | 38 | l *Listener 39 | s *http.Server 40 | } 41 | 42 | func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) { 43 | opts := options.(*v1.StaticFilePluginOptions) 44 | 45 | listener := NewProxyListener() 46 | 47 | sp := &StaticFilePlugin{ 48 | opts: opts, 49 | 50 | l: listener, 51 | } 52 | var prefix string 53 | if opts.StripPrefix != "" { 54 | prefix = "/" + opts.StripPrefix + "/" 55 | } else { 56 | prefix = "/" 57 | } 58 | 59 | router := mux.NewRouter() 60 | router.Use(netpkg.NewHTTPAuthMiddleware(opts.HTTPUser, opts.HTTPPassword).SetAuthFailDelay(200 * time.Millisecond).Middleware) 61 | router.PathPrefix(prefix).Handler(netpkg.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(opts.LocalPath))))).Methods("GET") 62 | sp.s = &http.Server{ 63 | Handler: router, 64 | ReadHeaderTimeout: 60 * time.Second, 65 | } 66 | go func() { 67 | _ = sp.s.Serve(listener) 68 | }() 69 | return sp, nil 70 | } 71 | 72 | func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 73 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 74 | _ = sp.l.PutConn(wrapConn) 75 | } 76 | 77 | func (sp *StaticFilePlugin) Name() string { 78 | return v1.PluginStaticFile 79 | } 80 | 81 | func (sp *StaticFilePlugin) Close() error { 82 | sp.s.Close() 83 | sp.l.Close() 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/plugin/client/unix_domain_socket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "io" 21 | "net" 22 | 23 | libio "github.com/fatedier/golib/io" 24 | 25 | v1 "github.com/xx/xxx/pkg/config/v1" 26 | ) 27 | 28 | func init() { 29 | Register(v1.PluginUnixDomainSocket, NewUnixDomainSocketPlugin) 30 | } 31 | 32 | type UnixDomainSocketPlugin struct { 33 | UnixAddr *net.UnixAddr 34 | } 35 | 36 | func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) { 37 | opts := options.(*v1.UnixDomainSocketPluginOptions) 38 | 39 | unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath) 40 | if errRet != nil { 41 | err = errRet 42 | return 43 | } 44 | 45 | p = &UnixDomainSocketPlugin{ 46 | UnixAddr: unixAddr, 47 | } 48 | return 49 | } 50 | 51 | func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) { 52 | localConn, err := net.DialUnix("unix", nil, uds.UnixAddr) 53 | if err != nil { 54 | return 55 | } 56 | if extra.ProxyProtocolHeader != nil { 57 | if _, err := extra.ProxyProtocolHeader.WriteTo(localConn); err != nil { 58 | return 59 | } 60 | } 61 | 62 | libio.Join(localConn, conn) 63 | } 64 | 65 | func (uds *UnixDomainSocketPlugin) Name() string { 66 | return v1.PluginUnixDomainSocket 67 | } 68 | 69 | func (uds *UnixDomainSocketPlugin) Close() error { 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/plugin/server/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "crypto/tls" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "net/http" 25 | "net/url" 26 | "reflect" 27 | "strings" 28 | 29 | v1 "github.com/xx/xxx/pkg/config/v1" 30 | ) 31 | 32 | type httpPlugin struct { 33 | options v1.HTTPPluginOptions 34 | 35 | url string 36 | client *http.Client 37 | } 38 | 39 | func NewHTTPPluginOptions(options v1.HTTPPluginOptions) Plugin { 40 | url := fmt.Sprintf("%s%s", options.Addr, options.Path) 41 | 42 | var client *http.Client 43 | if strings.HasPrefix(url, "https://") { 44 | tr := &http.Transport{ 45 | TLSClientConfig: &tls.Config{InsecureSkipVerify: !options.TLSVerify}, 46 | } 47 | client = &http.Client{Transport: tr} 48 | } else { 49 | client = &http.Client{} 50 | } 51 | 52 | if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { 53 | url = "http://" + url 54 | } 55 | return &httpPlugin{ 56 | options: options, 57 | url: url, 58 | client: client, 59 | } 60 | } 61 | 62 | func (p *httpPlugin) Name() string { 63 | return p.options.Name 64 | } 65 | 66 | func (p *httpPlugin) IsSupport(op string) bool { 67 | for _, v := range p.options.Ops { 68 | if v == op { 69 | return true 70 | } 71 | } 72 | return false 73 | } 74 | 75 | func (p *httpPlugin) Handle(ctx context.Context, op string, content interface{}) (*Response, interface{}, error) { 76 | r := &Request{ 77 | Version: APIVersion, 78 | Op: op, 79 | Content: content, 80 | } 81 | var res Response 82 | res.Content = reflect.New(reflect.TypeOf(content)).Interface() 83 | if err := p.do(ctx, r, &res); err != nil { 84 | return nil, nil, err 85 | } 86 | return &res, res.Content, nil 87 | } 88 | 89 | func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error { 90 | buf, err := json.Marshal(r) 91 | if err != nil { 92 | return err 93 | } 94 | v := url.Values{} 95 | v.Set("version", r.Version) 96 | v.Set("op", r.Op) 97 | req, err := http.NewRequest("POST", p.url+"?"+v.Encode(), bytes.NewReader(buf)) 98 | if err != nil { 99 | return err 100 | } 101 | req = req.WithContext(ctx) 102 | req.Header.Set("X-Frp-Reqid", GetReqidFromContext(ctx)) 103 | req.Header.Set("Content-Type", "application/json") 104 | resp, err := p.client.Do(req) 105 | if err != nil { 106 | return err 107 | } 108 | defer resp.Body.Close() 109 | 110 | if resp.StatusCode != http.StatusOK { 111 | return fmt.Errorf("do http request error code: %d", resp.StatusCode) 112 | } 113 | buf, err = io.ReadAll(resp.Body) 114 | if err != nil { 115 | return err 116 | } 117 | return json.Unmarshal(buf, res) 118 | } 119 | -------------------------------------------------------------------------------- /pkg/plugin/server/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | const ( 22 | APIVersion = "0.1.0" 23 | 24 | OpLogin = "Login" 25 | OpNewProxy = "NewProxy" 26 | OpCloseProxy = "CloseProxy" 27 | OpPing = "Ping" 28 | OpNewWorkConn = "NewWorkConn" 29 | OpNewUserConn = "NewUserConn" 30 | ) 31 | 32 | type Plugin interface { 33 | Name() string 34 | IsSupport(op string) bool 35 | Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/plugin/server/tracer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | type key int 22 | 23 | const ( 24 | reqidKey key = 0 25 | ) 26 | 27 | func NewReqidContext(ctx context.Context, reqid string) context.Context { 28 | return context.WithValue(ctx, reqidKey, reqid) 29 | } 30 | 31 | func GetReqidFromContext(ctx context.Context) string { 32 | ret, _ := ctx.Value(reqidKey).(string) 33 | return ret 34 | } 35 | -------------------------------------------------------------------------------- /pkg/plugin/server/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "github.com/xx/xxx/pkg/msg" 19 | ) 20 | 21 | type Request struct { 22 | Version string `json:"version"` 23 | Op string `json:"op"` 24 | Content interface{} `json:"content"` 25 | } 26 | 27 | type Response struct { 28 | Reject bool `json:"reject"` 29 | RejectReason string `json:"reject_reason"` 30 | Unchange bool `json:"unchange"` 31 | Content interface{} `json:"content"` 32 | } 33 | 34 | type LoginContent struct { 35 | msg.Login 36 | 37 | ClientAddress string `json:"client_address,omitempty"` 38 | } 39 | 40 | type UserInfo struct { 41 | User string `json:"user"` 42 | Metas map[string]string `json:"metas"` 43 | RunID string `json:"run_id"` 44 | } 45 | 46 | type NewProxyContent struct { 47 | User UserInfo `json:"user"` 48 | msg.NewProxy 49 | } 50 | 51 | type CloseProxyContent struct { 52 | User UserInfo `json:"user"` 53 | msg.CloseProxy 54 | } 55 | 56 | type PingContent struct { 57 | User UserInfo `json:"user"` 58 | msg.Ping 59 | } 60 | 61 | type NewWorkConnContent struct { 62 | User UserInfo `json:"user"` 63 | msg.NewWorkConn 64 | } 65 | 66 | type NewUserConnContent struct { 67 | User UserInfo `json:"user"` 68 | ProxyName string `json:"proxy_name"` 69 | ProxyType string `json:"proxy_type"` 70 | RemoteAddr string `json:"remote_addr"` 71 | } 72 | -------------------------------------------------------------------------------- /pkg/proto/udp/udp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package udp 16 | 17 | import ( 18 | "encoding/base64" 19 | "net" 20 | "sync" 21 | "time" 22 | 23 | "github.com/fatedier/golib/errors" 24 | "github.com/fatedier/golib/pool" 25 | 26 | "github.com/xx/xxx/pkg/msg" 27 | ) 28 | 29 | func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket { 30 | return &msg.UDPPacket{ 31 | Content: base64.StdEncoding.EncodeToString(buf), 32 | LocalAddr: laddr, 33 | RemoteAddr: raddr, 34 | } 35 | } 36 | 37 | func GetContent(m *msg.UDPPacket) (buf []byte, err error) { 38 | buf, err = base64.StdEncoding.DecodeString(m.Content) 39 | return 40 | } 41 | 42 | func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UDPPacket, sendCh chan<- *msg.UDPPacket, bufSize int) { 43 | // read 44 | go func() { 45 | for udpMsg := range readCh { 46 | buf, err := GetContent(udpMsg) 47 | if err != nil { 48 | continue 49 | } 50 | _, _ = udpConn.WriteToUDP(buf, udpMsg.RemoteAddr) 51 | } 52 | }() 53 | 54 | // write 55 | buf := pool.GetBuf(bufSize) 56 | defer pool.PutBuf(buf) 57 | for { 58 | n, remoteAddr, err := udpConn.ReadFromUDP(buf) 59 | if err != nil { 60 | return 61 | } 62 | // buf[:n] will be encoded to string, so the bytes can be reused 63 | udpMsg := NewUDPPacket(buf[:n], nil, remoteAddr) 64 | 65 | select { 66 | case sendCh <- udpMsg: 67 | default: 68 | } 69 | } 70 | } 71 | 72 | func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<- msg.Message, bufSize int) { 73 | var mu sync.RWMutex 74 | udpConnMap := make(map[string]*net.UDPConn) 75 | 76 | // read from dstAddr and write to sendCh 77 | writerFn := func(raddr *net.UDPAddr, udpConn *net.UDPConn) { 78 | addr := raddr.String() 79 | defer func() { 80 | mu.Lock() 81 | delete(udpConnMap, addr) 82 | mu.Unlock() 83 | udpConn.Close() 84 | }() 85 | 86 | buf := pool.GetBuf(bufSize) 87 | for { 88 | _ = udpConn.SetReadDeadline(time.Now().Add(30 * time.Second)) 89 | n, _, err := udpConn.ReadFromUDP(buf) 90 | if err != nil { 91 | return 92 | } 93 | 94 | udpMsg := NewUDPPacket(buf[:n], nil, raddr) 95 | if err = errors.PanicToError(func() { 96 | select { 97 | case sendCh <- udpMsg: 98 | default: 99 | } 100 | }); err != nil { 101 | return 102 | } 103 | } 104 | } 105 | 106 | // read from readCh 107 | go func() { 108 | for udpMsg := range readCh { 109 | buf, err := GetContent(udpMsg) 110 | if err != nil { 111 | continue 112 | } 113 | mu.Lock() 114 | udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()] 115 | if !ok { 116 | udpConn, err = net.DialUDP("udp", nil, dstAddr) 117 | if err != nil { 118 | mu.Unlock() 119 | continue 120 | } 121 | udpConnMap[udpMsg.RemoteAddr.String()] = udpConn 122 | } 123 | mu.Unlock() 124 | 125 | _, err = udpConn.Write(buf) 126 | if err != nil { 127 | udpConn.Close() 128 | } 129 | 130 | if !ok { 131 | go writerFn(udpMsg.RemoteAddr, udpConn) 132 | } 133 | } 134 | }() 135 | } 136 | -------------------------------------------------------------------------------- /pkg/proto/udp/udp_test.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUdpPacket(t *testing.T) { 10 | assert := assert.New(t) 11 | 12 | buf := []byte("hello world") 13 | udpMsg := NewUDPPacket(buf, nil, nil) 14 | 15 | newBuf, err := GetContent(udpMsg) 16 | assert.NoError(err) 17 | assert.EqualValues(buf, newBuf) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/sdk/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/xx/xxx/client" 14 | httppkg "github.com/xx/xxx/pkg/util/http" 15 | ) 16 | 17 | type Client struct { 18 | address string 19 | authUser string 20 | authPwd string 21 | } 22 | 23 | func New(host string, port int) *Client { 24 | return &Client{ 25 | address: net.JoinHostPort(host, strconv.Itoa(port)), 26 | } 27 | } 28 | 29 | func (c *Client) SetAuth(user, pwd string) { 30 | c.authUser = user 31 | c.authPwd = pwd 32 | } 33 | 34 | func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) { 35 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil) 36 | if err != nil { 37 | return nil, err 38 | } 39 | content, err := c.do(req) 40 | if err != nil { 41 | return nil, err 42 | } 43 | allStatus := make(client.StatusResp) 44 | if err = json.Unmarshal([]byte(content), &allStatus); err != nil { 45 | return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content)) 46 | } 47 | for _, pss := range allStatus { 48 | for _, ps := range pss { 49 | if ps.Name == name { 50 | return &ps, nil 51 | } 52 | } 53 | } 54 | return nil, fmt.Errorf("no proxy status found") 55 | } 56 | 57 | func (c *Client) GetAllProxyStatus() (client.StatusResp, error) { 58 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil) 59 | if err != nil { 60 | return nil, err 61 | } 62 | content, err := c.do(req) 63 | if err != nil { 64 | return nil, err 65 | } 66 | allStatus := make(client.StatusResp) 67 | if err = json.Unmarshal([]byte(content), &allStatus); err != nil { 68 | return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content)) 69 | } 70 | return allStatus, nil 71 | } 72 | 73 | func (c *Client) Reload(strictMode bool) error { 74 | v := url.Values{} 75 | if strictMode { 76 | v.Set("strictConfig", "true") 77 | } 78 | queryStr := "" 79 | if len(v) > 0 { 80 | queryStr = "?" + v.Encode() 81 | } 82 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload"+queryStr, nil) 83 | if err != nil { 84 | return err 85 | } 86 | _, err = c.do(req) 87 | return err 88 | } 89 | 90 | func (c *Client) Stop() error { 91 | req, err := http.NewRequest("POST", "http://"+c.address+"/api/stop", nil) 92 | if err != nil { 93 | return err 94 | } 95 | _, err = c.do(req) 96 | return err 97 | } 98 | 99 | func (c *Client) GetConfig() (string, error) { 100 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil) 101 | if err != nil { 102 | return "", err 103 | } 104 | return c.do(req) 105 | } 106 | 107 | func (c *Client) UpdateConfig(content string) error { 108 | req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content)) 109 | if err != nil { 110 | return err 111 | } 112 | _, err = c.do(req) 113 | return err 114 | } 115 | 116 | func (c *Client) setAuthHeader(req *http.Request) { 117 | if c.authUser != "" || c.authPwd != "" { 118 | req.Header.Set("Authorization", httppkg.BasicAuth(c.authUser, c.authPwd)) 119 | } 120 | } 121 | 122 | func (c *Client) do(req *http.Request) (string, error) { 123 | c.setAuthHeader(req) 124 | 125 | resp, err := http.DefaultClient.Do(req) 126 | if err != nil { 127 | return "", err 128 | } 129 | defer resp.Body.Close() 130 | 131 | if resp.StatusCode != 200 { 132 | return "", fmt.Errorf("api status code [%d]", resp.StatusCode) 133 | } 134 | buf, err := io.ReadAll(resp.Body) 135 | if err != nil { 136 | return "", err 137 | } 138 | return string(buf), nil 139 | } 140 | -------------------------------------------------------------------------------- /pkg/ssh/terminal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ssh 16 | 17 | import ( 18 | "github.com/xx/xxx/client/proxy" 19 | v1 "github.com/xx/xxx/pkg/config/v1" 20 | ) 21 | 22 | func createSuccessInfo(user string, pc v1.ProxyConfigurer, ps *proxy.WorkingStatus) string { 23 | base := pc.GetBaseConfig() 24 | out := "\n" 25 | out += "frp (via SSH) (Ctrl+C to quit)\n\n" 26 | out += "User: " + user + "\n" 27 | out += "ProxyName: " + base.Name + "\n" 28 | out += "Type: " + base.Type + "\n" 29 | out += "RemoteAddress: " + ps.RemoteAddr + "\n" 30 | return out 31 | } 32 | -------------------------------------------------------------------------------- /pkg/transport/message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package transport 16 | 17 | import ( 18 | "context" 19 | "reflect" 20 | "sync" 21 | 22 | "github.com/fatedier/golib/errors" 23 | 24 | "github.com/xx/xxx/pkg/msg" 25 | ) 26 | 27 | type MessageTransporter interface { 28 | Send(msg.Message) error 29 | // Recv(ctx context.Context, laneKey string, msgType string) (Message, error) 30 | // Do will first send msg, then recv msg with the same laneKey and specified msgType. 31 | Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error) 32 | // Dispatch will dispatch message to related channel registered in Do function by its message type and laneKey. 33 | Dispatch(m msg.Message, laneKey string) bool 34 | // Same with Dispatch but with specified message type. 35 | DispatchWithType(m msg.Message, msgType, laneKey string) bool 36 | } 37 | 38 | func NewMessageTransporter(sendCh chan msg.Message) MessageTransporter { 39 | return &transporterImpl{ 40 | sendCh: sendCh, 41 | registry: make(map[string]map[string]chan msg.Message), 42 | } 43 | } 44 | 45 | type transporterImpl struct { 46 | sendCh chan msg.Message 47 | 48 | // First key is message type and second key is lane key. 49 | // Dispatch will dispatch message to related channel by its message type 50 | // and lane key. 51 | registry map[string]map[string]chan msg.Message 52 | mu sync.RWMutex 53 | } 54 | 55 | func (impl *transporterImpl) Send(m msg.Message) error { 56 | return errors.PanicToError(func() { 57 | impl.sendCh <- m 58 | }) 59 | } 60 | 61 | func (impl *transporterImpl) Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error) { 62 | ch := make(chan msg.Message, 1) 63 | defer close(ch) 64 | unregisterFn := impl.registerMsgChan(ch, laneKey, recvMsgType) 65 | defer unregisterFn() 66 | 67 | if err := impl.Send(req); err != nil { 68 | return nil, err 69 | } 70 | 71 | select { 72 | case <-ctx.Done(): 73 | return nil, ctx.Err() 74 | case resp := <-ch: 75 | return resp, nil 76 | } 77 | } 78 | 79 | func (impl *transporterImpl) DispatchWithType(m msg.Message, msgType, laneKey string) bool { 80 | var ch chan msg.Message 81 | impl.mu.RLock() 82 | byLaneKey, ok := impl.registry[msgType] 83 | if ok { 84 | ch = byLaneKey[laneKey] 85 | } 86 | impl.mu.RUnlock() 87 | 88 | if ch == nil { 89 | return false 90 | } 91 | 92 | if err := errors.PanicToError(func() { 93 | ch <- m 94 | }); err != nil { 95 | return false 96 | } 97 | return true 98 | } 99 | 100 | func (impl *transporterImpl) Dispatch(m msg.Message, laneKey string) bool { 101 | msgType := reflect.TypeOf(m).Elem().Name() 102 | return impl.DispatchWithType(m, msgType, laneKey) 103 | } 104 | 105 | func (impl *transporterImpl) registerMsgChan(recvCh chan msg.Message, laneKey string, msgType string) (unregister func()) { 106 | impl.mu.Lock() 107 | byLaneKey, ok := impl.registry[msgType] 108 | if !ok { 109 | byLaneKey = make(map[string]chan msg.Message) 110 | impl.registry[msgType] = byLaneKey 111 | } 112 | byLaneKey[laneKey] = recvCh 113 | impl.mu.Unlock() 114 | 115 | unregister = func() { 116 | impl.mu.Lock() 117 | delete(byLaneKey, laneKey) 118 | impl.mu.Unlock() 119 | } 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /pkg/transport/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package transport 16 | 17 | import ( 18 | "crypto/rand" 19 | "crypto/rsa" 20 | "crypto/tls" 21 | "crypto/x509" 22 | "encoding/pem" 23 | "math/big" 24 | "os" 25 | ) 26 | 27 | func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) { 28 | tlsCert, err := tls.LoadX509KeyPair(certfile, keyfile) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &tlsCert, nil 33 | } 34 | 35 | func newRandomTLSKeyPair() *tls.Certificate { 36 | key, err := rsa.GenerateKey(rand.Reader, 2048) 37 | if err != nil { 38 | panic(err) 39 | } 40 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 41 | certDER, err := x509.CreateCertificate( 42 | rand.Reader, 43 | &template, 44 | &template, 45 | &key.PublicKey, 46 | key) 47 | if err != nil { 48 | panic(err) 49 | } 50 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 51 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 52 | 53 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 54 | if err != nil { 55 | panic(err) 56 | } 57 | return &tlsCert 58 | } 59 | 60 | // Only support one ca file to add 61 | func newCertPool(caPath string) (*x509.CertPool, error) { 62 | pool := x509.NewCertPool() 63 | 64 | caCrt, err := os.ReadFile(caPath) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | pool.AppendCertsFromPEM(caCrt) 70 | 71 | return pool, nil 72 | } 73 | 74 | func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) { 75 | base := &tls.Config{} 76 | 77 | if certPath == "" || keyPath == "" { 78 | // server will generate tls conf by itself 79 | cert := newRandomTLSKeyPair() 80 | base.Certificates = []tls.Certificate{*cert} 81 | } else { 82 | cert, err := newCustomTLSKeyPair(certPath, keyPath) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | base.Certificates = []tls.Certificate{*cert} 88 | } 89 | 90 | if caPath != "" { 91 | pool, err := newCertPool(caPath) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | base.ClientAuth = tls.RequireAndVerifyClientCert 97 | base.ClientCAs = pool 98 | } 99 | 100 | return base, nil 101 | } 102 | 103 | func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Config, error) { 104 | base := &tls.Config{} 105 | 106 | if certPath != "" && keyPath != "" { 107 | cert, err := newCustomTLSKeyPair(certPath, keyPath) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | base.Certificates = []tls.Certificate{*cert} 113 | } 114 | 115 | base.ServerName = serverName 116 | 117 | if caPath != "" { 118 | pool, err := newCertPool(caPath) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | base.RootCAs = pool 124 | base.InsecureSkipVerify = false 125 | } else { 126 | base.InsecureSkipVerify = true 127 | } 128 | 129 | return base, nil 130 | } 131 | 132 | func NewRandomPrivateKey() ([]byte, error) { 133 | key, err := rsa.GenerateKey(rand.Reader, 2048) 134 | if err != nil { 135 | return nil, err 136 | } 137 | keyPEM := pem.EncodeToMemory(&pem.Block{ 138 | Type: "RSA PRIVATE KEY", 139 | Bytes: x509.MarshalPKCS1PrivateKey(key), 140 | }) 141 | return keyPEM, nil 142 | } 143 | -------------------------------------------------------------------------------- /pkg/util/http/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http 16 | 17 | import ( 18 | "encoding/base64" 19 | "net" 20 | "net/http" 21 | "strings" 22 | ) 23 | 24 | func OkResponse() *http.Response { 25 | header := make(http.Header) 26 | 27 | res := &http.Response{ 28 | Status: "OK", 29 | StatusCode: 200, 30 | Proto: "HTTP/1.1", 31 | ProtoMajor: 1, 32 | ProtoMinor: 1, 33 | Header: header, 34 | } 35 | return res 36 | } 37 | 38 | func ProxyUnauthorizedResponse() *http.Response { 39 | header := make(http.Header) 40 | header.Set("Proxy-Authenticate", `Basic realm="Restricted"`) 41 | res := &http.Response{ 42 | Status: "Proxy Authentication Required", 43 | StatusCode: 407, 44 | Proto: "HTTP/1.1", 45 | ProtoMajor: 1, 46 | ProtoMinor: 1, 47 | Header: header, 48 | } 49 | return res 50 | } 51 | 52 | // canonicalHost strips port from host if present and returns the canonicalized 53 | // host name. 54 | func CanonicalHost(host string) (string, error) { 55 | var err error 56 | host = strings.ToLower(host) 57 | if hasPort(host) { 58 | host, _, err = net.SplitHostPort(host) 59 | if err != nil { 60 | return "", err 61 | } 62 | } 63 | // Strip trailing dot from fully qualified domain names. 64 | host = strings.TrimSuffix(host, ".") 65 | return host, nil 66 | } 67 | 68 | // hasPort reports whether host contains a port number. host may be a host 69 | // name, an IPv4 or an IPv6 address. 70 | func hasPort(host string) bool { 71 | colons := strings.Count(host, ":") 72 | if colons == 0 { 73 | return false 74 | } 75 | if colons == 1 { 76 | return true 77 | } 78 | return host[0] == '[' && strings.Contains(host, "]:") 79 | } 80 | 81 | func ParseBasicAuth(auth string) (username, password string, ok bool) { 82 | const prefix = "Basic " 83 | // Case insensitive prefix match. See Issue 22736. 84 | if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { 85 | return 86 | } 87 | c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) 88 | if err != nil { 89 | return 90 | } 91 | cs := string(c) 92 | s := strings.IndexByte(cs, ':') 93 | if s < 0 { 94 | return 95 | } 96 | return cs[:s], cs[s+1:], true 97 | } 98 | 99 | func BasicAuth(username, passwd string) string { 100 | auth := username + ":" + passwd 101 | return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 102 | } 103 | -------------------------------------------------------------------------------- /pkg/util/http/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package http 16 | 17 | import ( 18 | "crypto/tls" 19 | "net" 20 | "net/http" 21 | "net/http/pprof" 22 | "strconv" 23 | "time" 24 | 25 | "github.com/gorilla/mux" 26 | 27 | "github.com/xx/xxx/assets" 28 | v1 "github.com/xx/xxx/pkg/config/v1" 29 | netpkg "github.com/xx/xxx/pkg/util/net" 30 | ) 31 | 32 | var ( 33 | defaultReadTimeout = 60 * time.Second 34 | defaultWriteTimeout = 60 * time.Second 35 | ) 36 | 37 | type Server struct { 38 | addr string 39 | ln net.Listener 40 | tlsCfg *tls.Config 41 | 42 | router *mux.Router 43 | hs *http.Server 44 | 45 | authMiddleware mux.MiddlewareFunc 46 | } 47 | 48 | func NewServer(cfg v1.WebServerConfig) (*Server, error) { 49 | assets.Load(cfg.AssetsDir) 50 | 51 | addr := net.JoinHostPort(cfg.Addr, strconv.Itoa(cfg.Port)) 52 | if addr == ":" { 53 | addr = ":http" 54 | } 55 | 56 | ln, err := net.Listen("tcp", addr) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | router := mux.NewRouter() 62 | hs := &http.Server{ 63 | Addr: addr, 64 | Handler: router, 65 | ReadTimeout: defaultReadTimeout, 66 | WriteTimeout: defaultWriteTimeout, 67 | } 68 | s := &Server{ 69 | addr: addr, 70 | ln: ln, 71 | hs: hs, 72 | router: router, 73 | } 74 | if cfg.PprofEnable { 75 | s.registerPprofHandlers() 76 | } 77 | if cfg.TLS != nil { 78 | cert, err := tls.LoadX509KeyPair(cfg.TLS.CertFile, cfg.TLS.KeyFile) 79 | if err != nil { 80 | return nil, err 81 | } 82 | s.tlsCfg = &tls.Config{ 83 | Certificates: []tls.Certificate{cert}, 84 | } 85 | } 86 | s.authMiddleware = netpkg.NewHTTPAuthMiddleware(cfg.User, cfg.Password).SetAuthFailDelay(200 * time.Millisecond).Middleware 87 | return s, nil 88 | } 89 | 90 | func (s *Server) Address() string { 91 | return s.addr 92 | } 93 | 94 | func (s *Server) Run() error { 95 | ln := s.ln 96 | if s.tlsCfg != nil { 97 | ln = tls.NewListener(ln, s.tlsCfg) 98 | } 99 | return s.hs.Serve(ln) 100 | } 101 | 102 | func (s *Server) Close() error { 103 | return s.hs.Close() 104 | } 105 | 106 | type RouterRegisterHelper struct { 107 | Router *mux.Router 108 | AssetsFS http.FileSystem 109 | AuthMiddleware mux.MiddlewareFunc 110 | } 111 | 112 | func (s *Server) RouteRegister(register func(helper *RouterRegisterHelper)) { 113 | register(&RouterRegisterHelper{ 114 | Router: s.router, 115 | AssetsFS: assets.FileSystem, 116 | AuthMiddleware: s.authMiddleware, 117 | }) 118 | } 119 | 120 | func (s *Server) registerPprofHandlers() { 121 | s.router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 122 | s.router.HandleFunc("/debug/pprof/profile", pprof.Profile) 123 | s.router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 124 | s.router.HandleFunc("/debug/pprof/trace", pprof.Trace) 125 | s.router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index) 126 | } 127 | -------------------------------------------------------------------------------- /pkg/util/limit/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package limit 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "golang.org/x/time/rate" 22 | ) 23 | 24 | type Reader struct { 25 | r io.Reader 26 | limiter *rate.Limiter 27 | } 28 | 29 | func NewReader(r io.Reader, limiter *rate.Limiter) *Reader { 30 | return &Reader{ 31 | r: r, 32 | limiter: limiter, 33 | } 34 | } 35 | 36 | func (r *Reader) Read(p []byte) (n int, err error) { 37 | b := r.limiter.Burst() 38 | if b < len(p) { 39 | p = p[:b] 40 | } 41 | n, err = r.r.Read(p) 42 | if err != nil { 43 | return 44 | } 45 | 46 | err = r.limiter.WaitN(context.Background(), n) 47 | if err != nil { 48 | return 49 | } 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /pkg/util/limit/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package limit 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "golang.org/x/time/rate" 22 | ) 23 | 24 | type Writer struct { 25 | w io.Writer 26 | limiter *rate.Limiter 27 | } 28 | 29 | func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer { 30 | return &Writer{ 31 | w: w, 32 | limiter: limiter, 33 | } 34 | } 35 | 36 | func (w *Writer) Write(p []byte) (n int, err error) { 37 | var nn int 38 | b := w.limiter.Burst() 39 | for { 40 | end := len(p) 41 | if end == 0 { 42 | break 43 | } 44 | if b < len(p) { 45 | end = b 46 | } 47 | err = w.limiter.WaitN(context.Background(), end) 48 | if err != nil { 49 | return 50 | } 51 | 52 | nn, err = w.w.Write(p[:end]) 53 | n += nn 54 | if err != nil { 55 | return 56 | } 57 | p = p[end:] 58 | } 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /pkg/util/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "bytes" 19 | "os" 20 | 21 | "github.com/fatedier/golib/log" 22 | ) 23 | 24 | var ( 25 | TraceLevel = log.TraceLevel 26 | DebugLevel = log.DebugLevel 27 | InfoLevel = log.InfoLevel 28 | WarnLevel = log.WarnLevel 29 | ErrorLevel = log.ErrorLevel 30 | ) 31 | 32 | var Logger *log.Logger 33 | 34 | func init() { 35 | Logger = log.New( 36 | log.WithCaller(true), 37 | log.AddCallerSkip(1), 38 | log.WithLevel(log.InfoLevel), 39 | ) 40 | } 41 | 42 | func InitLogger(logPath string, levelStr string, maxDays int, disableLogColor bool) { 43 | options := []log.Option{} 44 | if logPath == "console" { 45 | if !disableLogColor { 46 | options = append(options, 47 | log.WithOutput(log.NewConsoleWriter(log.ConsoleConfig{ 48 | Colorful: true, 49 | }, os.Stdout)), 50 | ) 51 | } 52 | } else { 53 | writer := log.NewRotateFileWriter(log.RotateFileConfig{ 54 | FileName: logPath, 55 | Mode: log.RotateFileModeDaily, 56 | MaxDays: maxDays, 57 | }) 58 | writer.Init() 59 | options = append(options, log.WithOutput(writer)) 60 | } 61 | 62 | level, err := log.ParseLevel(levelStr) 63 | if err != nil { 64 | level = log.InfoLevel 65 | } 66 | options = append(options, log.WithLevel(level)) 67 | Logger = Logger.WithOptions(options...) 68 | } 69 | 70 | func Errorf(format string, v ...interface{}) { 71 | Logger.Errorf(format, v...) 72 | } 73 | 74 | func Warnf(format string, v ...interface{}) { 75 | Logger.Warnf(format, v...) 76 | } 77 | 78 | func Infof(format string, v ...interface{}) { 79 | Logger.Infof(format, v...) 80 | } 81 | 82 | func Debugf(format string, v ...interface{}) { 83 | Logger.Debugf(format, v...) 84 | } 85 | 86 | func Tracef(format string, v ...interface{}) { 87 | Logger.Tracef(format, v...) 88 | } 89 | 90 | func Logf(level log.Level, offset int, format string, v ...interface{}) { 91 | Logger.Logf(level, offset, format, v...) 92 | } 93 | 94 | type WriteLogger struct { 95 | level log.Level 96 | offset int 97 | } 98 | 99 | func NewWriteLogger(level log.Level, offset int) *WriteLogger { 100 | return &WriteLogger{ 101 | level: level, 102 | offset: offset, 103 | } 104 | } 105 | 106 | func (w *WriteLogger) Write(p []byte) (n int, err error) { 107 | Logger.Log(w.level, w.offset, string(bytes.TrimRight(p, "\n"))) 108 | return len(p), nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/util/metric/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metric 16 | 17 | import ( 18 | "sync/atomic" 19 | ) 20 | 21 | type Counter interface { 22 | Count() int32 23 | Inc(int32) 24 | Dec(int32) 25 | Snapshot() Counter 26 | Clear() 27 | } 28 | 29 | func NewCounter() Counter { 30 | return &StandardCounter{ 31 | count: 0, 32 | } 33 | } 34 | 35 | type StandardCounter struct { 36 | count int32 37 | } 38 | 39 | func (c *StandardCounter) Count() int32 { 40 | return atomic.LoadInt32(&c.count) 41 | } 42 | 43 | func (c *StandardCounter) Inc(count int32) { 44 | atomic.AddInt32(&c.count, count) 45 | } 46 | 47 | func (c *StandardCounter) Dec(count int32) { 48 | atomic.AddInt32(&c.count, -count) 49 | } 50 | 51 | func (c *StandardCounter) Snapshot() Counter { 52 | tmp := &StandardCounter{ 53 | count: atomic.LoadInt32(&c.count), 54 | } 55 | return tmp 56 | } 57 | 58 | func (c *StandardCounter) Clear() { 59 | atomic.StoreInt32(&c.count, 0) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/util/metric/counter_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCounter(t *testing.T) { 10 | assert := assert.New(t) 11 | c := NewCounter() 12 | c.Inc(10) 13 | assert.EqualValues(10, c.Count()) 14 | 15 | c.Dec(5) 16 | assert.EqualValues(5, c.Count()) 17 | 18 | cTmp := c.Snapshot() 19 | assert.EqualValues(5, cTmp.Count()) 20 | 21 | c.Clear() 22 | assert.EqualValues(0, c.Count()) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/util/metric/date_counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metric 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | ) 21 | 22 | type DateCounter interface { 23 | TodayCount() int64 24 | GetLastDaysCount(lastdays int64) []int64 25 | Inc(int64) 26 | Dec(int64) 27 | Snapshot() DateCounter 28 | Clear() 29 | } 30 | 31 | func NewDateCounter(reserveDays int64) DateCounter { 32 | if reserveDays <= 0 { 33 | reserveDays = 1 34 | } 35 | return newStandardDateCounter(reserveDays) 36 | } 37 | 38 | type StandardDateCounter struct { 39 | reserveDays int64 40 | counts []int64 41 | 42 | lastUpdateDate time.Time 43 | mu sync.Mutex 44 | } 45 | 46 | func newStandardDateCounter(reserveDays int64) *StandardDateCounter { 47 | now := time.Now() 48 | now = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) 49 | s := &StandardDateCounter{ 50 | reserveDays: reserveDays, 51 | counts: make([]int64, reserveDays), 52 | lastUpdateDate: now, 53 | } 54 | return s 55 | } 56 | 57 | func (c *StandardDateCounter) TodayCount() int64 { 58 | c.mu.Lock() 59 | defer c.mu.Unlock() 60 | 61 | c.rotate(time.Now()) 62 | return c.counts[0] 63 | } 64 | 65 | func (c *StandardDateCounter) GetLastDaysCount(lastdays int64) []int64 { 66 | if lastdays > c.reserveDays { 67 | lastdays = c.reserveDays 68 | } 69 | counts := make([]int64, lastdays) 70 | 71 | c.mu.Lock() 72 | defer c.mu.Unlock() 73 | c.rotate(time.Now()) 74 | for i := 0; i < int(lastdays); i++ { 75 | counts[i] = c.counts[i] 76 | } 77 | return counts 78 | } 79 | 80 | func (c *StandardDateCounter) Inc(count int64) { 81 | c.mu.Lock() 82 | defer c.mu.Unlock() 83 | c.rotate(time.Now()) 84 | c.counts[0] += count 85 | } 86 | 87 | func (c *StandardDateCounter) Dec(count int64) { 88 | c.mu.Lock() 89 | defer c.mu.Unlock() 90 | c.rotate(time.Now()) 91 | c.counts[0] -= count 92 | } 93 | 94 | func (c *StandardDateCounter) Snapshot() DateCounter { 95 | c.mu.Lock() 96 | defer c.mu.Unlock() 97 | tmp := newStandardDateCounter(c.reserveDays) 98 | for i := 0; i < int(c.reserveDays); i++ { 99 | tmp.counts[i] = c.counts[i] 100 | } 101 | return tmp 102 | } 103 | 104 | func (c *StandardDateCounter) Clear() { 105 | c.mu.Lock() 106 | defer c.mu.Unlock() 107 | for i := 0; i < int(c.reserveDays); i++ { 108 | c.counts[i] = 0 109 | } 110 | } 111 | 112 | // rotate 113 | // Must hold the lock before calling this function. 114 | func (c *StandardDateCounter) rotate(now time.Time) { 115 | now = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) 116 | days := int(now.Sub(c.lastUpdateDate).Hours() / 24) 117 | 118 | defer func() { 119 | c.lastUpdateDate = now 120 | }() 121 | 122 | if days <= 0 { 123 | return 124 | } else if days >= int(c.reserveDays) { 125 | c.counts = make([]int64, c.reserveDays) 126 | return 127 | } 128 | newCounts := make([]int64, c.reserveDays) 129 | 130 | for i := days; i < int(c.reserveDays); i++ { 131 | newCounts[i] = c.counts[i-days] 132 | } 133 | c.counts = newCounts 134 | } 135 | -------------------------------------------------------------------------------- /pkg/util/metric/date_counter_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDateCounter(t *testing.T) { 10 | assert := assert.New(t) 11 | 12 | dc := NewDateCounter(3) 13 | dc.Inc(10) 14 | assert.EqualValues(10, dc.TodayCount()) 15 | 16 | dc.Dec(5) 17 | assert.EqualValues(5, dc.TodayCount()) 18 | 19 | counts := dc.GetLastDaysCount(3) 20 | assert.EqualValues(3, len(counts)) 21 | assert.EqualValues(5, counts[0]) 22 | assert.EqualValues(0, counts[1]) 23 | assert.EqualValues(0, counts[2]) 24 | 25 | dcTmp := dc.Snapshot() 26 | assert.EqualValues(5, dcTmp.TodayCount()) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/util/metric/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metric 16 | 17 | // GaugeMetric represents a single numerical value that can arbitrarily go up 18 | // and down. 19 | type GaugeMetric interface { 20 | Inc() 21 | Dec() 22 | Set(float64) 23 | } 24 | 25 | // CounterMetric represents a single numerical value that only ever 26 | // goes up. 27 | type CounterMetric interface { 28 | Inc() 29 | } 30 | 31 | // HistogramMetric counts individual observations. 32 | type HistogramMetric interface { 33 | Observe(float64) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/util/myutil/aes.go: -------------------------------------------------------------------------------- 1 | package myutil 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/tls" 6 | "io/ioutil" 7 | "math/rand" 8 | "net/http" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) { 14 | cipher, _ := aes.NewCipher(generateKey(key)) 15 | length := (len(origData) + aes.BlockSize) / aes.BlockSize 16 | plain := make([]byte, length*aes.BlockSize) 17 | copy(plain, origData) 18 | pad := byte(len(plain) - len(origData)) 19 | for i := len(origData); i < len(plain); i++ { 20 | plain[i] = pad 21 | } 22 | encrypted = make([]byte, len(plain)) 23 | // 分组分块加密 24 | for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { 25 | cipher.Encrypt(encrypted[bs:be], plain[bs:be]) 26 | } 27 | 28 | return encrypted 29 | } 30 | 31 | func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) { 32 | cipher, _ := aes.NewCipher(generateKey(key)) 33 | decrypted = make([]byte, len(encrypted)) 34 | // 35 | for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { 36 | cipher.Decrypt(decrypted[bs:be], encrypted[bs:be]) 37 | } 38 | 39 | trim := 0 40 | if len(decrypted) > 0 { 41 | trim = len(decrypted) - int(decrypted[len(decrypted)-1]) 42 | } 43 | 44 | return decrypted[:trim] 45 | } 46 | 47 | func generateKey(key []byte) (genKey []byte) { 48 | genKey = make([]byte, 16) 49 | copy(genKey, key) 50 | for i := 16; i < len(key); { 51 | for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { 52 | genKey[j] ^= key[i] 53 | } 54 | } 55 | return genKey 56 | } 57 | 58 | func HttpGet(url, host string) (string, error) { 59 | clientHttp := &http.Client{ 60 | Timeout: 30 * time.Second, // 设置超时时间为30秒 61 | Transport: &http.Transport{ 62 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 忽略证书验证 63 | }, 64 | } 65 | req, err := http.NewRequest("GET", url, nil) 66 | if err != nil { 67 | return "", err 68 | } 69 | req.Host = host 70 | resp, err := clientHttp.Do(req) 71 | if err != nil { 72 | return "", err 73 | } 74 | defer resp.Body.Close() 75 | body, err := ioutil.ReadAll(resp.Body) 76 | if err != nil { 77 | return "", err 78 | } 79 | bodyStr := strings.Trim(string(body), "\n") 80 | return bodyStr, nil 81 | //fmt.Println("响应内容:", string(body)) 82 | } 83 | 84 | func GenerateAESKey() string { 85 | var letters = []byte("qazxswedcvfrtgbnhyujmkiolp0123456789") 86 | result := make([]byte, 16) 87 | rand.Seed(time.Now().Unix()) 88 | for i := range result { 89 | result[i] = letters[rand.Intn(len(letters))] 90 | } 91 | return string(result) 92 | } 93 | -------------------------------------------------------------------------------- /pkg/util/net/dial.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/url" 7 | 8 | libnet "github.com/fatedier/golib/net" 9 | "golang.org/x/net/websocket" 10 | ) 11 | 12 | func DialHookCustomTLSHeadByte(enableTLS bool, disableCustomTLSHeadByte bool) libnet.AfterHookFunc { 13 | return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) { 14 | if enableTLS && !disableCustomTLSHeadByte { 15 | _, err := c.Write([]byte{byte(FRPTLSHeadByte)}) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | } 20 | return ctx, c, nil 21 | } 22 | } 23 | 24 | func DialHookWebsocket(protocol string, host string) libnet.AfterHookFunc { 25 | return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) { 26 | if protocol != "wss" { 27 | protocol = "ws" 28 | } 29 | if host == "" { 30 | host = addr 31 | } 32 | addr = protocol + "://" + host + FrpWebsocketPath 33 | uri, err := url.Parse(addr) 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | 38 | origin := "http://" + uri.Host 39 | cfg, err := websocket.NewConfig(addr, origin) 40 | if err != nil { 41 | return nil, nil, err 42 | } 43 | 44 | conn, err := websocket.NewClient(cfg, c) 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | return ctx, conn, nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/util/net/dns.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "context" 19 | "net" 20 | ) 21 | 22 | func SetDefaultDNSAddress(dnsAddress string) { 23 | if _, _, err := net.SplitHostPort(dnsAddress); err != nil { 24 | dnsAddress = net.JoinHostPort(dnsAddress, "53") 25 | } 26 | // Change default dns server 27 | net.DefaultResolver = &net.Resolver{ 28 | PreferGo: true, 29 | Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { 30 | return net.Dial(network, dnsAddress) 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/util/net/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "compress/gzip" 19 | "io" 20 | "net/http" 21 | "strings" 22 | "time" 23 | 24 | "github.com/xx/xxx/pkg/util/util" 25 | ) 26 | 27 | type HTTPAuthMiddleware struct { 28 | user string 29 | passwd string 30 | authFailDelay time.Duration 31 | } 32 | 33 | func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware { 34 | return &HTTPAuthMiddleware{ 35 | user: user, 36 | passwd: passwd, 37 | } 38 | } 39 | 40 | func (authMid *HTTPAuthMiddleware) SetAuthFailDelay(delay time.Duration) *HTTPAuthMiddleware { 41 | authMid.authFailDelay = delay 42 | return authMid 43 | } 44 | 45 | func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler { 46 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 47 | reqUser, reqPasswd, hasAuth := r.BasicAuth() 48 | if (authMid.user == "" && authMid.passwd == "") || 49 | (hasAuth && util.ConstantTimeEqString(reqUser, authMid.user) && 50 | util.ConstantTimeEqString(reqPasswd, authMid.passwd)) { 51 | next.ServeHTTP(w, r) 52 | } else { 53 | if authMid.authFailDelay > 0 { 54 | time.Sleep(authMid.authFailDelay) 55 | } 56 | w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) 57 | http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 58 | } 59 | }) 60 | } 61 | 62 | type HTTPGzipWrapper struct { 63 | h http.Handler 64 | } 65 | 66 | func (gw *HTTPGzipWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { 67 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 68 | gw.h.ServeHTTP(w, r) 69 | return 70 | } 71 | w.Header().Set("Content-Encoding", "gzip") 72 | gz := gzip.NewWriter(w) 73 | defer gz.Close() 74 | gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w} 75 | gw.h.ServeHTTP(gzr, r) 76 | } 77 | 78 | func MakeHTTPGzipHandler(h http.Handler) http.Handler { 79 | return &HTTPGzipWrapper{ 80 | h: h, 81 | } 82 | } 83 | 84 | type gzipResponseWriter struct { 85 | io.Writer 86 | http.ResponseWriter 87 | } 88 | 89 | func (w gzipResponseWriter) Write(b []byte) (int, error) { 90 | return w.Writer.Write(b) 91 | } 92 | -------------------------------------------------------------------------------- /pkg/util/net/kcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | 21 | kcp "github.com/xtaci/kcp-go/v5" 22 | ) 23 | 24 | type KCPListener struct { 25 | listener net.Listener 26 | acceptCh chan net.Conn 27 | closeFlag bool 28 | } 29 | 30 | func ListenKcp(address string) (l *KCPListener, err error) { 31 | listener, err := kcp.ListenWithOptions(address, nil, 10, 3) 32 | if err != nil { 33 | return l, err 34 | } 35 | _ = listener.SetReadBuffer(4194304) 36 | _ = listener.SetWriteBuffer(4194304) 37 | 38 | l = &KCPListener{ 39 | listener: listener, 40 | acceptCh: make(chan net.Conn), 41 | closeFlag: false, 42 | } 43 | 44 | go func() { 45 | for { 46 | conn, err := listener.AcceptKCP() 47 | if err != nil { 48 | if l.closeFlag { 49 | close(l.acceptCh) 50 | return 51 | } 52 | continue 53 | } 54 | conn.SetStreamMode(true) 55 | conn.SetWriteDelay(true) 56 | conn.SetNoDelay(1, 20, 2, 1) 57 | conn.SetMtu(1350) 58 | conn.SetWindowSize(1024, 1024) 59 | conn.SetACKNoDelay(false) 60 | 61 | l.acceptCh <- conn 62 | } 63 | }() 64 | return l, err 65 | } 66 | 67 | func (l *KCPListener) Accept() (net.Conn, error) { 68 | conn, ok := <-l.acceptCh 69 | if !ok { 70 | return conn, fmt.Errorf("channel for kcp listener closed") 71 | } 72 | return conn, nil 73 | } 74 | 75 | func (l *KCPListener) Close() error { 76 | if !l.closeFlag { 77 | l.closeFlag = true 78 | l.listener.Close() 79 | } 80 | return nil 81 | } 82 | 83 | func (l *KCPListener) Addr() net.Addr { 84 | return l.listener.Addr() 85 | } 86 | 87 | func NewKCPConnFromUDP(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) { 88 | udpAddr, err := net.ResolveUDPAddr("udp", raddr) 89 | if err != nil { 90 | return nil, err 91 | } 92 | var pConn net.PacketConn = conn 93 | if connected { 94 | pConn = &ConnectedUDPConn{conn} 95 | } 96 | kcpConn, err := kcp.NewConn3(1, udpAddr, nil, 10, 3, pConn) 97 | if err != nil { 98 | return nil, err 99 | } 100 | kcpConn.SetStreamMode(true) 101 | kcpConn.SetWriteDelay(true) 102 | kcpConn.SetNoDelay(1, 20, 2, 1) 103 | kcpConn.SetMtu(1350) 104 | kcpConn.SetWindowSize(1024, 1024) 105 | kcpConn.SetACKNoDelay(false) 106 | return kcpConn, nil 107 | } 108 | -------------------------------------------------------------------------------- /pkg/util/net/listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "sync" 21 | 22 | "github.com/fatedier/golib/errors" 23 | ) 24 | 25 | // InternalListener is a listener that can be used to accept connections from 26 | // other goroutines. 27 | type InternalListener struct { 28 | acceptCh chan net.Conn 29 | closed bool 30 | mu sync.Mutex 31 | } 32 | 33 | func NewInternalListener() *InternalListener { 34 | return &InternalListener{ 35 | acceptCh: make(chan net.Conn, 128), 36 | } 37 | } 38 | 39 | func (l *InternalListener) Accept() (net.Conn, error) { 40 | conn, ok := <-l.acceptCh 41 | if !ok { 42 | return nil, fmt.Errorf("listener closed") 43 | } 44 | return conn, nil 45 | } 46 | 47 | func (l *InternalListener) PutConn(conn net.Conn) error { 48 | err := errors.PanicToError(func() { 49 | select { 50 | case l.acceptCh <- conn: 51 | default: 52 | conn.Close() 53 | } 54 | }) 55 | if err != nil { 56 | return fmt.Errorf("put conn error: listener is closed") 57 | } 58 | return nil 59 | } 60 | 61 | func (l *InternalListener) Close() error { 62 | l.mu.Lock() 63 | defer l.mu.Unlock() 64 | if !l.closed { 65 | close(l.acceptCh) 66 | l.closed = true 67 | } 68 | return nil 69 | } 70 | 71 | func (l *InternalListener) Addr() net.Addr { 72 | return &InternalAddr{} 73 | } 74 | 75 | type InternalAddr struct{} 76 | 77 | func (ia *InternalAddr) Network() string { 78 | return "internal" 79 | } 80 | 81 | func (ia *InternalAddr) String() string { 82 | return "internal" 83 | } 84 | -------------------------------------------------------------------------------- /pkg/util/net/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "crypto/tls" 19 | "fmt" 20 | "net" 21 | "time" 22 | 23 | libnet "github.com/fatedier/golib/net" 24 | ) 25 | 26 | var FRPTLSHeadByte = 0x17 27 | 28 | func CheckAndEnableTLSServerConnWithTimeout( 29 | c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration, 30 | ) (out net.Conn, isTLS bool, custom bool, err error) { 31 | sc, r := libnet.NewSharedConnSize(c, 2) 32 | buf := make([]byte, 1) 33 | var n int 34 | _ = c.SetReadDeadline(time.Now().Add(timeout)) 35 | n, err = r.Read(buf) 36 | _ = c.SetReadDeadline(time.Time{}) 37 | if err != nil { 38 | return 39 | } 40 | 41 | switch { 42 | case n == 1 && int(buf[0]) == FRPTLSHeadByte: 43 | out = tls.Server(c, tlsConfig) 44 | isTLS = true 45 | custom = true 46 | case n == 1 && int(buf[0]) == 0x16: 47 | out = tls.Server(sc, tlsConfig) 48 | isTLS = true 49 | default: 50 | if tlsOnly { 51 | err = fmt.Errorf("non-TLS connection received on a TlsOnly server") 52 | return 53 | } 54 | out = sc 55 | } 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /pkg/util/net/websocket.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "golang.org/x/net/websocket" 10 | ) 11 | 12 | var ErrWebsocketListenerClosed = errors.New("websocket listener closed") 13 | 14 | const ( 15 | FrpWebsocketPath = "/~!frp" 16 | ) 17 | 18 | type WebsocketListener struct { 19 | ln net.Listener 20 | acceptCh chan net.Conn 21 | 22 | server *http.Server 23 | } 24 | 25 | // NewWebsocketListener to handle websocket connections 26 | // ln: tcp listener for websocket connections 27 | func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { 28 | wl = &WebsocketListener{ 29 | acceptCh: make(chan net.Conn), 30 | } 31 | 32 | muxer := http.NewServeMux() 33 | muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) { 34 | notifyCh := make(chan struct{}) 35 | conn := WrapCloseNotifyConn(c, func() { 36 | close(notifyCh) 37 | }) 38 | wl.acceptCh <- conn 39 | <-notifyCh 40 | })) 41 | 42 | wl.server = &http.Server{ 43 | Addr: ln.Addr().String(), 44 | Handler: muxer, 45 | ReadHeaderTimeout: 60 * time.Second, 46 | } 47 | 48 | go func() { 49 | _ = wl.server.Serve(ln) 50 | }() 51 | return 52 | } 53 | 54 | func (p *WebsocketListener) Accept() (net.Conn, error) { 55 | c, ok := <-p.acceptCh 56 | if !ok { 57 | return nil, ErrWebsocketListenerClosed 58 | } 59 | return c, nil 60 | } 61 | 62 | func (p *WebsocketListener) Close() error { 63 | return p.server.Close() 64 | } 65 | 66 | func (p *WebsocketListener) Addr() net.Addr { 67 | return p.ln.Addr() 68 | } 69 | -------------------------------------------------------------------------------- /pkg/util/system/system.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !android 16 | 17 | package system 18 | 19 | // EnableCompatibilityMode enables compatibility mode for different system. 20 | // For example, on Android, the inability to obtain the correct time zone will result in incorrect log time output. 21 | func EnableCompatibilityMode() { 22 | } 23 | -------------------------------------------------------------------------------- /pkg/util/system/system_android.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package system 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "os/exec" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | func EnableCompatibilityMode() { 26 | fixTimezone() 27 | fixDNSResolver() 28 | } 29 | 30 | // fixTimezone is used to try our best to fix timezone issue on some Android devices. 31 | func fixTimezone() { 32 | out, err := exec.Command("/system/bin/getprop", "persist.sys.timezone").Output() 33 | if err != nil { 34 | return 35 | } 36 | loc, err := time.LoadLocation(strings.TrimSpace(string(out))) 37 | if err != nil { 38 | return 39 | } 40 | time.Local = loc 41 | } 42 | 43 | // fixDNSResolver will first attempt to resolve google.com to check if the current DNS is available. 44 | // If it is not available, it will default to using 8.8.8.8 as the DNS server. 45 | // This is a workaround for the issue that golang can't get the default DNS servers on Android. 46 | func fixDNSResolver() { 47 | // First, we attempt to resolve a domain. If resolution is successful, no modifications are necessary. 48 | // In real-world scenarios, users may have already configured /etc/resolv.conf, or compiled directly 49 | // in the Android environment instead of using cross-platform compilation, so this issue does not arise. 50 | if net.DefaultResolver != nil { 51 | timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second) 52 | defer cancel() 53 | _, err := net.DefaultResolver.LookupHost(timeoutCtx, "google.com") 54 | if err == nil { 55 | return 56 | } 57 | } 58 | // If the resolution fails, use 8.8.8.8 as the DNS server. 59 | // Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially. 60 | net.DefaultResolver = &net.Resolver{ 61 | PreferGo: true, 62 | Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { 63 | if addr == "127.0.0.1:53" || addr == "[::1]:53" { 64 | addr = "8.8.8.8:53" 65 | } 66 | var d net.Dialer 67 | return d.DialContext(ctx, network, addr) 68 | }, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pkg/util/tcpmux/httpconnect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 guylewin, guy@lewin.co.il 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tcpmux 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "io" 21 | "net" 22 | "net/http" 23 | "time" 24 | 25 | libnet "github.com/fatedier/golib/net" 26 | 27 | httppkg "github.com/xx/xxx/pkg/util/http" 28 | "github.com/xx/xxx/pkg/util/vhost" 29 | ) 30 | 31 | type HTTPConnectTCPMuxer struct { 32 | *vhost.Muxer 33 | 34 | // If passthrough is set to true, the CONNECT request will be forwarded to the backend service. 35 | // Otherwise, it will return an OK response to the client and forward the remaining content to the backend service. 36 | passthrough bool 37 | } 38 | 39 | func NewHTTPConnectTCPMuxer(listener net.Listener, passthrough bool, timeout time.Duration) (*HTTPConnectTCPMuxer, error) { 40 | ret := &HTTPConnectTCPMuxer{passthrough: passthrough} 41 | mux, err := vhost.NewMuxer(listener, ret.getHostFromHTTPConnect, timeout) 42 | mux.SetCheckAuthFunc(ret.auth). 43 | SetSuccessHookFunc(ret.sendConnectResponse). 44 | SetFailHookFunc(vhostFailed) 45 | ret.Muxer = mux 46 | return ret, err 47 | } 48 | 49 | func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host, httpUser, httpPwd string, err error) { 50 | bufioReader := bufio.NewReader(rd) 51 | 52 | req, err := http.ReadRequest(bufioReader) 53 | if err != nil { 54 | return 55 | } 56 | 57 | if req.Method != "CONNECT" { 58 | err = fmt.Errorf("connections to tcp vhost must be of method CONNECT") 59 | return 60 | } 61 | 62 | host, _ = httppkg.CanonicalHost(req.Host) 63 | proxyAuth := req.Header.Get("Proxy-Authorization") 64 | if proxyAuth != "" { 65 | httpUser, httpPwd, _ = httppkg.ParseBasicAuth(proxyAuth) 66 | } 67 | return 68 | } 69 | 70 | func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, _ map[string]string) error { 71 | if muxer.passthrough { 72 | return nil 73 | } 74 | res := httppkg.OkResponse() 75 | if res.Body != nil { 76 | defer res.Body.Close() 77 | } 78 | return res.Write(c) 79 | } 80 | 81 | func (muxer *HTTPConnectTCPMuxer) auth(c net.Conn, username, password string, reqInfo map[string]string) (bool, error) { 82 | reqUsername := reqInfo["HTTPUser"] 83 | reqPassword := reqInfo["HTTPPwd"] 84 | if username == reqUsername && password == reqPassword { 85 | return true, nil 86 | } 87 | 88 | resp := httppkg.ProxyUnauthorizedResponse() 89 | if resp.Body != nil { 90 | defer resp.Body.Close() 91 | } 92 | _ = resp.Write(c) 93 | return false, nil 94 | } 95 | 96 | func vhostFailed(c net.Conn) { 97 | res := vhost.NotFoundResponse() 98 | if res.Body != nil { 99 | defer res.Body.Close() 100 | } 101 | _ = res.Write(c) 102 | _ = c.Close() 103 | } 104 | 105 | func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) { 106 | reqInfoMap := make(map[string]string, 0) 107 | sc, rd := libnet.NewSharedConn(c) 108 | 109 | host, httpUser, httpPwd, err := muxer.readHTTPConnectRequest(rd) 110 | if err != nil { 111 | return nil, reqInfoMap, err 112 | } 113 | 114 | reqInfoMap["Host"] = host 115 | reqInfoMap["Scheme"] = "tcp" 116 | reqInfoMap["HTTPUser"] = httpUser 117 | reqInfoMap["HTTPPwd"] = httpPwd 118 | 119 | outConn := c 120 | if muxer.passthrough { 121 | outConn = sc 122 | } 123 | return outConn, reqInfoMap, nil 124 | } 125 | -------------------------------------------------------------------------------- /pkg/util/util/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | func EmptyOr[T comparable](v T, fallback T) T { 18 | var zero T 19 | if zero == v { 20 | return fallback 21 | } 22 | return v 23 | } 24 | -------------------------------------------------------------------------------- /pkg/util/util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRandId(t *testing.T) { 10 | assert := assert.New(t) 11 | id, err := RandID() 12 | assert.NoError(err) 13 | t.Log(id) 14 | assert.Equal(16, len(id)) 15 | } 16 | 17 | func TestGetAuthKey(t *testing.T) { 18 | assert := assert.New(t) 19 | key := GetAuthKey("1234", 1488720000) 20 | assert.Equal("6df41a43725f0c770fd56379e12acf8c", key) 21 | } 22 | 23 | func TestParseRangeNumbers(t *testing.T) { 24 | assert := assert.New(t) 25 | numbers, err := ParseRangeNumbers("2-5") 26 | if assert.NoError(err) { 27 | assert.Equal([]int64{2, 3, 4, 5}, numbers) 28 | } 29 | 30 | numbers, err = ParseRangeNumbers("1") 31 | if assert.NoError(err) { 32 | assert.Equal([]int64{1}, numbers) 33 | } 34 | 35 | numbers, err = ParseRangeNumbers("3-5,8") 36 | if assert.NoError(err) { 37 | assert.Equal([]int64{3, 4, 5, 8}, numbers) 38 | } 39 | 40 | numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ") 41 | if assert.NoError(err) { 42 | assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers) 43 | } 44 | 45 | _, err = ParseRangeNumbers("3-a") 46 | assert.Error(err) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/util/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | var version = "0.58.1" 18 | 19 | func Full() string { 20 | return version 21 | } 22 | -------------------------------------------------------------------------------- /pkg/util/vhost/https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vhost 16 | 17 | import ( 18 | "crypto/tls" 19 | "io" 20 | "net" 21 | "time" 22 | 23 | libnet "github.com/fatedier/golib/net" 24 | ) 25 | 26 | type HTTPSMuxer struct { 27 | *Muxer 28 | } 29 | 30 | func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, error) { 31 | mux, err := NewMuxer(listener, GetHTTPSHostname, timeout) 32 | mux.SetFailHookFunc(vhostFailed) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return &HTTPSMuxer{mux}, err 37 | } 38 | 39 | func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) { 40 | reqInfoMap := make(map[string]string, 0) 41 | sc, rd := libnet.NewSharedConn(c) 42 | 43 | clientHello, err := readClientHello(rd) 44 | if err != nil { 45 | return nil, reqInfoMap, err 46 | } 47 | 48 | reqInfoMap["Host"] = clientHello.ServerName 49 | reqInfoMap["Scheme"] = "https" 50 | return sc, reqInfoMap, nil 51 | } 52 | 53 | func readClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) { 54 | var hello *tls.ClientHelloInfo 55 | 56 | // Note that Handshake always fails because the readOnlyConn is not a real connection. 57 | // As long as the Client Hello is successfully read, the failure should only happen after GetConfigForClient is called, 58 | // so we only care about the error if hello was never set. 59 | err := tls.Server(readOnlyConn{reader: reader}, &tls.Config{ 60 | GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { 61 | hello = &tls.ClientHelloInfo{} 62 | *hello = *argHello 63 | return nil, nil 64 | }, 65 | }).Handshake() 66 | 67 | if hello == nil { 68 | return nil, err 69 | } 70 | return hello, nil 71 | } 72 | 73 | func vhostFailed(c net.Conn) { 74 | // Alert with alertUnrecognizedName 75 | _ = tls.Server(c, &tls.Config{}).Handshake() 76 | c.Close() 77 | } 78 | 79 | type readOnlyConn struct { 80 | reader io.Reader 81 | } 82 | 83 | func (conn readOnlyConn) Read(p []byte) (int, error) { return conn.reader.Read(p) } 84 | func (conn readOnlyConn) Write(_ []byte) (int, error) { return 0, io.ErrClosedPipe } 85 | func (conn readOnlyConn) Close() error { return nil } 86 | func (conn readOnlyConn) LocalAddr() net.Addr { return nil } 87 | func (conn readOnlyConn) RemoteAddr() net.Addr { return nil } 88 | func (conn readOnlyConn) SetDeadline(_ time.Time) error { return nil } 89 | func (conn readOnlyConn) SetReadDeadline(_ time.Time) error { return nil } 90 | func (conn readOnlyConn) SetWriteDeadline(_ time.Time) error { return nil } 91 | -------------------------------------------------------------------------------- /pkg/util/vhost/https_test.go: -------------------------------------------------------------------------------- 1 | package vhost 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestGetHTTPSHostname(t *testing.T) { 13 | require := require.New(t) 14 | 15 | l, err := net.Listen("tcp", "127.0.0.1:") 16 | require.NoError(err) 17 | defer l.Close() 18 | 19 | var conn net.Conn 20 | go func() { 21 | conn, _ = l.Accept() 22 | require.NotNil(conn) 23 | }() 24 | 25 | go func() { 26 | time.Sleep(100 * time.Millisecond) 27 | tls.Dial("tcp", l.Addr().String(), &tls.Config{ 28 | InsecureSkipVerify: true, 29 | ServerName: "example.com", 30 | }) 31 | }() 32 | 33 | time.Sleep(200 * time.Millisecond) 34 | _, infos, err := GetHTTPSHostname(conn) 35 | require.NoError(err) 36 | require.Equal("example.com", infos["Host"]) 37 | require.Equal("https", infos["Scheme"]) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/util/vhost/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vhost 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "net/http" 21 | "os" 22 | 23 | "github.com/xx/xxx/pkg/util/log" 24 | "github.com/xx/xxx/pkg/util/version" 25 | ) 26 | 27 | var NotFoundPagePath = "" 28 | 29 | const ( 30 | NotFound = ` 31 | 32 | 33 | Not Found 34 | 41 | 42 | 43 |

The page you requested was not found.

44 |

Sorry, the page you are looking for is currently unavailable.
45 | Please try again later.

46 |

The server is powered by frp.

47 |

Faithfully yours, frp.

48 | 49 | 50 | ` 51 | ) 52 | 53 | func getNotFoundPageContent() []byte { 54 | var ( 55 | buf []byte 56 | err error 57 | ) 58 | if NotFoundPagePath != "" { 59 | buf, err = os.ReadFile(NotFoundPagePath) 60 | if err != nil { 61 | log.Warnf("read custom 404 page error: %v", err) 62 | buf = []byte(NotFound) 63 | } 64 | } else { 65 | buf = []byte(NotFound) 66 | } 67 | return buf 68 | } 69 | 70 | func NotFoundResponse() *http.Response { 71 | header := make(http.Header) 72 | header.Set("server", "frp/"+version.Full()) 73 | header.Set("Content-Type", "text/html") 74 | 75 | content := getNotFoundPageContent() 76 | res := &http.Response{ 77 | Status: "Not Found", 78 | StatusCode: 404, 79 | Proto: "HTTP/1.1", 80 | ProtoMajor: 1, 81 | ProtoMinor: 1, 82 | Header: header, 83 | Body: io.NopCloser(bytes.NewReader(content)), 84 | ContentLength: int64(len(content)), 85 | } 86 | return res 87 | } 88 | -------------------------------------------------------------------------------- /pkg/util/vhost/router.go: -------------------------------------------------------------------------------- 1 | package vhost 2 | 3 | import ( 4 | "cmp" 5 | "errors" 6 | "slices" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | var ErrRouterConfigConflict = errors.New("router config conflict") 12 | 13 | type routerByHTTPUser map[string][]*Router 14 | 15 | type Routers struct { 16 | indexByDomain map[string]routerByHTTPUser 17 | 18 | mutex sync.RWMutex 19 | } 20 | 21 | type Router struct { 22 | domain string 23 | location string 24 | httpUser string 25 | 26 | // store any object here 27 | payload interface{} 28 | } 29 | 30 | func NewRouters() *Routers { 31 | return &Routers{ 32 | indexByDomain: make(map[string]routerByHTTPUser), 33 | } 34 | } 35 | 36 | func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error { 37 | domain = strings.ToLower(domain) 38 | 39 | r.mutex.Lock() 40 | defer r.mutex.Unlock() 41 | 42 | if _, exist := r.exist(domain, location, httpUser); exist { 43 | return ErrRouterConfigConflict 44 | } 45 | 46 | routersByHTTPUser, found := r.indexByDomain[domain] 47 | if !found { 48 | routersByHTTPUser = make(map[string][]*Router) 49 | } 50 | vrs, found := routersByHTTPUser[httpUser] 51 | if !found { 52 | vrs = make([]*Router, 0, 1) 53 | } 54 | 55 | vr := &Router{ 56 | domain: domain, 57 | location: location, 58 | httpUser: httpUser, 59 | payload: payload, 60 | } 61 | vrs = append(vrs, vr) 62 | 63 | slices.SortFunc(vrs, func(a, b *Router) int { 64 | return -cmp.Compare(a.location, b.location) 65 | }) 66 | 67 | routersByHTTPUser[httpUser] = vrs 68 | r.indexByDomain[domain] = routersByHTTPUser 69 | return nil 70 | } 71 | 72 | func (r *Routers) Del(domain, location, httpUser string) { 73 | domain = strings.ToLower(domain) 74 | 75 | r.mutex.Lock() 76 | defer r.mutex.Unlock() 77 | 78 | routersByHTTPUser, found := r.indexByDomain[domain] 79 | if !found { 80 | return 81 | } 82 | 83 | vrs, found := routersByHTTPUser[httpUser] 84 | if !found { 85 | return 86 | } 87 | newVrs := make([]*Router, 0) 88 | for _, vr := range vrs { 89 | if vr.location != location { 90 | newVrs = append(newVrs, vr) 91 | } 92 | } 93 | routersByHTTPUser[httpUser] = newVrs 94 | } 95 | 96 | func (r *Routers) Get(host, path, httpUser string) (vr *Router, exist bool) { 97 | host = strings.ToLower(host) 98 | 99 | r.mutex.RLock() 100 | defer r.mutex.RUnlock() 101 | 102 | routersByHTTPUser, found := r.indexByDomain[host] 103 | if !found { 104 | return 105 | } 106 | 107 | vrs, found := routersByHTTPUser[httpUser] 108 | if !found { 109 | return 110 | } 111 | 112 | for _, vr = range vrs { 113 | if strings.HasPrefix(path, vr.location) { 114 | return vr, true 115 | } 116 | } 117 | return 118 | } 119 | 120 | func (r *Routers) exist(host, path, httpUser string) (route *Router, exist bool) { 121 | routersByHTTPUser, found := r.indexByDomain[host] 122 | if !found { 123 | return 124 | } 125 | routers, found := routersByHTTPUser[httpUser] 126 | if !found { 127 | return 128 | } 129 | 130 | for _, route = range routers { 131 | if path == route.location { 132 | return route, true 133 | } 134 | } 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /pkg/util/xlog/ctx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package xlog 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | type key int 22 | 23 | const ( 24 | xlogKey key = 0 25 | ) 26 | 27 | func NewContext(ctx context.Context, xl *Logger) context.Context { 28 | return context.WithValue(ctx, xlogKey, xl) 29 | } 30 | 31 | func FromContext(ctx context.Context) (xl *Logger, ok bool) { 32 | xl, ok = ctx.Value(xlogKey).(*Logger) 33 | return 34 | } 35 | 36 | func FromContextSafe(ctx context.Context) *Logger { 37 | xl, ok := ctx.Value(xlogKey).(*Logger) 38 | if !ok { 39 | xl = New() 40 | } 41 | return xl 42 | } 43 | -------------------------------------------------------------------------------- /pkg/util/xlog/xlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package xlog 16 | 17 | import ( 18 | "cmp" 19 | "slices" 20 | 21 | "github.com/xx/xxx/pkg/util/log" 22 | ) 23 | 24 | type LogPrefix struct { 25 | // Name is the name of the prefix, it won't be displayed in log but used to identify the prefix. 26 | Name string 27 | // Value is the value of the prefix, it will be displayed in log. 28 | Value string 29 | // The prefix with higher priority will be displayed first, default is 10. 30 | Priority int 31 | } 32 | 33 | // Logger is not thread safety for operations on prefix 34 | type Logger struct { 35 | prefixes []LogPrefix 36 | 37 | prefixString string 38 | } 39 | 40 | func New() *Logger { 41 | return &Logger{ 42 | prefixes: make([]LogPrefix, 0), 43 | } 44 | } 45 | 46 | func (l *Logger) ResetPrefixes() (old []LogPrefix) { 47 | old = l.prefixes 48 | l.prefixes = make([]LogPrefix, 0) 49 | l.prefixString = "" 50 | return 51 | } 52 | 53 | func (l *Logger) AppendPrefix(prefix string) *Logger { 54 | return l.AddPrefix(LogPrefix{ 55 | Name: prefix, 56 | Value: prefix, 57 | Priority: 10, 58 | }) 59 | } 60 | 61 | func (l *Logger) AddPrefix(prefix LogPrefix) *Logger { 62 | found := false 63 | if prefix.Priority <= 0 { 64 | prefix.Priority = 10 65 | } 66 | for _, p := range l.prefixes { 67 | if p.Name == prefix.Name { 68 | found = true 69 | p.Value = prefix.Value 70 | p.Priority = prefix.Priority 71 | } 72 | } 73 | if !found { 74 | l.prefixes = append(l.prefixes, prefix) 75 | } 76 | l.renderPrefixString() 77 | return l 78 | } 79 | 80 | func (l *Logger) renderPrefixString() { 81 | slices.SortStableFunc(l.prefixes, func(a, b LogPrefix) int { 82 | return cmp.Compare(a.Priority, b.Priority) 83 | }) 84 | l.prefixString = "" 85 | for _, v := range l.prefixes { 86 | l.prefixString += "[" + v.Value + "] " 87 | } 88 | } 89 | 90 | func (l *Logger) Spawn() *Logger { 91 | nl := New() 92 | nl.prefixes = append(nl.prefixes, l.prefixes...) 93 | nl.renderPrefixString() 94 | return nl 95 | } 96 | 97 | func (l *Logger) Errorf(format string, v ...interface{}) { 98 | log.Logger.Errorf(l.prefixString+format, v...) 99 | } 100 | 101 | func (l *Logger) Warnf(format string, v ...interface{}) { 102 | log.Logger.Warnf(l.prefixString+format, v...) 103 | } 104 | 105 | func (l *Logger) Infof(format string, v ...interface{}) { 106 | log.Logger.Infof(l.prefixString+format, v...) 107 | } 108 | 109 | func (l *Logger) Debugf(format string, v ...interface{}) { 110 | log.Logger.Debugf(l.prefixString+format, v...) 111 | } 112 | 113 | func (l *Logger) Tracef(format string, v ...interface{}) { 114 | log.Logger.Tracef(l.prefixString+format, v...) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/virtual/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package virtual 16 | 17 | import ( 18 | "context" 19 | "net" 20 | 21 | "github.com/xx/xxx/client" 22 | v1 "github.com/xx/xxx/pkg/config/v1" 23 | "github.com/xx/xxx/pkg/msg" 24 | netpkg "github.com/xx/xxx/pkg/util/net" 25 | ) 26 | 27 | type ClientOptions struct { 28 | Common *v1.ClientCommonConfig 29 | Spec *msg.ClientSpec 30 | HandleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool 31 | } 32 | 33 | type Client struct { 34 | l *netpkg.InternalListener 35 | svr *client.Service 36 | } 37 | 38 | func NewClient(options ClientOptions) (*Client, error) { 39 | if options.Common != nil { 40 | options.Common.Complete() 41 | } 42 | 43 | ln := netpkg.NewInternalListener() 44 | 45 | serviceOptions := client.ServiceOptions{ 46 | Common: options.Common, 47 | ClientSpec: options.Spec, 48 | ConnectorCreator: func(context.Context, *v1.ClientCommonConfig) client.Connector { 49 | return &pipeConnector{ 50 | peerListener: ln, 51 | } 52 | }, 53 | HandleWorkConnCb: options.HandleWorkConnCb, 54 | } 55 | svr, err := client.NewService(serviceOptions) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return &Client{ 60 | l: ln, 61 | svr: svr, 62 | }, nil 63 | } 64 | 65 | func (c *Client) PeerListener() net.Listener { 66 | return c.l 67 | } 68 | 69 | func (c *Client) UpdateProxyConfigurer(proxyCfgs []v1.ProxyConfigurer) { 70 | _ = c.svr.UpdateAllConfigurer(proxyCfgs, nil) 71 | } 72 | 73 | func (c *Client) Run(ctx context.Context) error { 74 | return c.svr.Run(ctx) 75 | } 76 | 77 | func (c *Client) Service() *client.Service { 78 | return c.svr 79 | } 80 | 81 | func (c *Client) Close() { 82 | c.svr.Close() 83 | c.l.Close() 84 | } 85 | 86 | type pipeConnector struct { 87 | peerListener *netpkg.InternalListener 88 | } 89 | 90 | func (pc *pipeConnector) Open() error { 91 | return nil 92 | } 93 | 94 | func (pc *pipeConnector) Connect() (net.Conn, error) { 95 | c1, c2 := net.Pipe() 96 | if err := pc.peerListener.PutConn(c1); err != nil { 97 | c1.Close() 98 | c2.Close() 99 | return nil, err 100 | } 101 | return c2, nil 102 | } 103 | 104 | func (pc *pipeConnector) Close() error { 105 | pc.peerListener.Close() 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /server/controller/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package controller 16 | 17 | import ( 18 | "github.com/xx/xxx/pkg/nathole" 19 | plugin "github.com/xx/xxx/pkg/plugin/server" 20 | "github.com/xx/xxx/pkg/util/tcpmux" 21 | "github.com/xx/xxx/pkg/util/vhost" 22 | "github.com/xx/xxx/server/group" 23 | "github.com/xx/xxx/server/ports" 24 | "github.com/xx/xxx/server/visitor" 25 | ) 26 | 27 | // All resource managers and controllers 28 | type ResourceController struct { 29 | // Manage all visitor listeners 30 | VisitorManager *visitor.Manager 31 | 32 | // TCP Group Controller 33 | TCPGroupCtl *group.TCPGroupCtl 34 | 35 | // HTTP Group Controller 36 | HTTPGroupCtl *group.HTTPGroupController 37 | 38 | // TCP Mux Group Controller 39 | TCPMuxGroupCtl *group.TCPMuxGroupCtl 40 | 41 | // Manage all TCP ports 42 | TCPPortManager *ports.Manager 43 | 44 | // Manage all UDP ports 45 | UDPPortManager *ports.Manager 46 | 47 | // For HTTP proxies, forwarding HTTP requests 48 | HTTPReverseProxy *vhost.HTTPReverseProxy 49 | 50 | // For HTTPS proxies, route requests to different clients by hostname and other information 51 | VhostHTTPSMuxer *vhost.HTTPSMuxer 52 | 53 | // Controller for nat hole connections 54 | NatHoleController *nathole.Controller 55 | 56 | // TCPMux HTTP CONNECT multiplexer 57 | TCPMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxer 58 | 59 | // All server manager plugin 60 | PluginManager *plugin.Manager 61 | } 62 | -------------------------------------------------------------------------------- /server/group/group.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package group 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | var ( 22 | ErrGroupAuthFailed = errors.New("group auth failed") 23 | ErrGroupParamsInvalid = errors.New("group params invalid") 24 | ErrListenerClosed = errors.New("group listener closed") 25 | ErrGroupDifferentPort = errors.New("group should have same remote port") 26 | ErrProxyRepeated = errors.New("group proxy repeated") 27 | ) 28 | -------------------------------------------------------------------------------- /server/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ServerMetrics interface { 8 | NewClient() 9 | CloseClient() 10 | NewProxy(name string, proxyType string) 11 | CloseProxy(name string, proxyType string) 12 | OpenConnection(name string, proxyType string) 13 | CloseConnection(name string, proxyType string) 14 | AddTrafficIn(name string, proxyType string, trafficBytes int64) 15 | AddTrafficOut(name string, proxyType string, trafficBytes int64) 16 | } 17 | 18 | var Server ServerMetrics = noopServerMetrics{} 19 | 20 | var registerMetrics sync.Once 21 | 22 | func Register(m ServerMetrics) { 23 | registerMetrics.Do(func() { 24 | Server = m 25 | }) 26 | } 27 | 28 | type noopServerMetrics struct{} 29 | 30 | func (noopServerMetrics) NewClient() {} 31 | func (noopServerMetrics) CloseClient() {} 32 | func (noopServerMetrics) NewProxy(string, string) {} 33 | func (noopServerMetrics) CloseProxy(string, string) {} 34 | func (noopServerMetrics) OpenConnection(string, string) {} 35 | func (noopServerMetrics) CloseConnection(string, string) {} 36 | func (noopServerMetrics) AddTrafficIn(string, string, int64) {} 37 | func (noopServerMetrics) AddTrafficOut(string, string, int64) {} 38 | -------------------------------------------------------------------------------- /server/proxy/https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | "strings" 20 | 21 | v1 "github.com/xx/xxx/pkg/config/v1" 22 | "github.com/xx/xxx/pkg/util/util" 23 | "github.com/xx/xxx/pkg/util/vhost" 24 | ) 25 | 26 | func init() { 27 | RegisterProxyFactory(reflect.TypeOf(&v1.HTTPSProxyConfig{}), NewHTTPSProxy) 28 | } 29 | 30 | type HTTPSProxy struct { 31 | *BaseProxy 32 | cfg *v1.HTTPSProxyConfig 33 | } 34 | 35 | func NewHTTPSProxy(baseProxy *BaseProxy) Proxy { 36 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.HTTPSProxyConfig) 37 | if !ok { 38 | return nil 39 | } 40 | return &HTTPSProxy{ 41 | BaseProxy: baseProxy, 42 | cfg: unwrapped, 43 | } 44 | } 45 | 46 | func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) { 47 | xl := pxy.xl 48 | routeConfig := &vhost.RouteConfig{} 49 | 50 | defer func() { 51 | if err != nil { 52 | pxy.Close() 53 | } 54 | }() 55 | addrs := make([]string, 0) 56 | for _, domain := range pxy.cfg.CustomDomains { 57 | if domain == "" { 58 | continue 59 | } 60 | 61 | routeConfig.Domain = domain 62 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig) 63 | if errRet != nil { 64 | err = errRet 65 | return 66 | } 67 | xl.Infof("https proxy listen for host [%s]", routeConfig.Domain) 68 | pxy.listeners = append(pxy.listeners, l) 69 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) 70 | } 71 | 72 | if pxy.cfg.SubDomain != "" { 73 | routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost 74 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig) 75 | if errRet != nil { 76 | err = errRet 77 | return 78 | } 79 | xl.Infof("https proxy listen for host [%s]", routeConfig.Domain) 80 | pxy.listeners = append(pxy.listeners, l) 81 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) 82 | } 83 | 84 | pxy.startCommonTCPListenersHandler() 85 | remoteAddr = strings.Join(addrs, ",") 86 | return 87 | } 88 | 89 | func (pxy *HTTPSProxy) Close() { 90 | pxy.BaseProxy.Close() 91 | } 92 | -------------------------------------------------------------------------------- /server/proxy/stcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | 20 | v1 "github.com/xx/xxx/pkg/config/v1" 21 | ) 22 | 23 | func init() { 24 | RegisterProxyFactory(reflect.TypeOf(&v1.STCPProxyConfig{}), NewSTCPProxy) 25 | } 26 | 27 | type STCPProxy struct { 28 | *BaseProxy 29 | cfg *v1.STCPProxyConfig 30 | } 31 | 32 | func NewSTCPProxy(baseProxy *BaseProxy) Proxy { 33 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.STCPProxyConfig) 34 | if !ok { 35 | return nil 36 | } 37 | return &STCPProxy{ 38 | BaseProxy: baseProxy, 39 | cfg: unwrapped, 40 | } 41 | } 42 | 43 | func (pxy *STCPProxy) Run() (remoteAddr string, err error) { 44 | xl := pxy.xl 45 | allowUsers := pxy.cfg.AllowUsers 46 | // if allowUsers is empty, only allow same user from proxy 47 | if len(allowUsers) == 0 { 48 | allowUsers = []string{pxy.GetUserInfo().User} 49 | } 50 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers) 51 | if errRet != nil { 52 | err = errRet 53 | return 54 | } 55 | pxy.listeners = append(pxy.listeners, listener) 56 | xl.Infof("stcp proxy custom listen success") 57 | 58 | pxy.startCommonTCPListenersHandler() 59 | return 60 | } 61 | 62 | func (pxy *STCPProxy) Close() { 63 | pxy.BaseProxy.Close() 64 | pxy.rc.VisitorManager.CloseListener(pxy.GetName()) 65 | } 66 | -------------------------------------------------------------------------------- /server/proxy/sudp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | 20 | v1 "github.com/xx/xxx/pkg/config/v1" 21 | ) 22 | 23 | func init() { 24 | RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy) 25 | } 26 | 27 | type SUDPProxy struct { 28 | *BaseProxy 29 | cfg *v1.SUDPProxyConfig 30 | } 31 | 32 | func NewSUDPProxy(baseProxy *BaseProxy) Proxy { 33 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.SUDPProxyConfig) 34 | if !ok { 35 | return nil 36 | } 37 | return &SUDPProxy{ 38 | BaseProxy: baseProxy, 39 | cfg: unwrapped, 40 | } 41 | } 42 | 43 | func (pxy *SUDPProxy) Run() (remoteAddr string, err error) { 44 | xl := pxy.xl 45 | allowUsers := pxy.cfg.AllowUsers 46 | // if allowUsers is empty, only allow same user from proxy 47 | if len(allowUsers) == 0 { 48 | allowUsers = []string{pxy.GetUserInfo().User} 49 | } 50 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers) 51 | if errRet != nil { 52 | err = errRet 53 | return 54 | } 55 | pxy.listeners = append(pxy.listeners, listener) 56 | xl.Infof("sudp proxy custom listen success") 57 | 58 | pxy.startCommonTCPListenersHandler() 59 | return 60 | } 61 | 62 | func (pxy *SUDPProxy) Close() { 63 | pxy.BaseProxy.Close() 64 | pxy.rc.VisitorManager.CloseListener(pxy.GetName()) 65 | } 66 | -------------------------------------------------------------------------------- /server/proxy/tcpmux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 guylewin, guy@lewin.co.il 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "reflect" 21 | "strings" 22 | 23 | v1 "github.com/xx/xxx/pkg/config/v1" 24 | "github.com/xx/xxx/pkg/util/util" 25 | "github.com/xx/xxx/pkg/util/vhost" 26 | ) 27 | 28 | func init() { 29 | RegisterProxyFactory(reflect.TypeOf(&v1.TCPMuxProxyConfig{}), NewTCPMuxProxy) 30 | } 31 | 32 | type TCPMuxProxy struct { 33 | *BaseProxy 34 | cfg *v1.TCPMuxProxyConfig 35 | } 36 | 37 | func NewTCPMuxProxy(baseProxy *BaseProxy) Proxy { 38 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.TCPMuxProxyConfig) 39 | if !ok { 40 | return nil 41 | } 42 | return &TCPMuxProxy{ 43 | BaseProxy: baseProxy, 44 | cfg: unwrapped, 45 | } 46 | } 47 | 48 | func (pxy *TCPMuxProxy) httpConnectListen( 49 | domain, routeByHTTPUser, httpUser, httpPwd string, addrs []string) ([]string, error, 50 | ) { 51 | var l net.Listener 52 | var err error 53 | routeConfig := &vhost.RouteConfig{ 54 | Domain: domain, 55 | RouteByHTTPUser: routeByHTTPUser, 56 | Username: httpUser, 57 | Password: httpPwd, 58 | } 59 | if pxy.cfg.LoadBalancer.Group != "" { 60 | l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, 61 | pxy.cfg.LoadBalancer.Group, pxy.cfg.LoadBalancer.GroupKey, *routeConfig) 62 | } else { 63 | l, err = pxy.rc.TCPMuxHTTPConnectMuxer.Listen(pxy.ctx, routeConfig) 64 | } 65 | if err != nil { 66 | return nil, err 67 | } 68 | pxy.xl.Infof("tcpmux httpconnect multiplexer listens for host [%s], group [%s] routeByHTTPUser [%s]", 69 | domain, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser) 70 | pxy.listeners = append(pxy.listeners, l) 71 | return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TCPMuxHTTPConnectPort)), nil 72 | } 73 | 74 | func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) { 75 | addrs := make([]string, 0) 76 | for _, domain := range pxy.cfg.CustomDomains { 77 | if domain == "" { 78 | continue 79 | } 80 | 81 | addrs, err = pxy.httpConnectListen(domain, pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPassword, addrs) 82 | if err != nil { 83 | return "", err 84 | } 85 | } 86 | 87 | if pxy.cfg.SubDomain != "" { 88 | addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost, 89 | pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPassword, addrs) 90 | if err != nil { 91 | return "", err 92 | } 93 | } 94 | 95 | pxy.startCommonTCPListenersHandler() 96 | remoteAddr = strings.Join(addrs, ",") 97 | return remoteAddr, err 98 | } 99 | 100 | func (pxy *TCPMuxProxy) Run() (remoteAddr string, err error) { 101 | switch v1.TCPMultiplexerType(pxy.cfg.Multiplexer) { 102 | case v1.TCPMultiplexerHTTPConnect: 103 | remoteAddr, err = pxy.httpConnectRun() 104 | default: 105 | err = fmt.Errorf("unknown multiplexer [%s]", pxy.cfg.Multiplexer) 106 | } 107 | 108 | if err != nil { 109 | pxy.Close() 110 | } 111 | return remoteAddr, err 112 | } 113 | 114 | func (pxy *TCPMuxProxy) Close() { 115 | pxy.BaseProxy.Close() 116 | } 117 | -------------------------------------------------------------------------------- /server/proxy/xtcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | 21 | "github.com/fatedier/golib/errors" 22 | 23 | v1 "github.com/xx/xxx/pkg/config/v1" 24 | "github.com/xx/xxx/pkg/msg" 25 | ) 26 | 27 | func init() { 28 | RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy) 29 | } 30 | 31 | type XTCPProxy struct { 32 | *BaseProxy 33 | cfg *v1.XTCPProxyConfig 34 | 35 | closeCh chan struct{} 36 | } 37 | 38 | func NewXTCPProxy(baseProxy *BaseProxy) Proxy { 39 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.XTCPProxyConfig) 40 | if !ok { 41 | return nil 42 | } 43 | return &XTCPProxy{ 44 | BaseProxy: baseProxy, 45 | cfg: unwrapped, 46 | } 47 | } 48 | 49 | func (pxy *XTCPProxy) Run() (remoteAddr string, err error) { 50 | xl := pxy.xl 51 | 52 | if pxy.rc.NatHoleController == nil { 53 | err = fmt.Errorf("xtcp is not supported in frps") 54 | return 55 | } 56 | allowUsers := pxy.cfg.AllowUsers 57 | // if allowUsers is empty, only allow same user from proxy 58 | if len(allowUsers) == 0 { 59 | allowUsers = []string{pxy.GetUserInfo().User} 60 | } 61 | sidCh, err := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Secretkey, allowUsers) 62 | if err != nil { 63 | return "", err 64 | } 65 | go func() { 66 | for { 67 | select { 68 | case <-pxy.closeCh: 69 | return 70 | case sid := <-sidCh: 71 | workConn, errRet := pxy.GetWorkConnFromPool(nil, nil) 72 | if errRet != nil { 73 | continue 74 | } 75 | m := &msg.NatHoleSid{ 76 | Sid: sid, 77 | } 78 | errRet = msg.WriteMsg(workConn, m) 79 | if errRet != nil { 80 | xl.Warnf("write nat hole sid package error, %v", errRet) 81 | } 82 | workConn.Close() 83 | } 84 | } 85 | }() 86 | return 87 | } 88 | 89 | func (pxy *XTCPProxy) Close() { 90 | pxy.BaseProxy.Close() 91 | pxy.rc.NatHoleController.CloseClient(pxy.GetName()) 92 | _ = errors.PanicToError(func() { 93 | close(pxy.closeCh) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /server/visitor/visitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package visitor 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "net" 21 | "slices" 22 | "sync" 23 | 24 | libio "github.com/fatedier/golib/io" 25 | 26 | netpkg "github.com/xx/xxx/pkg/util/net" 27 | "github.com/xx/xxx/pkg/util/util" 28 | ) 29 | 30 | type listenerBundle struct { 31 | l *netpkg.InternalListener 32 | sk string 33 | allowUsers []string 34 | } 35 | 36 | // Manager for visitor listeners. 37 | type Manager struct { 38 | listeners map[string]*listenerBundle 39 | 40 | mu sync.RWMutex 41 | } 42 | 43 | func NewManager() *Manager { 44 | return &Manager{ 45 | listeners: make(map[string]*listenerBundle), 46 | } 47 | } 48 | 49 | func (vm *Manager) Listen(name string, sk string, allowUsers []string) (*netpkg.InternalListener, error) { 50 | vm.mu.Lock() 51 | defer vm.mu.Unlock() 52 | 53 | if _, ok := vm.listeners[name]; ok { 54 | return nil, fmt.Errorf("custom listener for [%s] is repeated", name) 55 | } 56 | 57 | l := netpkg.NewInternalListener() 58 | vm.listeners[name] = &listenerBundle{ 59 | l: l, 60 | sk: sk, 61 | allowUsers: allowUsers, 62 | } 63 | return l, nil 64 | } 65 | 66 | func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string, 67 | useEncryption bool, useCompression bool, visitorUser string, 68 | ) (err error) { 69 | vm.mu.RLock() 70 | defer vm.mu.RUnlock() 71 | 72 | if l, ok := vm.listeners[name]; ok { 73 | if util.GetAuthKey(l.sk, timestamp) != signKey { 74 | err = fmt.Errorf("visitor connection of [%s] auth failed", name) 75 | return 76 | } 77 | 78 | if !slices.Contains(l.allowUsers, visitorUser) && !slices.Contains(l.allowUsers, "*") { 79 | err = fmt.Errorf("visitor connection of [%s] user [%s] not allowed", name, visitorUser) 80 | return 81 | } 82 | 83 | var rwc io.ReadWriteCloser = conn 84 | if useEncryption { 85 | if rwc, err = libio.WithEncryption(rwc, []byte(l.sk)); err != nil { 86 | err = fmt.Errorf("create encryption connection failed: %v", err) 87 | return 88 | } 89 | } 90 | if useCompression { 91 | rwc = libio.WithCompression(rwc) 92 | } 93 | err = l.l.PutConn(netpkg.WrapReadWriteCloserToConn(rwc, conn)) 94 | } else { 95 | err = fmt.Errorf("custom listener for [%s] doesn't exist", name) 96 | return 97 | } 98 | return 99 | } 100 | 101 | func (vm *Manager) CloseListener(name string) { 102 | vm.mu.Lock() 103 | defer vm.mu.Unlock() 104 | 105 | delete(vm.listeners, name) 106 | } 107 | --------------------------------------------------------------------------------