├── .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 | 
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 | 
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 | 
57 |
58 | 将加密后的文本上传到oss上,注意权限公共可读
59 | 
60 |
61 |
62 | ### 配置frps.toml
63 | > 配置frps.toml,其中webhook为钉钉webhook通知;
64 | > webhookFlag为true时,将开启钉钉通知
65 |
66 | 
67 |
68 |
69 | ### 编译frpc客户端
70 | > 执行./frps encrypt命令时,已自动把相关frpc配置加密信息填充到frpc模板中,其中`root@pts/0`为启动命令参数,可自行修改
71 |
72 | 
73 |
74 | > 这里只需要执行相应客户端编译命令即可, 请查看Makefile文件内容
75 |
76 | > 执行 `make frpc-darwin`命令,将在bin目录生成frpc客户端
77 |
78 | > 若需要混淆编译,请使用`garble`进行编译
79 |
80 | ### 运行效果图
81 |
82 | 可多次执行frpc客户端,钉钉通知随机生成的socks5账户密码及端口
83 | 
84 |
85 | 
86 |
87 |
88 | ### oss域前置抓包效果
89 | 如果直接填写oss域名, https抓包将会在SNI显示oss域名
90 |
91 | 
92 |
93 | 如果通过配置 host访问,将无法得到oss域名,还有一个好处是服务器若没配置dns,直接写oss域名将无法解析
94 |
95 | 
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 |
--------------------------------------------------------------------------------