├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── Makefile.cross-compiles ├── README.md ├── Release.md ├── assets ├── assets.go ├── frpc │ ├── static │ │ ├── 535877f50039c0cb49a6196a5b7517cd.woff │ │ ├── 732389ded34cb9c52dd88271f1345af9.ttf │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── manifest.js │ │ └── vendor.js │ └── statik │ │ └── statik.go └── frps │ ├── static │ ├── 535877f50039c0cb49a6196a5b7517cd.woff │ ├── 732389ded34cb9c52dd88271f1345af9.ttf │ ├── favicon.ico │ ├── index.html │ ├── manifest.js │ └── vendor.js │ └── statik │ └── statik.go ├── client ├── admin.go ├── admin_api.go ├── control.go ├── event │ └── event.go ├── health │ └── health.go ├── proxy │ ├── proxy.go │ ├── proxy_manager.go │ └── proxy_wrapper.go ├── service.go ├── visitor.go └── visitor_manager.go ├── cmd ├── frpc │ ├── main.go │ └── sub │ │ ├── http.go │ │ ├── https.go │ │ ├── reload.go │ │ ├── root.go │ │ ├── status.go │ │ ├── stcp.go │ │ ├── sudp.go │ │ ├── tcp.go │ │ ├── tcpmux.go │ │ ├── udp.go │ │ ├── verify.go │ │ └── xtcp.go └── frps │ ├── main.go │ ├── root.go │ └── verify.go ├── conf ├── frpc.ini ├── frpc_full.ini ├── frps.ini ├── frps_full.ini └── systemd │ ├── frpc.service │ ├── frpc@.service │ ├── frps.service │ └── frps@.service ├── doc ├── pic │ ├── architecture.png │ ├── dashboard.png │ ├── donate-alipay.png │ ├── donate-wechatpay.png │ └── zsxq.jpg ├── server_plugin.md └── server_plugin_zh.md ├── dockerfiles ├── Dockerfile-for-frpc └── Dockerfile-for-frps ├── go.mod ├── go.sum ├── hack └── run-e2e.sh ├── package.sh ├── pkg ├── auth │ ├── auth.go │ ├── oidc.go │ └── token.go ├── config │ ├── README.md │ ├── client.go │ ├── client_test.go │ ├── parse.go │ ├── proxy.go │ ├── proxy_test.go │ ├── server.go │ ├── server_test.go │ ├── types.go │ ├── types_test.go │ ├── utils.go │ ├── value.go │ ├── visitor.go │ └── visitor_test.go ├── consts │ └── consts.go ├── errors │ └── errors.go ├── metrics │ ├── aggregate │ │ └── server.go │ ├── mem │ │ ├── server.go │ │ └── types.go │ ├── metrics.go │ └── prometheus │ │ └── server.go ├── msg │ ├── ctl.go │ └── msg.go ├── nathole │ └── nathole.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 ├── transport │ └── tls.go └── util │ ├── limit │ ├── reader.go │ └── writer.go │ ├── log │ └── log.go │ ├── metric │ ├── counter.go │ ├── counter_test.go │ ├── date_counter.go │ ├── date_counter_test.go │ └── metrics.go │ ├── net │ ├── conn.go │ ├── http.go │ ├── kcp.go │ ├── listener.go │ ├── tls.go │ ├── udp.go │ └── websocket.go │ ├── tcpmux │ └── httpconnect.go │ ├── util │ ├── http.go │ ├── util.go │ └── util_test.go │ ├── version │ ├── version.go │ └── version_test.go │ ├── vhost │ ├── http.go │ ├── https.go │ ├── resource.go │ ├── reverseproxy.go │ ├── router.go │ └── vhost.go │ └── xlog │ ├── ctx.go │ └── xlog.go ├── server ├── control.go ├── controller │ └── resource.go ├── dashboard.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 ├── test └── e2e │ ├── basic │ ├── basic.go │ ├── client.go │ ├── client_server.go │ ├── cmd.go │ ├── config.go │ ├── http.go │ └── server.go │ ├── e2e.go │ ├── e2e_test.go │ ├── examples.go │ ├── features │ ├── bandwidth_limit.go │ ├── chaos.go │ ├── group.go │ └── real_ip.go │ ├── framework │ ├── cleanup.go │ ├── client.go │ ├── consts │ │ └── consts.go │ ├── expect.go │ ├── framework.go │ ├── ginkgowrapper │ │ └── wrapper.go │ ├── log.go │ ├── mockservers.go │ ├── process.go │ ├── request.go │ ├── test_context.go │ └── util.go │ ├── mock │ └── server │ │ ├── httpserver │ │ └── server.go │ │ ├── interface.go │ │ └── streamserver │ │ └── server.go │ ├── pkg │ ├── port │ │ ├── port.go │ │ └── util.go │ ├── process │ │ └── process.go │ ├── request │ │ └── request.go │ ├── rpc │ │ └── rpc.go │ ├── sdk │ │ └── client │ │ │ └── client.go │ └── utils │ │ └── utils.go │ ├── plugin │ └── client_plugins.go │ └── suites.go └── web ├── frpc ├── .babelrc ├── .gitignore ├── Makefile ├── package.json ├── postcss.config.js ├── src │ ├── App.vue │ ├── assets │ │ └── favicon.ico │ ├── components │ │ ├── Configure.vue │ │ └── Overview.vue │ ├── index.html │ ├── main.js │ ├── router │ │ └── index.js │ └── utils │ │ ├── less │ │ └── custom.less │ │ └── status.js ├── webpack.config.js └── yarn.lock └── frps ├── .babelrc ├── .gitignore ├── Makefile ├── package.json ├── postcss.config.js ├── src ├── App.vue ├── assets │ └── favicon.ico ├── components │ ├── Overview.vue │ ├── ProxiesHttp.vue │ ├── ProxiesHttps.vue │ ├── ProxiesStcp.vue │ ├── ProxiesSudp.vue │ ├── ProxiesTcp.vue │ ├── ProxiesUdp.vue │ └── Traffic.vue ├── index.html ├── main.js ├── router │ └── index.js └── utils │ ├── chart.js │ ├── less │ └── custom.less │ └── proxy.js ├── webpack.config.js └── yarn.lock /.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 | bin/ 28 | packages/ 29 | release/ 30 | test/bin/ 31 | vendor/ 32 | dist/ 33 | .idea/ 34 | 35 | # Cache 36 | *.swp 37 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - skip: true 3 | checksum: 4 | name_template: 'checksums.txt' 5 | release: 6 | # Same as for github 7 | # Note: it can only be one: either github, gitlab or gitea 8 | github: 9 | owner: fatedier 10 | name: frp 11 | 12 | draft: false 13 | 14 | # You can add extra pre-existing files to the release. 15 | # The filename on the release will be the last part of the path (base). If 16 | # another file with the same name exists, the latest one found will be used. 17 | # Defaults to empty. 18 | extra_files: 19 | - glob: ./release/packages/* 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PATH := $(GOPATH)/bin:$(PATH) 2 | export GO111MODULE=on 3 | LDFLAGS := -s -w 4 | 5 | all: fmt build 6 | 7 | build: frps frpc 8 | 9 | # compile assets into binary file 10 | file: 11 | rm -rf ./assets/frps/static/* 12 | rm -rf ./assets/frpc/static/* 13 | cp -rf ./web/frps/dist/* ./assets/frps/static 14 | cp -rf ./web/frpc/dist/* ./assets/frpc/static 15 | rm -rf ./assets/frps/statik 16 | rm -rf ./assets/frpc/statik 17 | go generate ./assets/... 18 | 19 | fmt: 20 | go fmt ./... 21 | 22 | frps: 23 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps 24 | 25 | frpc: 26 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc 27 | 28 | test: gotest 29 | 30 | gotest: 31 | go test -v --cover ./assets/... 32 | go test -v --cover ./cmd/... 33 | go test -v --cover ./client/... 34 | go test -v --cover ./server/... 35 | go test -v --cover ./pkg/... 36 | 37 | e2e: 38 | ./hack/run-e2e.sh 39 | 40 | e2e-trace: 41 | DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh 42 | 43 | alltest: gotest e2e 44 | 45 | clean: 46 | rm -f ./bin/frpc 47 | rm -f ./bin/frps 48 | -------------------------------------------------------------------------------- /Makefile.cross-compiles: -------------------------------------------------------------------------------- 1 | export PATH := $(GOPATH)/bin:$(PATH) 2 | export GO111MODULE=on 3 | LDFLAGS := -s -w 4 | 5 | os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat 6 | 7 | all: build 8 | 9 | build: app 10 | 11 | app: 12 | @$(foreach n, $(os-archs),\ 13 | os=$(shell echo "$(n)" | cut -d : -f 1);\ 14 | arch=$(shell echo "$(n)" | cut -d : -f 2);\ 15 | gomips=$(shell echo "$(n)" | cut -d : -f 3);\ 16 | target_suffix=$${os}_$${arch};\ 17 | echo "Build $${os}-$${arch}...";\ 18 | env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\ 19 | env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\ 20 | echo "Build $${os}-$${arch} done";\ 21 | ) 22 | @mv ./release/frpc_windows_386 ./release/frpc_windows_386.exe 23 | @mv ./release/frps_windows_386 ./release/frps_windows_386.exe 24 | @mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe 25 | @mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FRP修改版 2 | 3 | 修改功能,基于0.37修改 4 | 5 | 参数化 6 | ``` 7 | -c, --config string config file of frpc (default "./frpc.ini") 8 | -h, --help help for frpc 9 | -x, --proxyname string proxyname (default "http_proxy") 10 | -o, --ptoken string privilege_token (default "pte222") 11 | -r, --rport string socks 5端口 12 | -t, --server_addr string FRPS服务器IP 13 | -p, --server_port string FRPS服务器端口 14 | -k, --socksPwd string Socks5认证密码(default "Rapid-7") 15 | -u, --socksUser string Socks5认证密码 (default "Rapid-7") 16 | -v, --version version of frpc 17 | ``` 18 | -------------------------------------------------------------------------------- /Release.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NS-Sp4ce/Frp_modify/21c02065abe7e97a26387bbc6cceed79f8850caf/Release.md -------------------------------------------------------------------------------- /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 | //go:generate statik -src=./frps/static -dest=./frps 18 | //go:generate statik -src=./frpc/static -dest=./frpc 19 | //go:generate go fmt ./frps/statik/statik.go 20 | //go:generate go fmt ./frpc/statik/statik.go 21 | 22 | import ( 23 | "io/ioutil" 24 | "net/http" 25 | "os" 26 | "path" 27 | 28 | "github.com/rakyll/statik/fs" 29 | ) 30 | 31 | var ( 32 | // store static files in memory by statik 33 | FileSystem http.FileSystem 34 | 35 | // if prefix is not empty, we get file content from disk 36 | prefixPath string 37 | ) 38 | 39 | // if path is empty, load assets in memory 40 | // or set FileSystem using disk files 41 | func Load(path string) (err error) { 42 | prefixPath = path 43 | if prefixPath != "" { 44 | FileSystem = http.Dir(prefixPath) 45 | return nil 46 | } else { 47 | FileSystem, err = fs.New() 48 | } 49 | return err 50 | } 51 | 52 | func ReadFile(file string) (content string, err error) { 53 | if prefixPath == "" { 54 | file, err := FileSystem.Open(path.Join("/", file)) 55 | if err != nil { 56 | return content, err 57 | } 58 | defer file.Close() 59 | buf, err := ioutil.ReadAll(file) 60 | if err != nil { 61 | return content, err 62 | } 63 | content = string(buf) 64 | } else { 65 | file, err := os.Open(path.Join(prefixPath, file)) 66 | if err != nil { 67 | return content, err 68 | } 69 | defer file.Close() 70 | buf, err := ioutil.ReadAll(file) 71 | if err != nil { 72 | return content, err 73 | } 74 | content = string(buf) 75 | } 76 | return content, err 77 | } 78 | -------------------------------------------------------------------------------- /assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NS-Sp4ce/Frp_modify/21c02065abe7e97a26387bbc6cceed79f8850caf/assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff -------------------------------------------------------------------------------- /assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NS-Sp4ce/Frp_modify/21c02065abe7e97a26387bbc6cceed79f8850caf/assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf -------------------------------------------------------------------------------- /assets/frpc/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NS-Sp4ce/Frp_modify/21c02065abe7e97a26387bbc6cceed79f8850caf/assets/frpc/static/favicon.ico -------------------------------------------------------------------------------- /assets/frpc/static/index.html: -------------------------------------------------------------------------------- 1 |
Sorry, the page you are looking for is currently unavailable.
46 | Please try again later.
The server is powered by frp.
48 |Faithfully yours, frp.
49 | 50 | 51 | ` 52 | ) 53 | 54 | func getNotFoundPageContent() []byte { 55 | var ( 56 | buf []byte 57 | err error 58 | ) 59 | if NotFoundPagePath != "" { 60 | buf, err = ioutil.ReadFile(NotFoundPagePath) 61 | if err != nil { 62 | frpLog.Warn("read custom 404 page error: %v", err) 63 | buf = []byte(NotFound) 64 | } 65 | } else { 66 | buf = []byte(NotFound) 67 | } 68 | return buf 69 | } 70 | 71 | func notFoundResponse() *http.Response { 72 | header := make(http.Header) 73 | header.Set("server", "frp/"+version.Full()) 74 | header.Set("Content-Type", "text/html") 75 | 76 | res := &http.Response{ 77 | Status: "Not Found", 78 | StatusCode: 404, 79 | Proto: "HTTP/1.0", 80 | ProtoMajor: 1, 81 | ProtoMinor: 0, 82 | Header: header, 83 | Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())), 84 | } 85 | return res 86 | } 87 | 88 | func noAuthResponse() *http.Response { 89 | header := make(map[string][]string) 90 | header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`} 91 | res := &http.Response{ 92 | Status: "401 Not authorized", 93 | StatusCode: 401, 94 | Proto: "HTTP/1.1", 95 | ProtoMajor: 1, 96 | ProtoMinor: 1, 97 | Header: header, 98 | } 99 | return res 100 | } 101 | -------------------------------------------------------------------------------- /pkg/util/vhost/router.go: -------------------------------------------------------------------------------- 1 | package vhost 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | ErrRouterConfigConflict = errors.New("router config conflict") 12 | ) 13 | 14 | type Routers struct { 15 | RouterByDomain map[string][]*Router 16 | mutex sync.RWMutex 17 | } 18 | 19 | type Router struct { 20 | domain string 21 | location string 22 | 23 | payload interface{} 24 | } 25 | 26 | func NewRouters() *Routers { 27 | return &Routers{ 28 | RouterByDomain: make(map[string][]*Router), 29 | } 30 | } 31 | 32 | func (r *Routers) Add(domain, location string, payload interface{}) error { 33 | r.mutex.Lock() 34 | defer r.mutex.Unlock() 35 | 36 | if _, exist := r.exist(domain, location); exist { 37 | return ErrRouterConfigConflict 38 | } 39 | 40 | vrs, found := r.RouterByDomain[domain] 41 | if !found { 42 | vrs = make([]*Router, 0, 1) 43 | } 44 | 45 | vr := &Router{ 46 | domain: domain, 47 | location: location, 48 | payload: payload, 49 | } 50 | vrs = append(vrs, vr) 51 | 52 | sort.Sort(sort.Reverse(ByLocation(vrs))) 53 | r.RouterByDomain[domain] = vrs 54 | return nil 55 | } 56 | 57 | func (r *Routers) Del(domain, location string) { 58 | r.mutex.Lock() 59 | defer r.mutex.Unlock() 60 | 61 | vrs, found := r.RouterByDomain[domain] 62 | if !found { 63 | return 64 | } 65 | newVrs := make([]*Router, 0) 66 | for _, vr := range vrs { 67 | if vr.location != location { 68 | newVrs = append(newVrs, vr) 69 | } 70 | } 71 | r.RouterByDomain[domain] = newVrs 72 | } 73 | 74 | func (r *Routers) Get(host, path string) (vr *Router, exist bool) { 75 | r.mutex.RLock() 76 | defer r.mutex.RUnlock() 77 | 78 | vrs, found := r.RouterByDomain[host] 79 | if !found { 80 | return 81 | } 82 | 83 | // can't support load balance, will to do 84 | for _, vr = range vrs { 85 | if strings.HasPrefix(path, vr.location) { 86 | return vr, true 87 | } 88 | } 89 | 90 | return 91 | } 92 | 93 | func (r *Routers) exist(host, path string) (vr *Router, exist bool) { 94 | vrs, found := r.RouterByDomain[host] 95 | if !found { 96 | return 97 | } 98 | 99 | for _, vr = range vrs { 100 | if path == vr.location { 101 | return vr, true 102 | } 103 | } 104 | 105 | return 106 | } 107 | 108 | // sort by location 109 | type ByLocation []*Router 110 | 111 | func (a ByLocation) Len() int { 112 | return len(a) 113 | } 114 | func (a ByLocation) Swap(i, j int) { 115 | a[i], a[j] = a[j], a[i] 116 | } 117 | func (a ByLocation) Less(i, j int) bool { 118 | return strings.Compare(a[i].location, a[j].location) < 0 119 | } 120 | -------------------------------------------------------------------------------- /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 | "github.com/fatedier/frp/pkg/util/log" 19 | ) 20 | 21 | // Logger is not thread safety for operations on prefix 22 | type Logger struct { 23 | prefixes []string 24 | 25 | prefixString string 26 | } 27 | 28 | func New() *Logger { 29 | return &Logger{ 30 | prefixes: make([]string, 0), 31 | } 32 | } 33 | 34 | func (l *Logger) ResetPrefixes() (old []string) { 35 | old = l.prefixes 36 | l.prefixes = make([]string, 0) 37 | l.prefixString = "" 38 | return 39 | } 40 | 41 | func (l *Logger) AppendPrefix(prefix string) *Logger { 42 | l.prefixes = append(l.prefixes, prefix) 43 | l.prefixString += "[" + prefix + "] " 44 | return l 45 | } 46 | 47 | func (l *Logger) Spawn() *Logger { 48 | nl := New() 49 | for _, v := range l.prefixes { 50 | nl.AppendPrefix(v) 51 | } 52 | return nl 53 | } 54 | 55 | func (l *Logger) Error(format string, v ...interface{}) { 56 | log.Log.Error(l.prefixString+format, v...) 57 | } 58 | 59 | func (l *Logger) Warn(format string, v ...interface{}) { 60 | log.Log.Warn(l.prefixString+format, v...) 61 | } 62 | 63 | func (l *Logger) Info(format string, v ...interface{}) { 64 | log.Log.Info(l.prefixString+format, v...) 65 | } 66 | 67 | func (l *Logger) Debug(format string, v ...interface{}) { 68 | log.Log.Debug(l.prefixString+format, v...) 69 | } 70 | 71 | func (l *Logger) Trace(format string, v ...interface{}) { 72 | log.Log.Trace(l.prefixString+format, v...) 73 | } 74 | -------------------------------------------------------------------------------- /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/fatedier/frp/pkg/nathole" 19 | plugin "github.com/fatedier/frp/pkg/plugin/server" 20 | "github.com/fatedier/frp/pkg/util/tcpmux" 21 | "github.com/fatedier/frp/pkg/util/vhost" 22 | "github.com/fatedier/frp/server/group" 23 | "github.com/fatedier/frp/server/ports" 24 | "github.com/fatedier/frp/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/dashboard.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 server 16 | 17 | import ( 18 | "net" 19 | "net/http" 20 | "time" 21 | 22 | "github.com/fatedier/frp/assets" 23 | frpNet "github.com/fatedier/frp/pkg/util/net" 24 | 25 | "github.com/gorilla/mux" 26 | "github.com/prometheus/client_golang/prometheus/promhttp" 27 | ) 28 | 29 | var ( 30 | httpServerReadTimeout = 10 * time.Second 31 | httpServerWriteTimeout = 10 * time.Second 32 | ) 33 | 34 | func (svr *Service) RunDashboardServer(address string) (err error) { 35 | // url router 36 | router := mux.NewRouter() 37 | 38 | user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd 39 | router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware) 40 | 41 | // metrics 42 | if svr.cfg.EnablePrometheus { 43 | router.Handle("/metrics", promhttp.Handler()) 44 | } 45 | 46 | // api, see dashboard_api.go 47 | router.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET") 48 | router.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET") 49 | router.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET") 50 | router.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET") 51 | 52 | // view 53 | router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET") 54 | router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET") 55 | 56 | router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 57 | http.Redirect(w, r, "/static/", http.StatusMovedPermanently) 58 | }) 59 | 60 | server := &http.Server{ 61 | Addr: address, 62 | Handler: router, 63 | ReadTimeout: httpServerReadTimeout, 64 | WriteTimeout: httpServerWriteTimeout, 65 | } 66 | if address == "" || address == ":" { 67 | address = ":http" 68 | } 69 | ln, err := net.Listen("tcp", address) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | go server.Serve(ln) 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /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(name string, proxyType string) {} 33 | func (noopServerMetrics) CloseProxy(name string, proxyType string) {} 34 | func (noopServerMetrics) OpenConnection(name string, proxyType string) {} 35 | func (noopServerMetrics) CloseConnection(name string, proxyType string) {} 36 | func (noopServerMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {} 37 | func (noopServerMetrics) AddTrafficOut(name string, proxyType string, trafficBytes 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 | "strings" 19 | 20 | "github.com/fatedier/frp/pkg/config" 21 | "github.com/fatedier/frp/pkg/util/util" 22 | "github.com/fatedier/frp/pkg/util/vhost" 23 | ) 24 | 25 | type HTTPSProxy struct { 26 | *BaseProxy 27 | cfg *config.HTTPSProxyConf 28 | } 29 | 30 | func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) { 31 | xl := pxy.xl 32 | routeConfig := &vhost.RouteConfig{} 33 | 34 | defer func() { 35 | if err != nil { 36 | pxy.Close() 37 | } 38 | }() 39 | addrs := make([]string, 0) 40 | for _, domain := range pxy.cfg.CustomDomains { 41 | if domain == "" { 42 | continue 43 | } 44 | 45 | routeConfig.Domain = domain 46 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig) 47 | if errRet != nil { 48 | err = errRet 49 | return 50 | } 51 | xl.Info("https proxy listen for host [%s]", routeConfig.Domain) 52 | pxy.listeners = append(pxy.listeners, l) 53 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) 54 | } 55 | 56 | if pxy.cfg.SubDomain != "" { 57 | routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost 58 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig) 59 | if errRet != nil { 60 | err = errRet 61 | return 62 | } 63 | xl.Info("https proxy listen for host [%s]", routeConfig.Domain) 64 | pxy.listeners = append(pxy.listeners, l) 65 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHTTPSPort))) 66 | } 67 | 68 | pxy.startListenHandler(pxy, HandleUserTCPConnection) 69 | remoteAddr = strings.Join(addrs, ",") 70 | return 71 | } 72 | 73 | func (pxy *HTTPSProxy) GetConf() config.ProxyConf { 74 | return pxy.cfg 75 | } 76 | 77 | func (pxy *HTTPSProxy) Close() { 78 | pxy.BaseProxy.Close() 79 | } 80 | -------------------------------------------------------------------------------- /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 | "github.com/fatedier/frp/pkg/config" 19 | ) 20 | 21 | type STCPProxy struct { 22 | *BaseProxy 23 | cfg *config.STCPProxyConf 24 | } 25 | 26 | func (pxy *STCPProxy) Run() (remoteAddr string, err error) { 27 | xl := pxy.xl 28 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) 29 | if errRet != nil { 30 | err = errRet 31 | return 32 | } 33 | pxy.listeners = append(pxy.listeners, listener) 34 | xl.Info("stcp proxy custom listen success") 35 | 36 | pxy.startListenHandler(pxy, HandleUserTCPConnection) 37 | return 38 | } 39 | 40 | func (pxy *STCPProxy) GetConf() config.ProxyConf { 41 | return pxy.cfg 42 | } 43 | 44 | func (pxy *STCPProxy) Close() { 45 | pxy.BaseProxy.Close() 46 | pxy.rc.VisitorManager.CloseListener(pxy.GetName()) 47 | } 48 | -------------------------------------------------------------------------------- /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 | "github.com/fatedier/frp/pkg/config" 19 | ) 20 | 21 | type SUDPProxy struct { 22 | *BaseProxy 23 | cfg *config.SUDPProxyConf 24 | } 25 | 26 | func (pxy *SUDPProxy) Run() (remoteAddr string, err error) { 27 | xl := pxy.xl 28 | 29 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) 30 | if errRet != nil { 31 | err = errRet 32 | return 33 | } 34 | pxy.listeners = append(pxy.listeners, listener) 35 | xl.Info("sudp proxy custom listen success") 36 | 37 | pxy.startListenHandler(pxy, HandleUserTCPConnection) 38 | return 39 | } 40 | 41 | func (pxy *SUDPProxy) GetConf() config.ProxyConf { 42 | return pxy.cfg 43 | } 44 | 45 | func (pxy *SUDPProxy) Close() { 46 | pxy.BaseProxy.Close() 47 | pxy.rc.VisitorManager.CloseListener(pxy.GetName()) 48 | } 49 | -------------------------------------------------------------------------------- /server/proxy/tcp.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 | "net" 20 | 21 | "github.com/fatedier/frp/pkg/config" 22 | ) 23 | 24 | type TCPProxy struct { 25 | *BaseProxy 26 | cfg *config.TCPProxyConf 27 | 28 | realPort int 29 | } 30 | 31 | func (pxy *TCPProxy) Run() (remoteAddr string, err error) { 32 | xl := pxy.xl 33 | if pxy.cfg.Group != "" { 34 | l, realPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort) 35 | if errRet != nil { 36 | err = errRet 37 | return 38 | } 39 | defer func() { 40 | if err != nil { 41 | l.Close() 42 | } 43 | }() 44 | pxy.realPort = realPort 45 | pxy.listeners = append(pxy.listeners, l) 46 | xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) 47 | } else { 48 | pxy.realPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) 49 | if err != nil { 50 | return 51 | } 52 | defer func() { 53 | if err != nil { 54 | pxy.rc.TCPPortManager.Release(pxy.realPort) 55 | } 56 | }() 57 | listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort)) 58 | if errRet != nil { 59 | err = errRet 60 | return 61 | } 62 | pxy.listeners = append(pxy.listeners, listener) 63 | xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) 64 | } 65 | 66 | pxy.cfg.RemotePort = pxy.realPort 67 | remoteAddr = fmt.Sprintf(":%d", pxy.realPort) 68 | pxy.startListenHandler(pxy, HandleUserTCPConnection) 69 | return 70 | } 71 | 72 | func (pxy *TCPProxy) GetConf() config.ProxyConf { 73 | return pxy.cfg 74 | } 75 | 76 | func (pxy *TCPProxy) Close() { 77 | pxy.BaseProxy.Close() 78 | if pxy.cfg.Group == "" { 79 | pxy.rc.TCPPortManager.Release(pxy.realPort) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /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 | "strings" 21 | 22 | "github.com/fatedier/frp/pkg/config" 23 | "github.com/fatedier/frp/pkg/consts" 24 | "github.com/fatedier/frp/pkg/util/util" 25 | "github.com/fatedier/frp/pkg/util/vhost" 26 | ) 27 | 28 | type TCPMuxProxy struct { 29 | *BaseProxy 30 | cfg *config.TCPMuxProxyConf 31 | } 32 | 33 | func (pxy *TCPMuxProxy) httpConnectListen(domain string, addrs []string) (_ []string, err error) { 34 | var l net.Listener 35 | if pxy.cfg.Group != "" { 36 | l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, domain) 37 | } else { 38 | routeConfig := &vhost.RouteConfig{ 39 | Domain: domain, 40 | } 41 | l, err = pxy.rc.TCPMuxHTTPConnectMuxer.Listen(pxy.ctx, routeConfig) 42 | } 43 | if err != nil { 44 | return nil, err 45 | } 46 | pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", domain) 47 | pxy.listeners = append(pxy.listeners, l) 48 | return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TCPMuxHTTPConnectPort)), nil 49 | } 50 | 51 | func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) { 52 | addrs := make([]string, 0) 53 | for _, domain := range pxy.cfg.CustomDomains { 54 | if domain == "" { 55 | continue 56 | } 57 | 58 | addrs, err = pxy.httpConnectListen(domain, addrs) 59 | if err != nil { 60 | return "", err 61 | } 62 | } 63 | 64 | if pxy.cfg.SubDomain != "" { 65 | addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost, addrs) 66 | if err != nil { 67 | return "", err 68 | } 69 | } 70 | 71 | pxy.startListenHandler(pxy, HandleUserTCPConnection) 72 | remoteAddr = strings.Join(addrs, ",") 73 | return remoteAddr, err 74 | } 75 | 76 | func (pxy *TCPMuxProxy) Run() (remoteAddr string, err error) { 77 | switch pxy.cfg.Multiplexer { 78 | case consts.HTTPConnectTCPMultiplexer: 79 | remoteAddr, err = pxy.httpConnectRun() 80 | default: 81 | err = fmt.Errorf("unknown multiplexer [%s]", pxy.cfg.Multiplexer) 82 | } 83 | 84 | if err != nil { 85 | pxy.Close() 86 | } 87 | return remoteAddr, err 88 | } 89 | 90 | func (pxy *TCPMuxProxy) GetConf() config.ProxyConf { 91 | return pxy.cfg 92 | } 93 | 94 | func (pxy *TCPMuxProxy) Close() { 95 | pxy.BaseProxy.Close() 96 | } 97 | -------------------------------------------------------------------------------- /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 | 20 | "github.com/fatedier/frp/pkg/config" 21 | "github.com/fatedier/frp/pkg/msg" 22 | 23 | "github.com/fatedier/golib/errors" 24 | ) 25 | 26 | type XTCPProxy struct { 27 | *BaseProxy 28 | cfg *config.XTCPProxyConf 29 | 30 | closeCh chan struct{} 31 | } 32 | 33 | func (pxy *XTCPProxy) Run() (remoteAddr string, err error) { 34 | xl := pxy.xl 35 | 36 | if pxy.rc.NatHoleController == nil { 37 | xl.Error("udp port for xtcp is not specified.") 38 | err = fmt.Errorf("xtcp is not supported in frps") 39 | return 40 | } 41 | sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk) 42 | go func() { 43 | for { 44 | select { 45 | case <-pxy.closeCh: 46 | break 47 | case sidRequest := <-sidCh: 48 | sr := sidRequest 49 | workConn, errRet := pxy.GetWorkConnFromPool(nil, nil) 50 | if errRet != nil { 51 | continue 52 | } 53 | m := &msg.NatHoleSid{ 54 | Sid: sr.Sid, 55 | } 56 | errRet = msg.WriteMsg(workConn, m) 57 | if errRet != nil { 58 | xl.Warn("write nat hole sid package error, %v", errRet) 59 | workConn.Close() 60 | break 61 | } 62 | 63 | go func() { 64 | raw, errRet := msg.ReadMsg(workConn) 65 | if errRet != nil { 66 | xl.Warn("read nat hole client ok package error: %v", errRet) 67 | workConn.Close() 68 | return 69 | } 70 | if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok { 71 | xl.Warn("read nat hole client ok package format error") 72 | workConn.Close() 73 | return 74 | } 75 | 76 | select { 77 | case sr.NotifyCh <- struct{}{}: 78 | default: 79 | } 80 | }() 81 | } 82 | } 83 | }() 84 | return 85 | } 86 | 87 | func (pxy *XTCPProxy) GetConf() config.ProxyConf { 88 | return pxy.cfg 89 | } 90 | 91 | func (pxy *XTCPProxy) Close() { 92 | pxy.BaseProxy.Close() 93 | pxy.rc.NatHoleController.CloseClient(pxy.GetName()) 94 | errors.PanicToError(func() { 95 | close(pxy.closeCh) 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /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 | "sync" 22 | 23 | frpNet "github.com/fatedier/frp/pkg/util/net" 24 | "github.com/fatedier/frp/pkg/util/util" 25 | 26 | frpIo "github.com/fatedier/golib/io" 27 | ) 28 | 29 | // Manager for visitor listeners. 30 | type Manager struct { 31 | visitorListeners map[string]*frpNet.CustomListener 32 | skMap map[string]string 33 | 34 | mu sync.RWMutex 35 | } 36 | 37 | func NewManager() *Manager { 38 | return &Manager{ 39 | visitorListeners: make(map[string]*frpNet.CustomListener), 40 | skMap: make(map[string]string), 41 | } 42 | } 43 | 44 | func (vm *Manager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) { 45 | vm.mu.Lock() 46 | defer vm.mu.Unlock() 47 | 48 | if _, ok := vm.visitorListeners[name]; ok { 49 | err = fmt.Errorf("custom listener for [%s] is repeated", name) 50 | return 51 | } 52 | 53 | l = frpNet.NewCustomListener() 54 | vm.visitorListeners[name] = l 55 | vm.skMap[name] = sk 56 | return 57 | } 58 | 59 | func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string, 60 | useEncryption bool, useCompression bool) (err error) { 61 | 62 | vm.mu.RLock() 63 | defer vm.mu.RUnlock() 64 | 65 | if l, ok := vm.visitorListeners[name]; ok { 66 | var sk string 67 | if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey { 68 | err = fmt.Errorf("visitor connection of [%s] auth failed", name) 69 | return 70 | } 71 | 72 | var rwc io.ReadWriteCloser = conn 73 | if useEncryption { 74 | if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil { 75 | err = fmt.Errorf("create encryption connection failed: %v", err) 76 | return 77 | } 78 | } 79 | if useCompression { 80 | rwc = frpIo.WithCompression(rwc) 81 | } 82 | err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn)) 83 | } else { 84 | err = fmt.Errorf("custom listener for [%s] doesn't exist", name) 85 | return 86 | } 87 | return 88 | } 89 | 90 | func (vm *Manager) CloseListener(name string) { 91 | vm.mu.Lock() 92 | defer vm.mu.Unlock() 93 | 94 | delete(vm.visitorListeners, name) 95 | delete(vm.skMap, name) 96 | } 97 | -------------------------------------------------------------------------------- /test/e2e/basic/client.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | "github.com/fatedier/frp/test/e2e/framework" 10 | "github.com/fatedier/frp/test/e2e/framework/consts" 11 | clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client" 12 | 13 | . "github.com/onsi/ginkgo" 14 | ) 15 | 16 | var _ = Describe("[Feature: ClientManage]", func() { 17 | f := framework.NewDefaultFramework() 18 | 19 | It("Update && Reload API", func() { 20 | serverConf := consts.DefaultServerConfig 21 | 22 | adminPort := f.AllocPort() 23 | 24 | p1Port := f.AllocPort() 25 | p2Port := f.AllocPort() 26 | p3Port := f.AllocPort() 27 | 28 | clientConf := consts.DefaultClientConfig + fmt.Sprintf(` 29 | admin_port = %d 30 | 31 | [p1] 32 | type = tcp 33 | local_port = {{ .%s }} 34 | remote_port = %d 35 | 36 | [p2] 37 | type = tcp 38 | local_port = {{ .%s }} 39 | remote_port = %d 40 | 41 | [p3] 42 | type = tcp 43 | local_port = {{ .%s }} 44 | remote_port = %d 45 | `, adminPort, 46 | framework.TCPEchoServerPort, p1Port, 47 | framework.TCPEchoServerPort, p2Port, 48 | framework.TCPEchoServerPort, p3Port) 49 | 50 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 51 | 52 | framework.NewRequestExpect(f).Port(p1Port).Ensure() 53 | framework.NewRequestExpect(f).Port(p2Port).Ensure() 54 | framework.NewRequestExpect(f).Port(p3Port).Ensure() 55 | 56 | client := clientsdk.New("127.0.0.1", adminPort) 57 | conf, err := client.GetConfig() 58 | framework.ExpectNoError(err) 59 | 60 | newP2Port := f.AllocPort() 61 | // change p2 port and remove p3 proxy 62 | newClientConf := strings.ReplaceAll(conf, strconv.Itoa(p2Port), strconv.Itoa(newP2Port)) 63 | p3Index := strings.Index(newClientConf, "[p3]") 64 | newClientConf = newClientConf[:p3Index] 65 | 66 | err = client.UpdateConfig(newClientConf) 67 | framework.ExpectNoError(err) 68 | 69 | err = client.Reload() 70 | framework.ExpectNoError(err) 71 | time.Sleep(time.Second) 72 | 73 | framework.NewRequestExpect(f).Port(p1Port).Explain("p1 port").Ensure() 74 | framework.NewRequestExpect(f).Port(p2Port).Explain("original p2 port").ExpectError(true).Ensure() 75 | framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure() 76 | framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure() 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /test/e2e/basic/config.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatedier/frp/test/e2e/framework" 7 | "github.com/fatedier/frp/test/e2e/framework/consts" 8 | "github.com/fatedier/frp/test/e2e/pkg/port" 9 | 10 | . "github.com/onsi/ginkgo" 11 | ) 12 | 13 | var _ = Describe("[Feature: Config]", func() { 14 | f := framework.NewDefaultFramework() 15 | 16 | Describe("Template", func() { 17 | It("render by env", func() { 18 | serverConf := consts.DefaultServerConfig 19 | clientConf := consts.DefaultClientConfig 20 | 21 | portName := port.GenName("TCP") 22 | serverConf += fmt.Sprintf(` 23 | token = {{ %s{{ .Envs.FRP_TOKEN }}%s }} 24 | `, "`", "`") 25 | 26 | clientConf += fmt.Sprintf(` 27 | token = {{ %s{{ .Envs.FRP_TOKEN }}%s }} 28 | 29 | [tcp] 30 | type = tcp 31 | local_port = {{ .%s }} 32 | remote_port = {{ .%s }} 33 | `, "`", "`", framework.TCPEchoServerPort, portName) 34 | 35 | f.SetEnvs([]string{"FRP_TOKEN=123"}) 36 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 37 | 38 | framework.NewRequestExpect(f).PortName(portName).Ensure() 39 | }) 40 | }) 41 | 42 | Describe("Includes", func() { 43 | It("split tcp proxies into different files", func() { 44 | serverPort := f.AllocPort() 45 | serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 46 | [common] 47 | bind_addr = 0.0.0.0 48 | bind_port = %d 49 | `, serverPort)) 50 | 51 | remotePort := f.AllocPort() 52 | proxyConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 53 | [tcp] 54 | type = tcp 55 | local_port = %d 56 | remote_port = %d 57 | `, f.PortByName(framework.TCPEchoServerPort), remotePort)) 58 | 59 | remotePort2 := f.AllocPort() 60 | proxyConfigPath2 := f.GenerateConfigFile(fmt.Sprintf(` 61 | [tcp2] 62 | type = tcp 63 | local_port = %d 64 | remote_port = %d 65 | `, f.PortByName(framework.TCPEchoServerPort), remotePort2)) 66 | 67 | clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 68 | [common] 69 | server_port = %d 70 | includes = %s,%s 71 | `, serverPort, proxyConfigPath, proxyConfigPath2)) 72 | 73 | _, _, err := f.RunFrps("-c", serverConfigPath) 74 | framework.ExpectNoError(err) 75 | 76 | _, _, err = f.RunFrpc("-c", clientConfigPath) 77 | framework.ExpectNoError(err) 78 | 79 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 80 | framework.NewRequestExpect(f).Port(remotePort2).Ensure() 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /test/e2e/e2e.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/fatedier/frp/pkg/util/log" 7 | "github.com/fatedier/frp/test/e2e/framework" 8 | 9 | "github.com/onsi/ginkgo" 10 | "github.com/onsi/ginkgo/config" 11 | "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { 15 | setupSuite() 16 | return nil 17 | }, func(data []byte) { 18 | // Run on all Ginkgo nodes 19 | setupSuitePerGinkgoNode() 20 | }) 21 | 22 | var _ = ginkgo.SynchronizedAfterSuite(func() { 23 | CleanupSuite() 24 | }, func() { 25 | AfterSuiteActions() 26 | }) 27 | 28 | // RunE2ETests checks configuration parameters (specified through flags) and then runs 29 | // E2E tests using the Ginkgo runner. 30 | // If a "report directory" is specified, one or more JUnit test reports will be 31 | // generated in this directory, and cluster logs will also be saved. 32 | // This function is called on each Ginkgo node in parallel mode. 33 | func RunE2ETests(t *testing.T) { 34 | gomega.RegisterFailHandler(framework.Fail) 35 | 36 | log.Info("Starting e2e run %q on Ginkgo node %d of total %d", 37 | framework.RunID, config.GinkgoConfig.ParallelNode, config.GinkgoConfig.ParallelTotal) 38 | ginkgo.RunSpecs(t, "frp e2e suite") 39 | } 40 | 41 | // setupSuite is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step. 42 | // There are certain operations we only want to run once per overall test invocation 43 | // (such as deleting old namespaces, or verifying that all system pods are running. 44 | // Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite 45 | // to ensure that these operations only run on the first parallel Ginkgo node. 46 | // 47 | // This function takes two parameters: one function which runs on only the first Ginkgo node, 48 | // returning an opaque byte array, and then a second function which runs on all Ginkgo nodes, 49 | // accepting the byte array. 50 | func setupSuite() { 51 | // Run only on Ginkgo node 1 52 | } 53 | 54 | // setupSuitePerGinkgoNode is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step. 55 | // There are certain operations we only want to run once per overall test invocation on each Ginkgo node 56 | // such as making some global variables accessible to all parallel executions 57 | // Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite 58 | // Ref: https://onsi.github.io/ginkgo/#parallel-specs 59 | func setupSuitePerGinkgoNode() { 60 | // config.GinkgoConfig.ParallelNode 61 | } 62 | -------------------------------------------------------------------------------- /test/e2e/e2e_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/fatedier/frp/pkg/util/log" 10 | "github.com/fatedier/frp/test/e2e/framework" 11 | 12 | // test source 13 | _ "github.com/fatedier/frp/test/e2e/basic" 14 | _ "github.com/fatedier/frp/test/e2e/features" 15 | _ "github.com/fatedier/frp/test/e2e/plugin" 16 | 17 | _ "github.com/onsi/ginkgo" 18 | ) 19 | 20 | // handleFlags sets up all flags and parses the command line. 21 | func handleFlags() { 22 | framework.RegisterCommonFlags(flag.CommandLine) 23 | flag.Parse() 24 | } 25 | 26 | func TestMain(m *testing.M) { 27 | // Register test flags, then parse flags. 28 | handleFlags() 29 | 30 | if err := framework.ValidateTestContext(&framework.TestContext); err != nil { 31 | fmt.Println(err) 32 | os.Exit(1) 33 | } 34 | 35 | log.InitLog("console", "", framework.TestContext.LogLevel, 0, true) 36 | os.Exit(m.Run()) 37 | } 38 | 39 | func TestE2E(t *testing.T) { 40 | RunE2ETests(t) 41 | } 42 | -------------------------------------------------------------------------------- /test/e2e/examples.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatedier/frp/test/e2e/framework" 7 | "github.com/fatedier/frp/test/e2e/framework/consts" 8 | 9 | . "github.com/onsi/ginkgo" 10 | ) 11 | 12 | var _ = Describe("[Feature: Example]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | Describe("TCP", func() { 16 | It("Expose a TCP echo server", func() { 17 | serverConf := consts.DefaultServerConfig 18 | clientConf := consts.DefaultClientConfig 19 | 20 | remotePort := f.AllocPort() 21 | clientConf += fmt.Sprintf(` 22 | [tcp] 23 | type = tcp 24 | local_port = {{ .%s }} 25 | remote_port = %d 26 | `, framework.TCPEchoServerPort, remotePort) 27 | 28 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 29 | 30 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/e2e/features/bandwidth_limit.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/fatedier/frp/test/e2e/framework" 9 | "github.com/fatedier/frp/test/e2e/framework/consts" 10 | "github.com/fatedier/frp/test/e2e/mock/server/streamserver" 11 | "github.com/fatedier/frp/test/e2e/pkg/request" 12 | 13 | . "github.com/onsi/ginkgo" 14 | ) 15 | 16 | var _ = Describe("[Feature: Bandwidth Limit]", func() { 17 | f := framework.NewDefaultFramework() 18 | 19 | It("Proxy Bandwidth Limit", func() { 20 | serverConf := consts.DefaultServerConfig 21 | clientConf := consts.DefaultClientConfig 22 | 23 | localPort := f.AllocPort() 24 | localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort)) 25 | f.RunServer("", localServer) 26 | 27 | remotePort := f.AllocPort() 28 | clientConf += fmt.Sprintf(` 29 | [tcp] 30 | type = tcp 31 | local_port = %d 32 | remote_port = %d 33 | bandwidth_limit = 10KB 34 | `, localPort, remotePort) 35 | 36 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 37 | 38 | content := strings.Repeat("a", 50*1024) // 5KB 39 | start := time.Now() 40 | framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) { 41 | r.Body([]byte(content)).Timeout(30 * time.Second) 42 | }).ExpectResp([]byte(content)).Ensure() 43 | duration := time.Now().Sub(start) 44 | 45 | framework.ExpectTrue(duration.Seconds() > 7, "100Kb with 10KB limit, want > 7 seconds, but got %d seconds", duration.Seconds()) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/e2e/features/chaos.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/fatedier/frp/test/e2e/framework" 8 | 9 | . "github.com/onsi/ginkgo" 10 | ) 11 | 12 | var _ = Describe("[Feature: Chaos]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | It("reconnect after frps restart", func() { 16 | serverPort := f.AllocPort() 17 | serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 18 | [common] 19 | bind_addr = 0.0.0.0 20 | bind_port = %d 21 | `, serverPort)) 22 | 23 | remotePort := f.AllocPort() 24 | clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 25 | [common] 26 | server_port = %d 27 | log_level = trace 28 | 29 | [tcp] 30 | type = tcp 31 | local_port = %d 32 | remote_port = %d 33 | `, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort)) 34 | 35 | // 1. start frps and frpc, expect request success 36 | ps, _, err := f.RunFrps("-c", serverConfigPath) 37 | framework.ExpectNoError(err) 38 | 39 | pc, _, err := f.RunFrpc("-c", clientConfigPath) 40 | framework.ExpectNoError(err) 41 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 42 | 43 | // 2. stop frps, expect request failed 44 | ps.Stop() 45 | time.Sleep(200 * time.Millisecond) 46 | framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure() 47 | 48 | // 3. restart frps, expect request success 49 | _, _, err = f.RunFrps("-c", serverConfigPath) 50 | framework.ExpectNoError(err) 51 | time.Sleep(2 * time.Second) 52 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 53 | 54 | // 4. stop frpc, expect request failed 55 | pc.Stop() 56 | time.Sleep(200 * time.Millisecond) 57 | framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure() 58 | 59 | // 5. restart frpc, expect request success 60 | _, _, err = f.RunFrpc("-c", clientConfigPath) 61 | framework.ExpectNoError(err) 62 | time.Sleep(time.Second) 63 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /test/e2e/framework/cleanup.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // CleanupActionHandle is an integer pointer type for handling cleanup action 8 | type CleanupActionHandle *int 9 | type cleanupFuncHandle struct { 10 | actionHandle CleanupActionHandle 11 | actionHook func() 12 | } 13 | 14 | var cleanupActionsLock sync.Mutex 15 | var cleanupHookList = []cleanupFuncHandle{} 16 | 17 | // AddCleanupAction installs a function that will be called in the event of the 18 | // whole test being terminated. This allows arbitrary pieces of the overall 19 | // test to hook into SynchronizedAfterSuite(). 20 | // The hooks are called in last-in-first-out order. 21 | func AddCleanupAction(fn func()) CleanupActionHandle { 22 | p := CleanupActionHandle(new(int)) 23 | cleanupActionsLock.Lock() 24 | defer cleanupActionsLock.Unlock() 25 | c := cleanupFuncHandle{actionHandle: p, actionHook: fn} 26 | cleanupHookList = append([]cleanupFuncHandle{c}, cleanupHookList...) 27 | return p 28 | } 29 | 30 | // RemoveCleanupAction removes a function that was installed by 31 | // AddCleanupAction. 32 | func RemoveCleanupAction(p CleanupActionHandle) { 33 | cleanupActionsLock.Lock() 34 | defer cleanupActionsLock.Unlock() 35 | for i, item := range cleanupHookList { 36 | if item.actionHandle == p { 37 | cleanupHookList = append(cleanupHookList[:i], cleanupHookList[i+1:]...) 38 | break 39 | } 40 | } 41 | } 42 | 43 | // RunCleanupActions runs all functions installed by AddCleanupAction. It does 44 | // not remove them (see RemoveCleanupAction) but it does run unlocked, so they 45 | // may remove themselves. 46 | func RunCleanupActions() { 47 | list := []func(){} 48 | func() { 49 | cleanupActionsLock.Lock() 50 | defer cleanupActionsLock.Unlock() 51 | for _, p := range cleanupHookList { 52 | list = append(list, p.actionHook) 53 | } 54 | }() 55 | // Run unlocked. 56 | for _, fn := range list { 57 | fn() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/e2e/framework/client.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | type FRPClient struct { 4 | port int 5 | } 6 | 7 | func (f *Framework) FRPClient(port int) *FRPClient { 8 | return &FRPClient{ 9 | port: port, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/e2e/framework/consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/fatedier/frp/test/e2e/pkg/port" 8 | ) 9 | 10 | const ( 11 | TestString = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet." 12 | 13 | DefaultTimeout = 2 * time.Second 14 | ) 15 | 16 | var ( 17 | PortServerName string 18 | PortClientAdmin string 19 | 20 | DefaultServerConfig = ` 21 | [common] 22 | bind_port = {{ .%s }} 23 | log_level = trace 24 | ` 25 | 26 | DefaultClientConfig = ` 27 | [common] 28 | server_port = {{ .%s }} 29 | log_level = trace 30 | ` 31 | ) 32 | 33 | func init() { 34 | PortServerName = port.GenName("Server") 35 | PortClientAdmin = port.GenName("ClientAdmin") 36 | DefaultServerConfig = fmt.Sprintf(DefaultServerConfig, port.GenName("Server")) 37 | DefaultClientConfig = fmt.Sprintf(DefaultClientConfig, port.GenName("Server")) 38 | } 39 | -------------------------------------------------------------------------------- /test/e2e/framework/ginkgowrapper/wrapper.go: -------------------------------------------------------------------------------- 1 | // Package ginkgowrapper wraps Ginkgo Fail and Skip functions to panic 2 | // with structured data instead of a constant string. 3 | package ginkgowrapper 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | "regexp" 9 | "runtime" 10 | "runtime/debug" 11 | "strings" 12 | 13 | "github.com/onsi/ginkgo" 14 | ) 15 | 16 | // FailurePanic is the value that will be panicked from Fail. 17 | type FailurePanic struct { 18 | Message string // The failure message passed to Fail 19 | Filename string // The filename that is the source of the failure 20 | Line int // The line number of the filename that is the source of the failure 21 | FullStackTrace string // A full stack trace starting at the source of the failure 22 | } 23 | 24 | // String makes FailurePanic look like the old Ginkgo panic when printed. 25 | func (FailurePanic) String() string { return ginkgo.GINKGO_PANIC } 26 | 27 | // Fail wraps ginkgo.Fail so that it panics with more useful 28 | // information about the failure. This function will panic with a 29 | // FailurePanic. 30 | func Fail(message string, callerSkip ...int) { 31 | skip := 1 32 | if len(callerSkip) > 0 { 33 | skip += callerSkip[0] 34 | } 35 | 36 | _, file, line, _ := runtime.Caller(skip) 37 | fp := FailurePanic{ 38 | Message: message, 39 | Filename: file, 40 | Line: line, 41 | FullStackTrace: pruneStack(skip), 42 | } 43 | 44 | defer func() { 45 | e := recover() 46 | if e != nil { 47 | panic(fp) 48 | } 49 | }() 50 | 51 | ginkgo.Fail(message, skip) 52 | } 53 | 54 | // ginkgo adds a lot of test running infrastructure to the stack, so 55 | // we filter those out 56 | var stackSkipPattern = regexp.MustCompile(`onsi/ginkgo`) 57 | 58 | func pruneStack(skip int) string { 59 | skip += 2 // one for pruneStack and one for debug.Stack 60 | stack := debug.Stack() 61 | scanner := bufio.NewScanner(bytes.NewBuffer(stack)) 62 | var prunedStack []string 63 | 64 | // skip the top of the stack 65 | for i := 0; i < 2*skip+1; i++ { 66 | scanner.Scan() 67 | } 68 | 69 | for scanner.Scan() { 70 | if stackSkipPattern.Match(scanner.Bytes()) { 71 | scanner.Scan() // these come in pairs 72 | } else { 73 | prunedStack = append(prunedStack, scanner.Text()) 74 | scanner.Scan() // these come in pairs 75 | prunedStack = append(prunedStack, scanner.Text()) 76 | } 77 | } 78 | 79 | return strings.Join(prunedStack, "\n") 80 | } 81 | -------------------------------------------------------------------------------- /test/e2e/framework/mockservers.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/fatedier/frp/test/e2e/framework/consts" 9 | "github.com/fatedier/frp/test/e2e/mock/server" 10 | "github.com/fatedier/frp/test/e2e/mock/server/httpserver" 11 | "github.com/fatedier/frp/test/e2e/mock/server/streamserver" 12 | "github.com/fatedier/frp/test/e2e/pkg/port" 13 | ) 14 | 15 | const ( 16 | TCPEchoServerPort = "TCPEchoServerPort" 17 | UDPEchoServerPort = "UDPEchoServerPort" 18 | UDSEchoServerAddr = "UDSEchoServerAddr" 19 | HTTPSimpleServerPort = "HTTPSimpleServerPort" 20 | ) 21 | 22 | type MockServers struct { 23 | tcpEchoServer server.Server 24 | udpEchoServer server.Server 25 | udsEchoServer server.Server 26 | httpSimpleServer server.Server 27 | } 28 | 29 | func NewMockServers(portAllocator *port.Allocator) *MockServers { 30 | s := &MockServers{} 31 | tcpPort := portAllocator.Get() 32 | udpPort := portAllocator.Get() 33 | httpPort := portAllocator.Get() 34 | s.tcpEchoServer = streamserver.New(streamserver.TCP, streamserver.WithBindPort(tcpPort)) 35 | s.udpEchoServer = streamserver.New(streamserver.UDP, streamserver.WithBindPort(udpPort)) 36 | s.httpSimpleServer = httpserver.New(httpserver.WithBindPort(httpPort), httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 37 | w.Write([]byte(consts.TestString)) 38 | }))) 39 | 40 | udsIndex := portAllocator.Get() 41 | udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex) 42 | os.Remove(udsAddr) 43 | s.udsEchoServer = streamserver.New(streamserver.Unix, streamserver.WithBindAddr(udsAddr)) 44 | return s 45 | } 46 | 47 | func (m *MockServers) Run() error { 48 | if err := m.tcpEchoServer.Run(); err != nil { 49 | return err 50 | } 51 | if err := m.udpEchoServer.Run(); err != nil { 52 | return err 53 | } 54 | if err := m.udsEchoServer.Run(); err != nil { 55 | return err 56 | } 57 | if err := m.httpSimpleServer.Run(); err != nil { 58 | return err 59 | } 60 | return nil 61 | } 62 | 63 | func (m *MockServers) Close() { 64 | m.tcpEchoServer.Close() 65 | m.udpEchoServer.Close() 66 | m.udsEchoServer.Close() 67 | m.httpSimpleServer.Close() 68 | os.Remove(m.udsEchoServer.BindAddr()) 69 | } 70 | 71 | func (m *MockServers) GetTemplateParams() map[string]interface{} { 72 | ret := make(map[string]interface{}) 73 | ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort() 74 | ret[UDPEchoServerPort] = m.udpEchoServer.BindPort() 75 | ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr() 76 | ret[HTTPSimpleServerPort] = m.httpSimpleServer.BindPort() 77 | return ret 78 | } 79 | 80 | func (m *MockServers) GetParam(key string) interface{} { 81 | params := m.GetTemplateParams() 82 | if v, ok := params[key]; ok { 83 | return v 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /test/e2e/framework/process.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path/filepath" 7 | "time" 8 | 9 | flog "github.com/fatedier/frp/pkg/util/log" 10 | "github.com/fatedier/frp/test/e2e/pkg/process" 11 | ) 12 | 13 | // RunProcesses run multiple processes from templates. 14 | // The first template should always be frps. 15 | func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) { 16 | templates := make([]string, 0, len(serverTemplates)+len(clientTemplates)) 17 | for _, t := range serverTemplates { 18 | templates = append(templates, t) 19 | } 20 | for _, t := range clientTemplates { 21 | templates = append(templates, t) 22 | } 23 | outs, ports, err := f.RenderTemplates(templates) 24 | ExpectNoError(err) 25 | ExpectTrue(len(templates) > 0) 26 | 27 | for name, port := range ports { 28 | f.usedPorts[name] = port 29 | } 30 | 31 | for i := range serverTemplates { 32 | path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i)) 33 | err = ioutil.WriteFile(path, []byte(outs[i]), 0666) 34 | ExpectNoError(err) 35 | flog.Trace("[%s] %s", path, outs[i]) 36 | 37 | p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs) 38 | f.serverConfPaths = append(f.serverConfPaths, path) 39 | f.serverProcesses = append(f.serverProcesses, p) 40 | err = p.Start() 41 | ExpectNoError(err) 42 | } 43 | time.Sleep(time.Second) 44 | 45 | for i := range clientTemplates { 46 | index := i + len(serverTemplates) 47 | path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i)) 48 | err = ioutil.WriteFile(path, []byte(outs[index]), 0666) 49 | ExpectNoError(err) 50 | flog.Trace("[%s] %s", path, outs[index]) 51 | 52 | p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs) 53 | f.clientConfPaths = append(f.clientConfPaths, path) 54 | f.clientProcesses = append(f.clientProcesses, p) 55 | err = p.Start() 56 | ExpectNoError(err) 57 | time.Sleep(500 * time.Millisecond) 58 | } 59 | time.Sleep(500 * time.Millisecond) 60 | } 61 | 62 | func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) { 63 | p := process.NewWithEnvs(TestContext.FRPServerPath, args, f.osEnvs) 64 | f.serverProcesses = append(f.serverProcesses, p) 65 | err := p.Start() 66 | if err != nil { 67 | return p, p.StdOutput(), err 68 | } 69 | // sleep for a while to get std output 70 | time.Sleep(500 * time.Millisecond) 71 | return p, p.StdOutput(), nil 72 | } 73 | 74 | func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) { 75 | p := process.NewWithEnvs(TestContext.FRPClientPath, args, f.osEnvs) 76 | f.clientProcesses = append(f.clientProcesses, p) 77 | err := p.Start() 78 | if err != nil { 79 | return p, p.StdOutput(), err 80 | } 81 | time.Sleep(500 * time.Millisecond) 82 | return p, p.StdOutput(), nil 83 | } 84 | 85 | func (f *Framework) GenerateConfigFile(content string) string { 86 | f.configFileIndex++ 87 | path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex)) 88 | err := ioutil.WriteFile(path, []byte(content), 0666) 89 | ExpectNoError(err) 90 | return path 91 | } 92 | -------------------------------------------------------------------------------- /test/e2e/framework/request.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | 7 | flog "github.com/fatedier/frp/pkg/util/log" 8 | "github.com/fatedier/frp/test/e2e/framework/consts" 9 | "github.com/fatedier/frp/test/e2e/pkg/request" 10 | ) 11 | 12 | func SpecifiedHTTPBodyHandler(body []byte) http.HandlerFunc { 13 | return func(w http.ResponseWriter, req *http.Request) { 14 | w.Write(body) 15 | } 16 | } 17 | 18 | func ExpectResponseCode(code int) EnsureFunc { 19 | return func(resp *request.Response) bool { 20 | if resp.Code == code { 21 | return true 22 | } 23 | flog.Warn("Expect code %d, but got %d", code, resp.Code) 24 | return false 25 | } 26 | } 27 | 28 | // NewRequest return a default request with default timeout and content. 29 | func NewRequest() *request.Request { 30 | return request.New(). 31 | Timeout(consts.DefaultTimeout). 32 | Body([]byte(consts.TestString)) 33 | } 34 | 35 | func NewHTTPRequest() *request.Request { 36 | return request.New().HTTP().HTTPParams("GET", "", "/", nil) 37 | } 38 | 39 | type RequestExpect struct { 40 | req *request.Request 41 | 42 | f *Framework 43 | expectResp []byte 44 | expectError bool 45 | explain []interface{} 46 | } 47 | 48 | func NewRequestExpect(f *Framework) *RequestExpect { 49 | return &RequestExpect{ 50 | req: NewRequest(), 51 | f: f, 52 | expectResp: []byte(consts.TestString), 53 | expectError: false, 54 | explain: make([]interface{}, 0), 55 | } 56 | } 57 | 58 | func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect { 59 | f(e.req) 60 | return e 61 | } 62 | 63 | func (e *RequestExpect) Protocol(protocol string) *RequestExpect { 64 | e.req.Protocol(protocol) 65 | return e 66 | } 67 | 68 | func (e *RequestExpect) PortName(name string) *RequestExpect { 69 | if e.f != nil { 70 | e.req.Port(e.f.PortByName(name)) 71 | } 72 | return e 73 | } 74 | 75 | func (e *RequestExpect) Port(port int) *RequestExpect { 76 | if e.f != nil { 77 | e.req.Port(port) 78 | } 79 | return e 80 | } 81 | 82 | func (e *RequestExpect) ExpectResp(resp []byte) *RequestExpect { 83 | e.expectResp = resp 84 | return e 85 | } 86 | 87 | func (e *RequestExpect) ExpectError(expectErr bool) *RequestExpect { 88 | e.expectError = expectErr 89 | return e 90 | } 91 | 92 | func (e *RequestExpect) Explain(explain ...interface{}) *RequestExpect { 93 | e.explain = explain 94 | return e 95 | } 96 | 97 | type EnsureFunc func(*request.Response) bool 98 | 99 | func (e *RequestExpect) Ensure(fns ...EnsureFunc) { 100 | ret, err := e.req.Do() 101 | if e.expectError { 102 | ExpectErrorWithOffset(1, err, e.explain...) 103 | return 104 | } 105 | ExpectNoErrorWithOffset(1, err, e.explain...) 106 | 107 | if len(fns) == 0 { 108 | if !bytes.Equal(e.expectResp, ret.Content) { 109 | flog.Trace("Response info: %+v", ret) 110 | } 111 | ExpectEqualValuesWithOffset(1, e.expectResp, ret.Content, e.explain...) 112 | } else { 113 | for _, fn := range fns { 114 | ok := fn(ret) 115 | if !ok { 116 | flog.Trace("Response info: %+v", ret) 117 | } 118 | ExpectTrueWithOffset(1, ok, e.explain...) 119 | } 120 | } 121 | } 122 | 123 | func (e *RequestExpect) Do() (*request.Response, error) { 124 | return e.req.Do() 125 | } 126 | -------------------------------------------------------------------------------- /test/e2e/framework/test_context.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/onsi/ginkgo/config" 9 | ) 10 | 11 | type TestContextType struct { 12 | FRPClientPath string 13 | FRPServerPath string 14 | LogLevel string 15 | Debug bool 16 | } 17 | 18 | var TestContext TestContextType 19 | 20 | // RegisterCommonFlags registers flags common to all e2e test suites. 21 | // The flag set can be flag.CommandLine (if desired) or a custom 22 | // flag set that then gets passed to viperconfig.ViperizeFlags. 23 | // 24 | // The other Register*Flags methods below can be used to add more 25 | // test-specific flags. However, those settings then get added 26 | // regardless whether the test is actually in the test suite. 27 | // 28 | func RegisterCommonFlags(flags *flag.FlagSet) { 29 | // Turn on EmitSpecProgress to get spec progress (especially on interrupt) 30 | config.GinkgoConfig.EmitSpecProgress = true 31 | 32 | // Randomize specs as well as suites 33 | config.GinkgoConfig.RandomizeAllSpecs = true 34 | 35 | flags.StringVar(&TestContext.FRPClientPath, "frpc-path", "../../bin/frpc", "The frp client binary to use.") 36 | flags.StringVar(&TestContext.FRPServerPath, "frps-path", "../../bin/frps", "The frp server binary to use.") 37 | flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.") 38 | flags.BoolVar(&TestContext.Debug, "debug", false, "Enable debug mode to print detail info.") 39 | } 40 | 41 | func ValidateTestContext(t *TestContextType) error { 42 | if t.FRPClientPath == "" || t.FRPServerPath == "" { 43 | return fmt.Errorf("frpc and frps binary path can't be empty") 44 | } 45 | if _, err := os.Stat(t.FRPClientPath); err != nil { 46 | return fmt.Errorf("load frpc-path error: %v", err) 47 | } 48 | if _, err := os.Stat(t.FRPServerPath); err != nil { 49 | return fmt.Errorf("load frps-path error: %v", err) 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /test/e2e/framework/util.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | ) 6 | 7 | // RunID is a unique identifier of the e2e run. 8 | // Beware that this ID is not the same for all tests in the e2e run, because each Ginkgo node creates it separately. 9 | var RunID string 10 | 11 | func init() { 12 | RunID = uuid.NewString() 13 | } 14 | -------------------------------------------------------------------------------- /test/e2e/mock/server/httpserver/server.go: -------------------------------------------------------------------------------- 1 | package httpserver 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | type Server struct { 11 | bindAddr string 12 | bindPort int 13 | hanlder http.Handler 14 | 15 | l net.Listener 16 | hs *http.Server 17 | } 18 | 19 | type Option func(*Server) *Server 20 | 21 | func New(options ...Option) *Server { 22 | s := &Server{ 23 | bindAddr: "127.0.0.1", 24 | } 25 | 26 | for _, option := range options { 27 | s = option(s) 28 | } 29 | return s 30 | } 31 | 32 | func WithBindAddr(addr string) Option { 33 | return func(s *Server) *Server { 34 | s.bindAddr = addr 35 | return s 36 | } 37 | } 38 | 39 | func WithBindPort(port int) Option { 40 | return func(s *Server) *Server { 41 | s.bindPort = port 42 | return s 43 | } 44 | } 45 | 46 | func WithHandler(h http.Handler) Option { 47 | return func(s *Server) *Server { 48 | s.hanlder = h 49 | return s 50 | } 51 | } 52 | 53 | func (s *Server) Run() error { 54 | if err := s.initListener(); err != nil { 55 | return err 56 | } 57 | 58 | addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)) 59 | hs := &http.Server{ 60 | Addr: addr, 61 | Handler: s.hanlder, 62 | } 63 | s.hs = hs 64 | go hs.Serve(s.l) 65 | return nil 66 | } 67 | 68 | func (s *Server) Close() error { 69 | if s.hs != nil { 70 | return s.hs.Close() 71 | } 72 | return nil 73 | } 74 | 75 | func (s *Server) initListener() (err error) { 76 | s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort)) 77 | return 78 | } 79 | 80 | func (s *Server) BindAddr() string { 81 | return s.bindAddr 82 | } 83 | 84 | func (s *Server) BindPort() int { 85 | return s.bindPort 86 | } 87 | -------------------------------------------------------------------------------- /test/e2e/mock/server/interface.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | type Server interface { 4 | Run() error 5 | Close() error 6 | BindAddr() string 7 | BindPort() int 8 | } 9 | -------------------------------------------------------------------------------- /test/e2e/mock/server/streamserver/server.go: -------------------------------------------------------------------------------- 1 | package streamserver 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | 9 | libnet "github.com/fatedier/frp/pkg/util/net" 10 | "github.com/fatedier/frp/test/e2e/pkg/rpc" 11 | ) 12 | 13 | type Type string 14 | 15 | const ( 16 | TCP Type = "tcp" 17 | UDP Type = "udp" 18 | Unix Type = "unix" 19 | ) 20 | 21 | type Server struct { 22 | netType Type 23 | bindAddr string 24 | bindPort int 25 | respContent []byte 26 | 27 | handler func(net.Conn) 28 | 29 | l net.Listener 30 | } 31 | 32 | type Option func(*Server) *Server 33 | 34 | func New(netType Type, options ...Option) *Server { 35 | s := &Server{ 36 | netType: netType, 37 | bindAddr: "127.0.0.1", 38 | } 39 | s.handler = s.handle 40 | 41 | for _, option := range options { 42 | s = option(s) 43 | } 44 | return s 45 | } 46 | 47 | func WithBindAddr(addr string) Option { 48 | return func(s *Server) *Server { 49 | s.bindAddr = addr 50 | return s 51 | } 52 | } 53 | 54 | func WithBindPort(port int) Option { 55 | return func(s *Server) *Server { 56 | s.bindPort = port 57 | return s 58 | } 59 | } 60 | 61 | func WithRespContent(content []byte) Option { 62 | return func(s *Server) *Server { 63 | s.respContent = content 64 | return s 65 | } 66 | } 67 | 68 | func WithCustomHandler(handler func(net.Conn)) Option { 69 | return func(s *Server) *Server { 70 | s.handler = handler 71 | return s 72 | } 73 | } 74 | 75 | func (s *Server) Run() error { 76 | if err := s.initListener(); err != nil { 77 | return err 78 | } 79 | 80 | go func() { 81 | for { 82 | c, err := s.l.Accept() 83 | if err != nil { 84 | return 85 | } 86 | go s.handler(c) 87 | } 88 | }() 89 | return nil 90 | } 91 | 92 | func (s *Server) Close() error { 93 | if s.l != nil { 94 | return s.l.Close() 95 | } 96 | return nil 97 | } 98 | 99 | func (s *Server) initListener() (err error) { 100 | switch s.netType { 101 | case TCP: 102 | s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort)) 103 | case UDP: 104 | s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort) 105 | case Unix: 106 | s.l, err = net.Listen("unix", s.bindAddr) 107 | default: 108 | return fmt.Errorf("unknown server type: %s", s.netType) 109 | } 110 | return err 111 | } 112 | 113 | func (s *Server) handle(c net.Conn) { 114 | defer c.Close() 115 | 116 | var reader io.Reader = c 117 | if s.netType == UDP { 118 | reader = bufio.NewReader(c) 119 | } 120 | for { 121 | buf, err := rpc.ReadBytes(reader) 122 | if err != nil { 123 | return 124 | } 125 | 126 | if len(s.respContent) > 0 { 127 | buf = s.respContent 128 | } 129 | rpc.WriteBytes(c, buf) 130 | } 131 | } 132 | 133 | func (s *Server) BindAddr() string { 134 | return s.bindAddr 135 | } 136 | 137 | func (s *Server) BindPort() int { 138 | return s.bindPort 139 | } 140 | -------------------------------------------------------------------------------- /test/e2e/pkg/port/port.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | 8 | "k8s.io/apimachinery/pkg/util/sets" 9 | ) 10 | 11 | type Allocator struct { 12 | reserved sets.Int 13 | used sets.Int 14 | mu sync.Mutex 15 | } 16 | 17 | // NewAllocator return a port allocator for testing. 18 | // Example: from: 10, to: 20, mod 4, index 1 19 | // Reserved ports: 13, 17 20 | func NewAllocator(from int, to int, mod int, index int) *Allocator { 21 | pa := &Allocator{ 22 | reserved: sets.NewInt(), 23 | used: sets.NewInt(), 24 | } 25 | 26 | for i := from; i <= to; i++ { 27 | if i%mod == index { 28 | pa.reserved.Insert(i) 29 | } 30 | } 31 | return pa 32 | } 33 | 34 | func (pa *Allocator) Get() int { 35 | return pa.GetByName("") 36 | } 37 | 38 | func (pa *Allocator) GetByName(portName string) int { 39 | var builder *nameBuilder 40 | if portName == "" { 41 | builder = &nameBuilder{} 42 | } else { 43 | var err error 44 | builder, err = unmarshalFromName(portName) 45 | if err != nil { 46 | fmt.Println(err, portName) 47 | return 0 48 | } 49 | } 50 | 51 | pa.mu.Lock() 52 | defer pa.mu.Unlock() 53 | 54 | for i := 0; i < 20; i++ { 55 | port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo) 56 | if port == 0 { 57 | return 0 58 | } 59 | 60 | l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 61 | if err != nil { 62 | // Maybe not controlled by us, mark it used. 63 | pa.used.Insert(port) 64 | continue 65 | } 66 | l.Close() 67 | 68 | udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port)) 69 | if err != nil { 70 | continue 71 | } 72 | udpConn, err := net.ListenUDP("udp", udpAddr) 73 | if err != nil { 74 | // Maybe not controlled by us, mark it used. 75 | pa.used.Insert(port) 76 | continue 77 | } 78 | udpConn.Close() 79 | 80 | pa.used.Insert(port) 81 | return port 82 | } 83 | return 0 84 | } 85 | 86 | func (pa *Allocator) getByRange(from, to int) int { 87 | if from <= 0 { 88 | port, _ := pa.reserved.PopAny() 89 | return port 90 | } 91 | 92 | // choose a random port between from - to 93 | ports := pa.reserved.UnsortedList() 94 | for _, port := range ports { 95 | if port >= from && port <= to { 96 | return port 97 | } 98 | } 99 | return 0 100 | } 101 | 102 | func (pa *Allocator) Release(port int) { 103 | if port <= 0 { 104 | return 105 | } 106 | 107 | pa.mu.Lock() 108 | defer pa.mu.Unlock() 109 | 110 | if pa.used.Has(port) { 111 | pa.used.Delete(port) 112 | pa.reserved.Insert(port) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/e2e/pkg/port/util.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | NameDelimiter = "_" 11 | ) 12 | 13 | type NameOption func(*nameBuilder) *nameBuilder 14 | 15 | type nameBuilder struct { 16 | name string 17 | rangePortFrom int 18 | rangePortTo int 19 | } 20 | 21 | func unmarshalFromName(name string) (*nameBuilder, error) { 22 | var builder nameBuilder 23 | arrs := strings.Split(name, NameDelimiter) 24 | switch len(arrs) { 25 | case 2: 26 | builder.name = arrs[1] 27 | case 4: 28 | builder.name = arrs[1] 29 | if fromPort, err := strconv.Atoi(arrs[2]); err != nil { 30 | return nil, fmt.Errorf("error range port from") 31 | } else { 32 | builder.rangePortFrom = fromPort 33 | } 34 | if toPort, err := strconv.Atoi(arrs[3]); err != nil { 35 | return nil, fmt.Errorf("error range port to") 36 | } else { 37 | builder.rangePortTo = toPort 38 | } 39 | default: 40 | return nil, fmt.Errorf("error port name format") 41 | } 42 | return &builder, nil 43 | } 44 | 45 | func (builder *nameBuilder) String() string { 46 | name := fmt.Sprintf("Port%s%s", NameDelimiter, builder.name) 47 | if builder.rangePortFrom > 0 && builder.rangePortTo > 0 && builder.rangePortTo > builder.rangePortFrom { 48 | name += fmt.Sprintf("%s%d%s%d", NameDelimiter, builder.rangePortFrom, NameDelimiter, builder.rangePortTo) 49 | } 50 | return name 51 | } 52 | 53 | func WithRangePorts(from, to int) NameOption { 54 | return func(builder *nameBuilder) *nameBuilder { 55 | builder.rangePortFrom = from 56 | builder.rangePortTo = to 57 | return builder 58 | } 59 | } 60 | 61 | func GenName(name string, options ...NameOption) string { 62 | name = strings.ReplaceAll(name, "-", "") 63 | name = strings.ReplaceAll(name, "_", "") 64 | builder := &nameBuilder{name: name} 65 | for _, option := range options { 66 | builder = option(builder) 67 | } 68 | return builder.String() 69 | } 70 | -------------------------------------------------------------------------------- /test/e2e/pkg/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os/exec" 7 | ) 8 | 9 | type Process struct { 10 | cmd *exec.Cmd 11 | cancel context.CancelFunc 12 | errorOutput *bytes.Buffer 13 | stdOutput *bytes.Buffer 14 | 15 | beforeStopHandler func() 16 | stopped bool 17 | } 18 | 19 | func New(path string, params []string) *Process { 20 | return NewWithEnvs(path, params, nil) 21 | } 22 | 23 | func NewWithEnvs(path string, params []string, envs []string) *Process { 24 | ctx, cancel := context.WithCancel(context.Background()) 25 | cmd := exec.CommandContext(ctx, path, params...) 26 | cmd.Env = envs 27 | p := &Process{ 28 | cmd: cmd, 29 | cancel: cancel, 30 | } 31 | p.errorOutput = bytes.NewBufferString("") 32 | p.stdOutput = bytes.NewBufferString("") 33 | cmd.Stderr = p.errorOutput 34 | cmd.Stdout = p.stdOutput 35 | return p 36 | } 37 | 38 | func (p *Process) Start() error { 39 | return p.cmd.Start() 40 | } 41 | 42 | func (p *Process) Stop() error { 43 | if p.stopped { 44 | return nil 45 | } 46 | defer func() { 47 | p.stopped = true 48 | }() 49 | if p.beforeStopHandler != nil { 50 | p.beforeStopHandler() 51 | } 52 | p.cancel() 53 | return p.cmd.Wait() 54 | } 55 | 56 | func (p *Process) ErrorOutput() string { 57 | return p.errorOutput.String() 58 | } 59 | 60 | func (p *Process) StdOutput() string { 61 | return p.stdOutput.String() 62 | } 63 | 64 | func (p *Process) SetBeforeStopHandler(fn func()) { 65 | p.beforeStopHandler = fn 66 | } 67 | -------------------------------------------------------------------------------- /test/e2e/pkg/rpc/rpc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | ) 9 | 10 | func WriteBytes(w io.Writer, buf []byte) (int, error) { 11 | out := bytes.NewBuffer(nil) 12 | binary.Write(out, binary.BigEndian, int64(len(buf))) 13 | out.Write(buf) 14 | return w.Write(out.Bytes()) 15 | } 16 | 17 | func ReadBytes(r io.Reader) ([]byte, error) { 18 | var length int64 19 | if err := binary.Read(r, binary.BigEndian, &length); err != nil { 20 | return nil, err 21 | } 22 | buffer := make([]byte, length) 23 | n, err := io.ReadFull(r, buffer) 24 | if err != nil { 25 | return nil, err 26 | } 27 | if int64(n) != length { 28 | return nil, errors.New("invalid length") 29 | } 30 | return buffer, nil 31 | } 32 | -------------------------------------------------------------------------------- /test/e2e/pkg/sdk/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/fatedier/frp/client" 13 | "github.com/fatedier/frp/test/e2e/pkg/utils" 14 | ) 15 | 16 | type Client struct { 17 | address string 18 | authUser string 19 | authPwd string 20 | } 21 | 22 | func New(host string, port int) *Client { 23 | return &Client{ 24 | address: net.JoinHostPort(host, strconv.Itoa(port)), 25 | } 26 | } 27 | 28 | func (c *Client) SetAuth(user, pwd string) { 29 | c.authUser = user 30 | c.authPwd = pwd 31 | } 32 | 33 | func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) { 34 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil) 35 | if err != nil { 36 | return nil, err 37 | } 38 | content, err := c.do(req) 39 | if err != nil { 40 | return nil, err 41 | } 42 | allStatus := &client.StatusResp{} 43 | if err = json.Unmarshal([]byte(content), &allStatus); err != nil { 44 | return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content)) 45 | } 46 | for _, s := range allStatus.TCP { 47 | if s.Name == name { 48 | return &s, nil 49 | } 50 | } 51 | for _, s := range allStatus.UDP { 52 | if s.Name == name { 53 | return &s, nil 54 | } 55 | } 56 | for _, s := range allStatus.HTTP { 57 | if s.Name == name { 58 | return &s, nil 59 | } 60 | } 61 | for _, s := range allStatus.HTTPS { 62 | if s.Name == name { 63 | return &s, nil 64 | } 65 | } 66 | for _, s := range allStatus.STCP { 67 | if s.Name == name { 68 | return &s, nil 69 | } 70 | } 71 | for _, s := range allStatus.XTCP { 72 | if s.Name == name { 73 | return &s, nil 74 | } 75 | } 76 | for _, s := range allStatus.SUDP { 77 | if s.Name == name { 78 | return &s, nil 79 | } 80 | } 81 | return nil, fmt.Errorf("no proxy status found") 82 | } 83 | 84 | func (c *Client) Reload() error { 85 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload", nil) 86 | if err != nil { 87 | return err 88 | } 89 | _, err = c.do(req) 90 | return err 91 | } 92 | 93 | func (c *Client) GetConfig() (string, error) { 94 | req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil) 95 | if err != nil { 96 | return "", err 97 | } 98 | return c.do(req) 99 | } 100 | 101 | func (c *Client) UpdateConfig(content string) error { 102 | req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content)) 103 | if err != nil { 104 | return err 105 | } 106 | _, err = c.do(req) 107 | return err 108 | } 109 | 110 | func (c *Client) setAuthHeader(req *http.Request) { 111 | if c.authUser != "" || c.authPwd != "" { 112 | req.Header.Set("Authorization", utils.BasicAuth(c.authUser, c.authPwd)) 113 | } 114 | } 115 | 116 | func (c *Client) do(req *http.Request) (string, error) { 117 | c.setAuthHeader(req) 118 | 119 | resp, err := http.DefaultClient.Do(req) 120 | if err != nil { 121 | return "", err 122 | } 123 | defer resp.Body.Close() 124 | 125 | if resp.StatusCode != 200 { 126 | return "", fmt.Errorf("api status code [%d]", resp.StatusCode) 127 | } 128 | buf, err := ioutil.ReadAll(resp.Body) 129 | if err != nil { 130 | return "", err 131 | } 132 | return string(buf), nil 133 | } 134 | -------------------------------------------------------------------------------- /test/e2e/pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | ) 6 | 7 | func BasicAuth(username, passwd string) string { 8 | auth := username + ":" + passwd 9 | return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 10 | } 11 | -------------------------------------------------------------------------------- /test/e2e/suites.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | // CleanupSuite is the boilerplate that can be used after tests on ginkgo were run, on the SynchronizedAfterSuite step. 4 | // Similar to SynchronizedBeforeSuite, we want to run some operations only once (such as collecting cluster logs). 5 | // Here, the order of functions is reversed; first, the function which runs everywhere, 6 | // and then the function that only runs on the first Ginkgo node. 7 | func CleanupSuite() { 8 | // Run on all Ginkgo nodes 9 | } 10 | 11 | // AfterSuiteActions are actions that are run on ginkgo's SynchronizedAfterSuite 12 | func AfterSuiteActions() { 13 | // Run only Ginkgo on node 1 14 | } 15 | -------------------------------------------------------------------------------- /web/frpc/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }] 4 | ], 5 | "plugins": [ 6 | [ 7 | "component", 8 | { 9 | "libraryName": "element-ui", 10 | "styleLibraryName": "theme-chalk" 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /web/frpc/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | .idea 6 | .vscode/settings.json 7 | -------------------------------------------------------------------------------- /web/frpc/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist build 2 | build: 3 | @npm run build 4 | 5 | dev: 6 | @npm run dev 7 | -------------------------------------------------------------------------------- /web/frpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frpc-web", 3 | "description": "An admin web ui for frp client.", 4 | "author": "fatedier", 5 | "private": true, 6 | "scripts": { 7 | "dev": "webpack-dev-server -d --inline --hot --env.dev", 8 | "build": "rimraf dist && webpack -p --progress --hide-modules" 9 | }, 10 | "dependencies": { 11 | "element-ui": "^2.5.3", 12 | "vue": "^2.5.22", 13 | "vue-resource": "^1.5.1", 14 | "vue-router": "^3.0.2", 15 | "whatwg-fetch": "^3.0.0" 16 | }, 17 | "engines": { 18 | "node": ">=6" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^9.4.7", 22 | "babel-core": "^6.26.3", 23 | "babel-eslint": "^10.0.1", 24 | "babel-loader": "^7.1.5", 25 | "babel-plugin-component": "^1.1.1", 26 | "babel-preset-es2015": "^6.24.1", 27 | "css-loader": "^2.1.0", 28 | "eslint": "^5.12.1", 29 | "eslint-config-enough": "^0.3.4", 30 | "eslint-loader": "^2.1.1", 31 | "file-loader": "^3.0.1", 32 | "html-loader": "^0.5.5", 33 | "html-webpack-plugin": "^2.24.1", 34 | "less": "^3.9.0", 35 | "less-loader": "^4.1.0", 36 | "postcss-loader": "^3.0.0", 37 | "rimraf": "^2.6.3", 38 | "style-loader": "^0.23.1", 39 | "url-loader": "^1.1.2", 40 | "vue-loader": "^15.6.2", 41 | "vue-template-compiler": "^2.5.22", 42 | "webpack": "^2.7.0", 43 | "webpack-cli": "^3.2.1", 44 | "webpack-dev-server": "^3.1.14" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /web/frpc/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')() 4 | ] 5 | } -------------------------------------------------------------------------------- /web/frpc/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 |