├── conf ├── frps.ini ├── frpc.ini └── systemd │ ├── frps.service │ ├── frps@.service │ ├── frpc@.service │ └── frpc.service ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yaml │ └── bug_report.yaml ├── FUNDING.yml └── workflows │ ├── goreleaser.yml │ └── stale.yml ├── Release.md ├── doc └── pic │ ├── zsxq.jpg │ ├── dashboard.png │ ├── architecture.png │ ├── donate-alipay.png │ └── donate-wechatpay.png ├── assets ├── frpc │ ├── static │ │ ├── favicon.ico │ │ ├── 535877f50039c0cb49a6196a5b7517cd.woff │ │ ├── 732389ded34cb9c52dd88271f1345af9.ttf │ │ ├── index.html │ │ └── manifest.js │ └── embed.go ├── frps │ ├── static │ │ ├── favicon.ico │ │ ├── 535877f50039c0cb49a6196a5b7517cd.woff │ │ ├── 732389ded34cb9c52dd88271f1345af9.ttf │ │ ├── index.html │ │ └── manifest.js │ └── embed.go └── assets.go ├── web ├── frpc │ ├── Makefile │ ├── src │ │ ├── assets │ │ │ └── favicon.ico │ │ ├── index.html │ │ ├── utils │ │ │ ├── less │ │ │ │ └── custom.less │ │ │ └── status.js │ │ ├── router │ │ │ └── index.js │ │ ├── main.js │ │ ├── App.vue │ │ └── components │ │ │ └── Overview.vue │ ├── .gitignore │ ├── postcss.config.js │ ├── .babelrc │ └── package.json └── frps │ ├── src │ ├── assets │ │ └── favicon.ico │ ├── index.html │ ├── utils │ │ └── less │ │ │ └── custom.less │ ├── main.js │ ├── components │ │ └── Traffic.vue │ ├── router │ │ └── index.js │ └── App.vue │ ├── .gitignore │ ├── Makefile │ ├── postcss.config.js │ ├── .babelrc │ └── package.json ├── test └── e2e │ ├── mock │ └── server │ │ ├── interface.go │ │ ├── httpserver │ │ └── server.go │ │ └── streamserver │ │ └── server.go │ ├── framework │ ├── client.go │ ├── util.go │ ├── consts │ │ └── consts.go │ ├── test_context.go │ ├── cleanup.go │ ├── ginkgowrapper │ │ └── wrapper.go │ ├── mockservers.go │ └── process.go │ ├── pkg │ ├── utils │ │ └── utils.go │ ├── rpc │ │ └── rpc.go │ ├── process │ │ └── process.go │ ├── port │ │ ├── util.go │ │ └── port.go │ └── cert │ │ └── generator.go │ ├── suites.go │ ├── examples.go │ ├── e2e_test.go │ ├── plugin │ └── utils.go │ ├── features │ ├── bandwidth_limit.go │ ├── monitor.go │ └── chaos.go │ ├── basic │ └── config.go │ └── e2e.go ├── pkg ├── metrics │ ├── metrics.go │ ├── mem │ │ └── types.go │ └── aggregate │ │ └── server.go ├── proto │ └── udp │ │ └── udp_test.go ├── util │ ├── metric │ │ ├── counter_test.go │ │ ├── date_counter_test.go │ │ ├── metrics.go │ │ └── counter.go │ ├── vhost │ │ ├── https_test.go │ │ ├── router.go │ │ ├── resource.go │ │ └── https.go │ ├── util │ │ ├── util_test.go │ │ └── http.go │ ├── xlog │ │ ├── ctx.go │ │ └── xlog.go │ ├── limit │ │ ├── reader.go │ │ └── writer.go │ ├── net │ │ ├── listener.go │ │ ├── tls.go │ │ ├── websocket.go │ │ └── kcp.go │ ├── version │ │ ├── version_test.go │ │ └── version.go │ ├── tcpmux │ │ └── httpconnect.go │ └── log │ │ └── log.go ├── errors │ └── errors.go ├── config │ ├── README.md │ ├── types_test.go │ ├── utils.go │ ├── value.go │ ├── types.go │ └── parse.go ├── plugin │ ├── server │ │ ├── tracer.go │ │ ├── plugin.go │ │ └── types.go │ └── client │ │ ├── socks5.go │ │ ├── unix_domain_socket.go │ │ ├── plugin.go │ │ ├── static_file.go │ │ └── http2https.go ├── msg │ └── ctl.go ├── consts │ └── consts.go └── transport │ └── tls.go ├── dockerfiles ├── Dockerfile-for-frpc └── Dockerfile-for-frps ├── .circleci └── config.yml ├── .gitignore ├── client ├── event │ └── event.go └── admin.go ├── .goreleaser.yml ├── hack └── run-e2e.sh ├── cmd ├── frpc │ ├── main.go │ └── sub │ │ ├── verify.go │ │ ├── reload.go │ │ ├── tcp.go │ │ ├── udp.go │ │ ├── https.go │ │ └── tcpmux.go └── frps │ ├── main.go │ └── verify.go ├── Makefile ├── server ├── group │ └── group.go ├── proxy │ ├── stcp.go │ ├── sudp.go │ ├── tcp.go │ ├── https.go │ ├── xtcp.go │ └── tcpmux.go ├── metrics │ └── metrics.go ├── controller │ └── resource.go ├── dashboard.go └── visitor │ └── visitor.go ├── Makefile.cross-compiles ├── go.mod ├── package.sh └── README_zh.md /conf/frps.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | bind_port = 7000 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /Release.md: -------------------------------------------------------------------------------- 1 | ### Improve 2 | 3 | * Remove authentication for healthz api. 4 | -------------------------------------------------------------------------------- /doc/pic/zsxq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/doc/pic/zsxq.jpg -------------------------------------------------------------------------------- /doc/pic/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/doc/pic/dashboard.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [fatedier] 4 | -------------------------------------------------------------------------------- /doc/pic/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/doc/pic/architecture.png -------------------------------------------------------------------------------- /doc/pic/donate-alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/doc/pic/donate-alipay.png -------------------------------------------------------------------------------- /doc/pic/donate-wechatpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/doc/pic/donate-wechatpay.png -------------------------------------------------------------------------------- /assets/frpc/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/assets/frpc/static/favicon.ico -------------------------------------------------------------------------------- /assets/frps/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/assets/frps/static/favicon.ico -------------------------------------------------------------------------------- /web/frpc/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist build 2 | build: 3 | @npm run build 4 | 5 | dev: 6 | @npm run dev 7 | -------------------------------------------------------------------------------- /web/frpc/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/web/frpc/src/assets/favicon.ico -------------------------------------------------------------------------------- /web/frps/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/web/frps/src/assets/favicon.ico -------------------------------------------------------------------------------- /web/frpc/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | .idea 6 | .vscode/settings.json 7 | -------------------------------------------------------------------------------- /web/frpc/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')() 4 | ] 5 | } -------------------------------------------------------------------------------- /web/frps/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | .idea 6 | .vscode/settings.json 7 | -------------------------------------------------------------------------------- /web/frps/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist build 2 | 3 | build: 4 | @npm run build 5 | 6 | dev: install 7 | @npm run dev 8 | -------------------------------------------------------------------------------- /web/frps/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')() 4 | ] 5 | } -------------------------------------------------------------------------------- /assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff -------------------------------------------------------------------------------- /assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf -------------------------------------------------------------------------------- /assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff -------------------------------------------------------------------------------- /assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/frp/dev/assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /conf/frpc.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | server_addr = 127.0.0.1 3 | server_port = 7000 4 | 5 | [ssh] 6 | type = tcp 7 | local_ip = 127.0.0.1 8 | local_port = 22 9 | remote_port = 6000 10 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/fatedier/frp/pkg/metrics/aggregate" 5 | ) 6 | 7 | var EnableMem = aggregate.EnableMem 8 | var EnablePrometheus = aggregate.EnablePrometheus 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/frpc/embed.go: -------------------------------------------------------------------------------- 1 | package frpc 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/fatedier/frp/assets" 7 | ) 8 | 9 | //go:embed static/* 10 | var content embed.FS 11 | 12 | func init() { 13 | assets.Register(content) 14 | } 15 | -------------------------------------------------------------------------------- /assets/frps/embed.go: -------------------------------------------------------------------------------- 1 | package frpc 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/fatedier/frp/assets" 7 | ) 8 | 9 | //go:embed static/* 10 | var content embed.FS 11 | 12 | func init() { 13 | assets.Register(content) 14 | } 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile-for-frpc: -------------------------------------------------------------------------------- 1 | FROM alpine:3.12.0 AS temp 2 | 3 | COPY bin/frpc /tmp 4 | 5 | RUN chmod -R 777 /tmp/frpc 6 | 7 | 8 | FROM alpine:3.12.0 9 | 10 | WORKDIR /app 11 | 12 | COPY --from=temp /tmp/frpc /usr/bin 13 | 14 | ENTRYPOINT ["/usr/bin/frpc"] 15 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile-for-frps: -------------------------------------------------------------------------------- 1 | FROM alpine:3.12.0 AS temp 2 | 3 | COPY bin/frps /tmp 4 | 5 | RUN chmod -R 777 /tmp/frps 6 | 7 | 8 | FROM alpine:3.12.0 9 | 10 | WORKDIR /app 11 | 12 | COPY --from=temp /tmp/frps /usr/bin 13 | 14 | ENTRYPOINT ["/usr/bin/frps"] 15 | -------------------------------------------------------------------------------- /conf/systemd/frps.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Frp Server Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=nobody 8 | Restart=on-failure 9 | RestartSec=5s 10 | ExecStart=/usr/bin/frps -c /etc/frp/frps.ini 11 | LimitNOFILE=1048576 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /conf/systemd/frps@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Frp Server Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=nobody 8 | Restart=on-failure 9 | RestartSec=5s 10 | ExecStart=/usr/bin/frps -c /etc/frp/%i.ini 11 | LimitNOFILE=1048576 12 | 13 | [Install] 14 | WantedBy=multi-user.target 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/frps/.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 | -------------------------------------------------------------------------------- /assets/frps/static/index.html: -------------------------------------------------------------------------------- 1 | frps dashboard
-------------------------------------------------------------------------------- /assets/frpc/static/index.html: -------------------------------------------------------------------------------- 1 | frp client admin UI
-------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /conf/systemd/frpc@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Frp Client Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=nobody 8 | Restart=on-failure 9 | RestartSec=5s 10 | ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini 11 | ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini 12 | LimitNOFILE=1048576 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /conf/systemd/frpc.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Frp Client Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=nobody 8 | Restart=on-failure 9 | RestartSec=5s 10 | ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini 11 | ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini 12 | LimitNOFILE=1048576 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /pkg/proto/udp/udp_test.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUdpPacket(t *testing.T) { 10 | assert := assert.New(t) 11 | 12 | buf := []byte("hello world") 13 | udpMsg := NewUDPPacket(buf, nil, nil) 14 | 15 | newBuf, err := GetContent(udpMsg) 16 | assert.NoError(err) 17 | assert.EqualValues(buf, newBuf) 18 | } 19 | -------------------------------------------------------------------------------- /web/frps/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frps dashboard 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/frpc/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frp client admin UI 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/frpc/src/utils/less/custom.less: -------------------------------------------------------------------------------- 1 | @color: red; 2 | 3 | .el-form-item { 4 | span { 5 | margin-left: 15px; 6 | } 7 | } 8 | 9 | .demo-table-expand { 10 | font-size: 0; 11 | 12 | label { 13 | width: 90px; 14 | color: #99a9bf; 15 | } 16 | 17 | .el-form-item { 18 | margin-right: 0; 19 | margin-bottom: 0; 20 | width: 50%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/frpc/src/utils/status.js: -------------------------------------------------------------------------------- 1 | class ProxyStatus { 2 | constructor(status) { 3 | this.name = status.name 4 | this.type = status.type 5 | this.status = status.status 6 | this.err = status.err 7 | this.local_addr = status.local_addr 8 | this.plugin = status.plugin 9 | this.remote_addr = status.remote_addr 10 | } 11 | } 12 | 13 | export {ProxyStatus} 14 | -------------------------------------------------------------------------------- /web/frps/src/utils/less/custom.less: -------------------------------------------------------------------------------- 1 | @color: red; 2 | 3 | .el-form-item { 4 | span { 5 | margin-left: 15px; 6 | } 7 | } 8 | 9 | .demo-table-expand { 10 | font-size: 0; 11 | 12 | label { 13 | width: 90px; 14 | color: #99a9bf; 15 | } 16 | 17 | .el-form-item { 18 | margin-right: 0; 19 | margin-bottom: 0; 20 | width: 50%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/util/metric/counter_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCounter(t *testing.T) { 10 | assert := assert.New(t) 11 | c := NewCounter() 12 | c.Inc(10) 13 | assert.EqualValues(10, c.Count()) 14 | 15 | c.Dec(5) 16 | assert.EqualValues(5, c.Count()) 17 | 18 | cTmp := c.Snapshot() 19 | assert.EqualValues(5, cTmp.Count()) 20 | 21 | c.Clear() 22 | assert.EqualValues(0, c.Count()) 23 | } 24 | -------------------------------------------------------------------------------- /web/frpc/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Overview from '../components/Overview.vue' 4 | import Configure from '../components/Configure.vue' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [{ 10 | path: '/', 11 | name: 'Overview', 12 | component: Overview 13 | },{ 14 | path: '/configure', 15 | name: 'Configure', 16 | component: Configure, 17 | }] 18 | }) 19 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | go-version-latest: 4 | docker: 5 | - image: cimg/go:1.17-node 6 | steps: 7 | - checkout 8 | - run: make 9 | - run: make alltest 10 | go-version-last: 11 | docker: 12 | - image: cimg/go:1.16-node 13 | steps: 14 | - checkout 15 | - run: make 16 | - run: make alltest 17 | 18 | workflows: 19 | version: 2 20 | build_and_test: 21 | jobs: 22 | - go-version-latest 23 | - go-version-last 24 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /client/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/fatedier/frp/pkg/msg" 7 | ) 8 | 9 | type Type int 10 | 11 | const ( 12 | EvStartProxy Type = iota 13 | EvCloseProxy 14 | ) 15 | 16 | var ( 17 | ErrPayloadType = errors.New("error payload type") 18 | ) 19 | 20 | type Handler func(evType Type, payload interface{}) error 21 | 22 | type StartProxyPayload struct { 23 | NewProxyMsg *msg.NewProxy 24 | } 25 | 26 | type CloseProxyPayload struct { 27 | CloseProxyMsg *msg.CloseProxy 28 | } 29 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /hack/run-e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd) 4 | 5 | which ginkgo &> /dev/null 6 | if [ $? -ne 0 ]; then 7 | echo "ginkgo not found, try to install..." 8 | go install github.com/onsi/ginkgo/ginkgo@latest 9 | fi 10 | 11 | debug=false 12 | if [ x${DEBUG} == x"true" ]; then 13 | debug=true 14 | fi 15 | logLevel=debug 16 | if [ x${LOG_LEVEL} != x"" ]; then 17 | logLevel=${LOG_LEVEL} 18 | fi 19 | 20 | ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug} 21 | -------------------------------------------------------------------------------- /pkg/util/metric/date_counter_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDateCounter(t *testing.T) { 10 | assert := assert.New(t) 11 | 12 | dc := NewDateCounter(3) 13 | dc.Inc(10) 14 | assert.EqualValues(10, dc.TodayCount()) 15 | 16 | dc.Dec(5) 17 | assert.EqualValues(5, dc.TodayCount()) 18 | 19 | counts := dc.GetLastDaysCount(3) 20 | assert.EqualValues(3, len(counts)) 21 | assert.EqualValues(5, counts[0]) 22 | assert.EqualValues(0, counts[1]) 23 | assert.EqualValues(0, counts[2]) 24 | 25 | dcTmp := dc.Snapshot() 26 | assert.EqualValues(5, dcTmp.TodayCount()) 27 | } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | var ( 22 | ErrMsgType = errors.New("message type error") 23 | ErrCtlClosed = errors.New("control is closed") 24 | ) 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/config/README.md: -------------------------------------------------------------------------------- 1 | So far, there is no mature Go project that does well in parsing `*.ini` files. 2 | 3 | By comparison, we have selected an open source project: `https://github.com/go-ini/ini`. 4 | 5 | This library helped us solve most of the key-value matching, but there are still some problems, such as not supporting parsing `map`. 6 | 7 | We add our own logic on the basis of this library. In the current situationwhich, we need to complete the entire `Unmarshal` in two steps: 8 | 9 | * Step#1, use `go-ini` to complete the basic parameter matching; 10 | * Step#2, parse our custom parameters to realize parsing special structure, like `map`, `array`. 11 | 12 | Some of the keywords in `tag`(like inline, extends, etc.) may be different from standard libraries such as `json` and `protobuf` in Go. For details, please refer to the library documentation: https://ini.unknwon.io/docs/intro. -------------------------------------------------------------------------------- /pkg/util/vhost/https_test.go: -------------------------------------------------------------------------------- 1 | package vhost 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestGetHTTPSHostname(t *testing.T) { 13 | require := require.New(t) 14 | 15 | l, err := net.Listen("tcp", ":") 16 | require.NoError(err) 17 | defer l.Close() 18 | 19 | var conn net.Conn 20 | go func() { 21 | conn, _ = l.Accept() 22 | require.NotNil(conn) 23 | }() 24 | 25 | go func() { 26 | time.Sleep(100 * time.Millisecond) 27 | tls.Dial("tcp", l.Addr().String(), &tls.Config{ 28 | InsecureSkipVerify: true, 29 | ServerName: "example.com", 30 | }) 31 | }() 32 | 33 | time.Sleep(200 * time.Millisecond) 34 | _, infos, err := GetHTTPSHostname(conn) 35 | require.NoError(err) 36 | require.Equal("example.com", infos["Host"]) 37 | require.Equal("https", infos["Scheme"]) 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | goreleaser: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.17 19 | 20 | - run: | 21 | # https://github.com/actions/setup-go/issues/107 22 | cp -f `which go` /usr/bin/go 23 | 24 | - name: Make All 25 | run: | 26 | ./package.sh 27 | 28 | - name: Run GoReleaser 29 | uses: goreleaser/goreleaser-action@v2 30 | with: 31 | version: latest 32 | args: release --rm-dist --release-notes=./Release.md 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }} 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/frpc/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "math/rand" 19 | "time" 20 | 21 | _ "github.com/fatedier/frp/assets/frpc" 22 | "github.com/fatedier/frp/cmd/frpc/sub" 23 | 24 | "github.com/fatedier/golib/crypto" 25 | ) 26 | 27 | func main() { 28 | crypto.DefaultSalt = "frp" 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | sub.Execute() 32 | } 33 | -------------------------------------------------------------------------------- /cmd/frps/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "math/rand" 19 | "time" 20 | 21 | "github.com/fatedier/golib/crypto" 22 | 23 | _ "github.com/fatedier/frp/assets/frps" 24 | _ "github.com/fatedier/frp/pkg/metrics" 25 | ) 26 | 27 | func main() { 28 | crypto.DefaultSalt = "frp" 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | Execute() 32 | } 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 16 | fmt: 17 | go fmt ./... 18 | 19 | frps: 20 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps 21 | 22 | frpc: 23 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc 24 | 25 | test: gotest 26 | 27 | gotest: 28 | go test -v --cover ./assets/... 29 | go test -v --cover ./cmd/... 30 | go test -v --cover ./client/... 31 | go test -v --cover ./server/... 32 | go test -v --cover ./pkg/... 33 | 34 | e2e: 35 | ./hack/run-e2e.sh 36 | 37 | e2e-trace: 38 | DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh 39 | 40 | alltest: gotest e2e 41 | 42 | clean: 43 | rm -f ./bin/frpc 44 | rm -f ./bin/frps 45 | -------------------------------------------------------------------------------- /web/frps/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | //import ElementUI from 'element-ui' 3 | import { 4 | Button, 5 | Form, 6 | FormItem, 7 | Row, 8 | Col, 9 | Table, 10 | TableColumn, 11 | Popover, 12 | Menu, 13 | Submenu, 14 | MenuItem, 15 | Tag 16 | } from 'element-ui' 17 | import lang from 'element-ui/lib/locale/lang/en' 18 | import locale from 'element-ui/lib/locale' 19 | import 'element-ui/lib/theme-chalk/index.css' 20 | import './utils/less/custom.less' 21 | 22 | import App from './App.vue' 23 | import router from './router' 24 | import 'whatwg-fetch' 25 | 26 | locale.use(lang) 27 | 28 | Vue.use(Button) 29 | Vue.use(Form) 30 | Vue.use(FormItem) 31 | Vue.use(Row) 32 | Vue.use(Col) 33 | Vue.use(Table) 34 | Vue.use(TableColumn) 35 | Vue.use(Popover) 36 | Vue.use(Menu) 37 | Vue.use(Submenu) 38 | Vue.use(MenuItem) 39 | Vue.use(Tag) 40 | 41 | Vue.config.productionTip = false 42 | 43 | new Vue({ 44 | el: '#app', 45 | router, 46 | template: '', 47 | components: { App } 48 | }) 49 | -------------------------------------------------------------------------------- /pkg/plugin/server/tracer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | type key int 22 | 23 | const ( 24 | reqidKey key = 0 25 | ) 26 | 27 | func NewReqidContext(ctx context.Context, reqid string) context.Context { 28 | return context.WithValue(ctx, reqidKey, reqid) 29 | } 30 | 31 | func GetReqidFromContext(ctx context.Context) string { 32 | ret, _ := ctx.Value(reqidKey).(string) 33 | return ret 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea to improve frp 3 | title: "[Feature Request] " 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | This is only used to request new product features. 10 | - type: textarea 11 | id: feature-request 12 | attributes: 13 | label: Describe the feature request 14 | description: Tell us what's you want and why it should be added in frp. 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: alternatives 19 | attributes: 20 | label: Describe alternatives you've considered 21 | - type: checkboxes 22 | id: area 23 | attributes: 24 | label: Affected area 25 | options: 26 | - label: "Docs" 27 | - label: "Installation" 28 | - label: "Performance and Scalability" 29 | - label: "Security" 30 | - label: "User Experience" 31 | - label: "Test and Release" 32 | - label: "Developer Infrastructure" 33 | - label: "Client Plugin" 34 | - label: "Server Plugin" 35 | - label: "Extensions" 36 | - label: "Others" 37 | -------------------------------------------------------------------------------- /web/frps/src/components/Traffic.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 34 | 35 | 37 | -------------------------------------------------------------------------------- /pkg/util/metric/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metric 16 | 17 | // GaugeMetric represents a single numerical value that can arbitrarily go up 18 | // and down. 19 | type GaugeMetric interface { 20 | Inc() 21 | Dec() 22 | Set(float64) 23 | } 24 | 25 | // CounterMetric represents a single numerical value that only ever 26 | // goes up. 27 | type CounterMetric interface { 28 | Inc() 29 | } 30 | 31 | // HistogramMetric counts individual observations. 32 | type HistogramMetric interface { 33 | Observe(float64) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/plugin/server/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | const ( 22 | APIVersion = "0.1.0" 23 | 24 | OpLogin = "Login" 25 | OpNewProxy = "NewProxy" 26 | OpPing = "Ping" 27 | OpNewWorkConn = "NewWorkConn" 28 | OpNewUserConn = "NewUserConn" 29 | ) 30 | 31 | type Plugin interface { 32 | Name() string 33 | IsSupport(op string) bool 34 | Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error) 35 | } 36 | -------------------------------------------------------------------------------- /web/frpc/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | // import ElementUI from 'element-ui' 3 | import { 4 | Button, 5 | Form, 6 | FormItem, 7 | Row, 8 | Col, 9 | Table, 10 | TableColumn, 11 | Menu, 12 | MenuItem, 13 | MessageBox, 14 | Message, 15 | Input 16 | } from 'element-ui' 17 | import lang from 'element-ui/lib/locale/lang/en' 18 | import locale from 'element-ui/lib/locale' 19 | import 'element-ui/lib/theme-chalk/index.css' 20 | import './utils/less/custom.less' 21 | 22 | import App from './App.vue' 23 | import router from './router' 24 | import 'whatwg-fetch' 25 | 26 | locale.use(lang) 27 | 28 | Vue.use(Button) 29 | Vue.use(Form) 30 | Vue.use(FormItem) 31 | Vue.use(Row) 32 | Vue.use(Col) 33 | Vue.use(Table) 34 | Vue.use(TableColumn) 35 | Vue.use(Menu) 36 | Vue.use(MenuItem) 37 | Vue.use(Input) 38 | 39 | Vue.prototype.$msgbox = MessageBox; 40 | Vue.prototype.$confirm = MessageBox.confirm 41 | Vue.prototype.$message = Message 42 | 43 | //Vue.use(ElementUI) 44 | 45 | Vue.config.productionTip = false 46 | 47 | new Vue({ 48 | el: '#app', 49 | router, 50 | template: '', 51 | components: { App } 52 | }) 53 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | on: 3 | schedule: 4 | - cron: "20 0 * * *" 5 | workflow_dispatch: 6 | inputs: 7 | debug-only: 8 | description: 'In debug mod' 9 | required: false 10 | default: 'false' 11 | jobs: 12 | stale: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/stale@v3 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.' 19 | stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.' 20 | stale-issue-label: 'lifecycle/stale' 21 | exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' 22 | stale-pr-label: 'lifecycle/stale' 23 | exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' 24 | days-before-stale: 30 25 | days-before-close: 7 26 | debug-only: ${{ github.event.inputs.debug-only }} 27 | -------------------------------------------------------------------------------- /pkg/util/util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRandId(t *testing.T) { 10 | assert := assert.New(t) 11 | id, err := RandID() 12 | assert.NoError(err) 13 | t.Log(id) 14 | assert.Equal(16, len(id)) 15 | } 16 | 17 | func TestGetAuthKey(t *testing.T) { 18 | assert := assert.New(t) 19 | key := GetAuthKey("1234", 1488720000) 20 | t.Log(key) 21 | assert.Equal("6df41a43725f0c770fd56379e12acf8c", key) 22 | } 23 | 24 | func TestParseRangeNumbers(t *testing.T) { 25 | assert := assert.New(t) 26 | numbers, err := ParseRangeNumbers("2-5") 27 | if assert.NoError(err) { 28 | assert.Equal([]int64{2, 3, 4, 5}, numbers) 29 | } 30 | 31 | numbers, err = ParseRangeNumbers("1") 32 | if assert.NoError(err) { 33 | assert.Equal([]int64{1}, numbers) 34 | } 35 | 36 | numbers, err = ParseRangeNumbers("3-5,8") 37 | if assert.NoError(err) { 38 | assert.Equal([]int64{3, 4, 5, 8}, numbers) 39 | } 40 | 41 | numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ") 42 | if assert.NoError(err) { 43 | assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers) 44 | } 45 | 46 | _, err = ParseRangeNumbers("3-a") 47 | assert.Error(err) 48 | } 49 | -------------------------------------------------------------------------------- /test/e2e/plugin/utils.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | 9 | plugin "github.com/fatedier/frp/pkg/plugin/server" 10 | "github.com/fatedier/frp/pkg/util/log" 11 | "github.com/fatedier/frp/test/e2e/mock/server/httpserver" 12 | ) 13 | 14 | type PluginHandler func(req *plugin.Request) *plugin.Response 15 | 16 | type NewPluginRequest func() *plugin.Request 17 | 18 | func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler PluginHandler, tlsConfig *tls.Config) *httpserver.Server { 19 | return httpserver.New( 20 | httpserver.WithBindPort(port), 21 | httpserver.WithTlsConfig(tlsConfig), 22 | httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 23 | r := newFunc() 24 | buf, err := io.ReadAll(req.Body) 25 | if err != nil { 26 | w.WriteHeader(500) 27 | return 28 | } 29 | log.Trace("plugin request: %s", string(buf)) 30 | err = json.Unmarshal(buf, &r) 31 | if err != nil { 32 | w.WriteHeader(500) 33 | return 34 | } 35 | resp := handler(r) 36 | buf, _ = json.Marshal(resp) 37 | log.Trace("plugin response: %s", string(buf)) 38 | w.Write(buf) 39 | })), 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/config/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | type Wrap struct { 25 | B BandwidthQuantity `json:"b"` 26 | Int int `json:"int"` 27 | } 28 | 29 | func TestBandwidthQuantity(t *testing.T) { 30 | assert := assert.New(t) 31 | 32 | var w Wrap 33 | err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w) 34 | assert.NoError(err) 35 | assert.EqualValues(1*KB, w.B.Bytes()) 36 | 37 | buf, err := json.Marshal(&w) 38 | assert.NoError(err) 39 | assert.Equal(`{"b":"1KB","int":5}`, string(buf)) 40 | } 41 | -------------------------------------------------------------------------------- /cmd/frpc/sub/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/fatedier/frp/pkg/config" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func init() { 27 | rootCmd.AddCommand(verifyCmd) 28 | } 29 | 30 | var verifyCmd = &cobra.Command{ 31 | Use: "verify", 32 | Short: "Verify that the configures is valid", 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | _, _, _, err := config.ParseClientConfig(cfgFile) 35 | if err != nil { 36 | fmt.Println(err) 37 | os.Exit(1) 38 | } 39 | 40 | fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile) 41 | return nil 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /pkg/msg/ctl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package msg 16 | 17 | import ( 18 | "io" 19 | 20 | jsonMsg "github.com/fatedier/golib/msg/json" 21 | ) 22 | 23 | type Message = jsonMsg.Message 24 | 25 | var ( 26 | msgCtl *jsonMsg.MsgCtl 27 | ) 28 | 29 | func init() { 30 | msgCtl = jsonMsg.NewMsgCtl() 31 | for typeByte, msg := range msgTypeMap { 32 | msgCtl.RegisterMsg(typeByte, msg) 33 | } 34 | } 35 | 36 | func ReadMsg(c io.Reader) (msg Message, err error) { 37 | return msgCtl.ReadMsg(c) 38 | } 39 | 40 | func ReadMsgInto(c io.Reader, msg Message) (err error) { 41 | return msgCtl.ReadMsgInto(c, msg) 42 | } 43 | 44 | func WriteMsg(c io.Writer, msg interface{}) (err error) { 45 | return msgCtl.WriteMsg(c, msg) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/util/limit/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package limit 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "golang.org/x/time/rate" 22 | ) 23 | 24 | type Reader struct { 25 | r io.Reader 26 | limiter *rate.Limiter 27 | } 28 | 29 | func NewReader(r io.Reader, limiter *rate.Limiter) *Reader { 30 | return &Reader{ 31 | r: r, 32 | limiter: limiter, 33 | } 34 | } 35 | 36 | func (r *Reader) Read(p []byte) (n int, err error) { 37 | b := r.limiter.Burst() 38 | if b < len(p) { 39 | p = p[:b] 40 | } 41 | n, err = r.r.Read(p) 42 | if err != nil { 43 | return 44 | } 45 | 46 | err = r.limiter.WaitN(context.Background(), n) 47 | if err != nil { 48 | return 49 | } 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /pkg/consts/consts.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 consts 16 | 17 | var ( 18 | // proxy status 19 | Idle string = "idle" 20 | Working string = "working" 21 | Closed string = "closed" 22 | Online string = "online" 23 | Offline string = "offline" 24 | 25 | // proxy type 26 | TCPProxy string = "tcp" 27 | UDPProxy string = "udp" 28 | TCPMuxProxy string = "tcpmux" 29 | HTTPProxy string = "http" 30 | HTTPSProxy string = "https" 31 | STCPProxy string = "stcp" 32 | XTCPProxy string = "xtcp" 33 | SUDPProxy string = "sudp" 34 | 35 | // authentication method 36 | TokenAuthMethod string = "token" 37 | OidcAuthMethod string = "oidc" 38 | 39 | // TCP multiplexer 40 | HTTPConnectTCPMultiplexer string = "httpconnect" 41 | ) 42 | -------------------------------------------------------------------------------- /web/frps/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Overview from '../components/Overview.vue' 4 | import ProxiesTcp from '../components/ProxiesTcp.vue' 5 | import ProxiesUdp from '../components/ProxiesUdp.vue' 6 | import ProxiesHttp from '../components/ProxiesHttp.vue' 7 | import ProxiesHttps from '../components/ProxiesHttps.vue' 8 | import ProxiesStcp from '../components/ProxiesStcp.vue' 9 | import ProxiesSudp from '../components/ProxiesSudp.vue' 10 | 11 | Vue.use(Router) 12 | 13 | export default new Router({ 14 | routes: [{ 15 | path: '/', 16 | name: 'Overview', 17 | component: Overview 18 | }, { 19 | path: '/proxies/tcp', 20 | name: 'ProxiesTcp', 21 | component: ProxiesTcp 22 | }, { 23 | path: '/proxies/udp', 24 | name: 'ProxiesUdp', 25 | component: ProxiesUdp 26 | }, { 27 | path: '/proxies/http', 28 | name: 'ProxiesHttp', 29 | component: ProxiesHttp 30 | }, { 31 | path: '/proxies/https', 32 | name: 'ProxiesHttps', 33 | component: ProxiesHttps 34 | }, { 35 | path: '/proxies/stcp', 36 | name: 'ProxiesStcp', 37 | component: ProxiesStcp 38 | }, { 39 | path: '/proxies/sudp', 40 | name: 'ProxiesSudp', 41 | component: ProxiesSudp 42 | }] 43 | }) 44 | -------------------------------------------------------------------------------- /pkg/config/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string { 22 | m := make(map[string]string) 23 | 24 | for key, value := range set { 25 | if strings.HasPrefix(key, prefix) { 26 | m[strings.TrimPrefix(key, prefix)] = value 27 | } 28 | } 29 | 30 | if len(m) == 0 { 31 | return nil 32 | } 33 | 34 | return m 35 | } 36 | 37 | func GetMapByPrefix(set map[string]string, prefix string) map[string]string { 38 | m := make(map[string]string) 39 | 40 | for key, value := range set { 41 | if strings.HasPrefix(key, prefix) { 42 | m[key] = value 43 | } 44 | } 45 | 46 | if len(m) == 0 { 47 | return nil 48 | } 49 | 50 | return m 51 | } 52 | -------------------------------------------------------------------------------- /assets/frpc/static/manifest.js: -------------------------------------------------------------------------------- 1 | !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l=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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/util/limit/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package limit 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "golang.org/x/time/rate" 22 | ) 23 | 24 | type Writer struct { 25 | w io.Writer 26 | limiter *rate.Limiter 27 | } 28 | 29 | func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer { 30 | return &Writer{ 31 | w: w, 32 | limiter: limiter, 33 | } 34 | } 35 | 36 | func (w *Writer) Write(p []byte) (n int, err error) { 37 | var nn int 38 | b := w.limiter.Burst() 39 | for { 40 | end := len(p) 41 | if end == 0 { 42 | break 43 | } 44 | if b < len(p) { 45 | end = b 46 | } 47 | err = w.limiter.WaitN(context.Background(), end) 48 | if err != nil { 49 | return 50 | } 51 | 52 | nn, err = w.w.Write(p[:end]) 53 | n += nn 54 | if err != nil { 55 | return 56 | } 57 | p = p[end:] 58 | } 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/frps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frps-dashboard", 3 | "description": "A dashboard for frp server.", 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 | "bootstrap": "^3.3.7", 12 | "echarts": "^3.5.0", 13 | "element-ui": "^2.3.8", 14 | "humanize-plus": "^1.8.2", 15 | "vue": "^2.5.16", 16 | "vue-resource": "^1.2.1", 17 | "vue-router": "^2.3.0", 18 | "whatwg-fetch": "^2.0.3" 19 | }, 20 | "engines": { 21 | "node": ">=6" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^6.6.0", 25 | "babel-core": "^6.21.0", 26 | "babel-eslint": "^7.1.1", 27 | "babel-loader": "^6.4.0", 28 | "babel-plugin-component": "^1.1.1", 29 | "babel-preset-es2015": "^6.13.2", 30 | "css-loader": "^0.27.0", 31 | "eslint": "^3.12.2", 32 | "eslint-config-enough": "^0.2.2", 33 | "eslint-loader": "^1.6.3", 34 | "file-loader": "^0.10.1", 35 | "html-loader": "^0.4.5", 36 | "html-webpack-plugin": "^2.24.1", 37 | "less": "^3.0.4", 38 | "less-loader": "^4.1.0", 39 | "postcss-loader": "^1.3.3", 40 | "rimraf": "^2.5.4", 41 | "style-loader": "^0.13.2", 42 | "url-loader": "^1.0.1", 43 | "vue-loader": "^15.0.10", 44 | "vue-template-compiler": "^2.1.8", 45 | "webpack": "^2.2.0-rc.4", 46 | "webpack-dev-server": "^3.1.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/frps/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/fatedier/frp/pkg/config" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func init() { 27 | rootCmd.AddCommand(verifyCmd) 28 | } 29 | 30 | var verifyCmd = &cobra.Command{ 31 | Use: "verify", 32 | Short: "Verify that the configures is valid", 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | if cfgFile == "" { 35 | fmt.Println("no config file is specified") 36 | return nil 37 | } 38 | iniContent, err := config.GetRenderedConfFromFile(cfgFile) 39 | if err != nil { 40 | fmt.Println(err) 41 | os.Exit(1) 42 | } 43 | 44 | _, err = parseServerCommonCfg(CfgFileTypeIni, iniContent) 45 | if err != nil { 46 | fmt.Println(err) 47 | os.Exit(1) 48 | } 49 | 50 | fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile) 51 | return nil 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/util/metric/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metric 16 | 17 | import ( 18 | "sync/atomic" 19 | ) 20 | 21 | type Counter interface { 22 | Count() int32 23 | Inc(int32) 24 | Dec(int32) 25 | Snapshot() Counter 26 | Clear() 27 | } 28 | 29 | func NewCounter() Counter { 30 | return &StandardCounter{ 31 | count: 0, 32 | } 33 | } 34 | 35 | type StandardCounter struct { 36 | count int32 37 | } 38 | 39 | func (c *StandardCounter) Count() int32 { 40 | return atomic.LoadInt32(&c.count) 41 | } 42 | 43 | func (c *StandardCounter) Inc(count int32) { 44 | atomic.AddInt32(&c.count, count) 45 | } 46 | 47 | func (c *StandardCounter) Dec(count int32) { 48 | atomic.AddInt32(&c.count, -count) 49 | } 50 | 51 | func (c *StandardCounter) Snapshot() Counter { 52 | tmp := &StandardCounter{ 53 | count: atomic.LoadInt32(&c.count), 54 | } 55 | return tmp 56 | } 57 | 58 | func (c *StandardCounter) Clear() { 59 | atomic.StoreInt32(&c.count, 0) 60 | } 61 | -------------------------------------------------------------------------------- /test/e2e/features/monitor.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/fatedier/frp/pkg/util/log" 9 | "github.com/fatedier/frp/test/e2e/framework" 10 | "github.com/fatedier/frp/test/e2e/framework/consts" 11 | "github.com/fatedier/frp/test/e2e/pkg/request" 12 | 13 | . "github.com/onsi/ginkgo" 14 | ) 15 | 16 | var _ = Describe("[Feature: Monitor]", func() { 17 | f := framework.NewDefaultFramework() 18 | 19 | It("Prometheus metrics", func() { 20 | dashboardPort := f.AllocPort() 21 | serverConf := consts.DefaultServerConfig + fmt.Sprintf(` 22 | enable_prometheus = true 23 | dashboard_addr = 0.0.0.0 24 | dashboard_port = %d 25 | `, dashboardPort) 26 | 27 | clientConf := consts.DefaultClientConfig 28 | remotePort := f.AllocPort() 29 | clientConf += fmt.Sprintf(` 30 | [tcp] 31 | type = tcp 32 | local_port = {{ .%s }} 33 | remote_port = %d 34 | `, framework.TCPEchoServerPort, remotePort) 35 | 36 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 37 | 38 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 39 | time.Sleep(500 * time.Millisecond) 40 | 41 | framework.NewRequestExpect(f).RequestModify(func(r *request.Request) { 42 | r.HTTP().Port(dashboardPort).HTTPPath("/metrics") 43 | }).Ensure(func(resp *request.Response) bool { 44 | log.Trace("prometheus metrics response: \n%s", resp.Content) 45 | if resp.Code != 200 { 46 | return false 47 | } 48 | if !strings.Contains(string(resp.Content), "traffic_in") { 49 | return false 50 | } 51 | return true 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /pkg/util/net/listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "sync" 21 | 22 | "github.com/fatedier/golib/errors" 23 | ) 24 | 25 | // Custom listener 26 | type CustomListener struct { 27 | acceptCh chan net.Conn 28 | closed bool 29 | mu sync.Mutex 30 | } 31 | 32 | func NewCustomListener() *CustomListener { 33 | return &CustomListener{ 34 | acceptCh: make(chan net.Conn, 64), 35 | } 36 | } 37 | 38 | func (l *CustomListener) Accept() (net.Conn, error) { 39 | conn, ok := <-l.acceptCh 40 | if !ok { 41 | return nil, fmt.Errorf("listener closed") 42 | } 43 | return conn, nil 44 | } 45 | 46 | func (l *CustomListener) PutConn(conn net.Conn) error { 47 | err := errors.PanicToError(func() { 48 | select { 49 | case l.acceptCh <- conn: 50 | default: 51 | conn.Close() 52 | } 53 | }) 54 | return err 55 | } 56 | 57 | func (l *CustomListener) Close() error { 58 | l.mu.Lock() 59 | defer l.mu.Unlock() 60 | if !l.closed { 61 | close(l.acceptCh) 62 | l.closed = true 63 | } 64 | return nil 65 | } 66 | 67 | func (l *CustomListener) Addr() net.Addr { 68 | return (*net.TCPAddr)(nil) 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fatedier/frp 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 7 | github.com/coreos/go-oidc v2.2.1+incompatible 8 | github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb 9 | github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 10 | github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible 11 | github.com/go-playground/validator/v10 v10.6.1 12 | github.com/google/uuid v1.2.0 13 | github.com/gorilla/mux v1.8.0 14 | github.com/gorilla/websocket v1.4.2 15 | github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c 16 | github.com/klauspost/cpuid v1.2.0 // indirect 17 | github.com/klauspost/reedsolomon v1.9.1 // indirect 18 | github.com/leodido/go-urn v1.2.1 // indirect 19 | github.com/onsi/ginkgo v1.16.4 20 | github.com/onsi/gomega v1.13.0 21 | github.com/pires/go-proxyproto v0.5.0 22 | github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect 23 | github.com/prometheus/client_golang v1.11.0 24 | github.com/rodaine/table v1.0.1 25 | github.com/spf13/cobra v1.1.3 26 | github.com/stretchr/testify v1.7.0 27 | github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect 28 | github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect 29 | github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect 30 | github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect 31 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 32 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 33 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect 34 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba 35 | gopkg.in/ini.v1 v1.62.0 36 | gopkg.in/square/go-jose.v2 v2.4.1 // indirect 37 | k8s.io/apimachinery v0.21.2 38 | k8s.io/client-go v0.21.2 39 | ) 40 | -------------------------------------------------------------------------------- /pkg/config/value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "bytes" 19 | "os" 20 | "strings" 21 | "text/template" 22 | ) 23 | 24 | var ( 25 | glbEnvs map[string]string 26 | ) 27 | 28 | func init() { 29 | glbEnvs = make(map[string]string) 30 | envs := os.Environ() 31 | for _, env := range envs { 32 | kv := strings.Split(env, "=") 33 | if len(kv) != 2 { 34 | continue 35 | } 36 | glbEnvs[kv[0]] = kv[1] 37 | } 38 | } 39 | 40 | type Values struct { 41 | Envs map[string]string // environment vars 42 | } 43 | 44 | func GetValues() *Values { 45 | return &Values{ 46 | Envs: glbEnvs, 47 | } 48 | } 49 | 50 | func RenderContent(in []byte) (out []byte, err error) { 51 | tmpl, errRet := template.New("frp").Parse(string(in)) 52 | if errRet != nil { 53 | err = errRet 54 | return 55 | } 56 | 57 | buffer := bytes.NewBufferString("") 58 | v := GetValues() 59 | err = tmpl.Execute(buffer, v) 60 | if err != nil { 61 | return 62 | } 63 | out = buffer.Bytes() 64 | return 65 | } 66 | 67 | func GetRenderedConfFromFile(path string) (out []byte, err error) { 68 | var b []byte 69 | b, err = os.ReadFile(path) 70 | if err != nil { 71 | return 72 | } 73 | 74 | out, err = RenderContent(b) 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /pkg/util/version/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestFull(t *testing.T) { 27 | assert := assert.New(t) 28 | version := Full() 29 | arr := strings.Split(version, ".") 30 | assert.Equal(3, len(arr)) 31 | 32 | proto, err := strconv.ParseInt(arr[0], 10, 64) 33 | assert.NoError(err) 34 | assert.True(proto >= 0) 35 | 36 | major, err := strconv.ParseInt(arr[1], 10, 64) 37 | assert.NoError(err) 38 | assert.True(major >= 0) 39 | 40 | minor, err := strconv.ParseInt(arr[2], 10, 64) 41 | assert.NoError(err) 42 | assert.True(minor >= 0) 43 | } 44 | 45 | func TestVersion(t *testing.T) { 46 | assert := assert.New(t) 47 | proto := Proto(Full()) 48 | major := Major(Full()) 49 | minor := Minor(Full()) 50 | parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor) 51 | version := Full() 52 | assert.Equal(parseVerion, version) 53 | } 54 | 55 | func TestCompact(t *testing.T) { 56 | assert := assert.New(t) 57 | ok, _ := Compat("0.9.0") 58 | assert.False(ok) 59 | 60 | ok, _ = Compat("10.0.0") 61 | assert.True(ok) 62 | 63 | ok, _ = Compat("0.10.0") 64 | assert.False(ok) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/plugin/server/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "github.com/fatedier/frp/pkg/msg" 19 | ) 20 | 21 | type Request struct { 22 | Version string `json:"version"` 23 | Op string `json:"op"` 24 | Content interface{} `json:"content"` 25 | } 26 | 27 | type Response struct { 28 | Reject bool `json:"reject"` 29 | RejectReason string `json:"reject_reason"` 30 | Unchange bool `json:"unchange"` 31 | Content interface{} `json:"content"` 32 | } 33 | 34 | type LoginContent struct { 35 | msg.Login 36 | } 37 | 38 | type UserInfo struct { 39 | User string `json:"user"` 40 | Metas map[string]string `json:"metas"` 41 | RunID string `json:"run_id"` 42 | } 43 | 44 | type NewProxyContent struct { 45 | User UserInfo `json:"user"` 46 | msg.NewProxy 47 | } 48 | 49 | type PingContent struct { 50 | User UserInfo `json:"user"` 51 | msg.Ping 52 | } 53 | 54 | type NewWorkConnContent struct { 55 | User UserInfo `json:"user"` 56 | msg.NewWorkConn 57 | } 58 | 59 | type NewUserConnContent struct { 60 | User UserInfo `json:"user"` 61 | ProxyName string `json:"proxy_name"` 62 | ProxyType string `json:"proxy_type"` 63 | RemoteAddr string `json:"remote_addr"` 64 | } 65 | -------------------------------------------------------------------------------- /package.sh: -------------------------------------------------------------------------------- 1 | # compile for version 2 | make 3 | if [ $? -ne 0 ]; then 4 | echo "make error" 5 | exit 1 6 | fi 7 | 8 | frp_version=`./bin/frps --version` 9 | echo "build version: $frp_version" 10 | 11 | # cross_compiles 12 | make -f ./Makefile.cross-compiles 13 | 14 | rm -rf ./release/packages 15 | mkdir -p ./release/packages 16 | 17 | os_all='linux windows darwin freebsd' 18 | arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle' 19 | 20 | cd ./release 21 | 22 | for os in $os_all; do 23 | for arch in $arch_all; do 24 | frp_dir_name="frp_${frp_version}_${os}_${arch}" 25 | frp_path="./packages/frp_${frp_version}_${os}_${arch}" 26 | 27 | if [ "x${os}" = x"windows" ]; then 28 | if [ ! -f "./frpc_${os}_${arch}.exe" ]; then 29 | continue 30 | fi 31 | if [ ! -f "./frps_${os}_${arch}.exe" ]; then 32 | continue 33 | fi 34 | mkdir ${frp_path} 35 | mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe 36 | mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe 37 | else 38 | if [ ! -f "./frpc_${os}_${arch}" ]; then 39 | continue 40 | fi 41 | if [ ! -f "./frps_${os}_${arch}" ]; then 42 | continue 43 | fi 44 | mkdir ${frp_path} 45 | mv ./frpc_${os}_${arch} ${frp_path}/frpc 46 | mv ./frps_${os}_${arch} ${frp_path}/frps 47 | fi 48 | cp ../LICENSE ${frp_path} 49 | cp -rf ../conf/* ${frp_path} 50 | 51 | # packages 52 | cd ./packages 53 | if [ "x${os}" = x"windows" ]; then 54 | zip -rq ${frp_dir_name}.zip ${frp_dir_name} 55 | else 56 | tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name} 57 | fi 58 | cd .. 59 | rm -rf ${frp_path} 60 | done 61 | done 62 | 63 | cd - 64 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /pkg/plugin/client/socks5.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "io" 19 | "log" 20 | "net" 21 | 22 | frpNet "github.com/fatedier/frp/pkg/util/net" 23 | 24 | gosocks5 "github.com/armon/go-socks5" 25 | ) 26 | 27 | const PluginSocks5 = "socks5" 28 | 29 | func init() { 30 | Register(PluginSocks5, NewSocks5Plugin) 31 | } 32 | 33 | type Socks5Plugin struct { 34 | Server *gosocks5.Server 35 | 36 | user string 37 | passwd string 38 | } 39 | 40 | func NewSocks5Plugin(params map[string]string) (p Plugin, err error) { 41 | user := params["plugin_user"] 42 | passwd := params["plugin_passwd"] 43 | 44 | cfg := &gosocks5.Config{ 45 | Logger: log.New(io.Discard, "", log.LstdFlags), 46 | } 47 | if user != "" || passwd != "" { 48 | cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd}) 49 | } 50 | sp := &Socks5Plugin{} 51 | sp.Server, err = gosocks5.New(cfg) 52 | p = sp 53 | return 54 | } 55 | 56 | func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { 57 | defer conn.Close() 58 | wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) 59 | sp.Server.ServeConn(wrapConn) 60 | } 61 | 62 | func (sp *Socks5Plugin) Name() string { 63 | return PluginSocks5 64 | } 65 | 66 | func (sp *Socks5Plugin) Close() error { 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /pkg/util/net/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "crypto/tls" 19 | "fmt" 20 | "net" 21 | "time" 22 | 23 | gnet "github.com/fatedier/golib/net" 24 | ) 25 | 26 | var ( 27 | FRPTLSHeadByte = 0x17 28 | ) 29 | 30 | func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (out net.Conn) { 31 | if !disableCustomTLSHeadByte { 32 | c.Write([]byte{byte(FRPTLSHeadByte)}) 33 | } 34 | out = tls.Client(c, tlsConfig) 35 | return 36 | } 37 | 38 | func CheckAndEnableTLSServerConnWithTimeout( 39 | c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration, 40 | ) (out net.Conn, isTLS bool, custom bool, err error) { 41 | 42 | sc, r := gnet.NewSharedConnSize(c, 2) 43 | buf := make([]byte, 1) 44 | var n int 45 | c.SetReadDeadline(time.Now().Add(timeout)) 46 | n, err = r.Read(buf) 47 | c.SetReadDeadline(time.Time{}) 48 | if err != nil { 49 | return 50 | } 51 | 52 | if n == 1 && int(buf[0]) == FRPTLSHeadByte { 53 | out = tls.Server(c, tlsConfig) 54 | isTLS = true 55 | custom = true 56 | } else if n == 1 && int(buf[0]) == 0x16 { 57 | out = tls.Server(sc, tlsConfig) 58 | isTLS = true 59 | } else { 60 | if tlsOnly { 61 | err = fmt.Errorf("non-TLS connection received on a TlsOnly server") 62 | return 63 | } 64 | out = sc 65 | } 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /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/pkg/cert/generator.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "time" 8 | ) 9 | 10 | // Artifacts hosts a private key, its corresponding serving certificate and 11 | // the CA certificate that signs the serving certificate. 12 | type Artifacts struct { 13 | // PEM encoded private key 14 | Key []byte 15 | // PEM encoded serving certificate 16 | Cert []byte 17 | // PEM encoded CA private key 18 | CAKey []byte 19 | // PEM encoded CA certificate 20 | CACert []byte 21 | // Resource version of the certs 22 | ResourceVersion string 23 | } 24 | 25 | // CertGenerator is an interface to provision the serving certificate. 26 | type CertGenerator interface { 27 | // Generate returns a Artifacts struct. 28 | Generate(CommonName string) (*Artifacts, error) 29 | // SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert. 30 | SetCA(caKey, caCert []byte) 31 | } 32 | 33 | // ValidCACert think cert and key are valid if they meet the following requirements: 34 | // - key and cert are valid pair 35 | // - caCert is the root ca of cert 36 | // - cert is for dnsName 37 | // - cert won't expire before time 38 | func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool { 39 | if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 { 40 | return false 41 | } 42 | // Verify key and cert are valid pair 43 | _, err := tls.X509KeyPair(cert, key) 44 | if err != nil { 45 | return false 46 | } 47 | 48 | // Verify cert is valid for at least 1 year. 49 | pool := x509.NewCertPool() 50 | if !pool.AppendCertsFromPEM(caCert) { 51 | return false 52 | } 53 | block, _ := pem.Decode(cert) 54 | if block == nil { 55 | return false 56 | } 57 | c, err := x509.ParseCertificate(block.Bytes) 58 | if err != nil { 59 | return false 60 | } 61 | ops := x509.VerifyOptions{ 62 | DNSName: dnsName, 63 | Roots: pool, 64 | CurrentTime: time, 65 | } 66 | _, err = c.Verify(ops) 67 | return err == nil 68 | } 69 | -------------------------------------------------------------------------------- /pkg/plugin/client/unix_domain_socket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "net" 21 | 22 | frpIo "github.com/fatedier/golib/io" 23 | ) 24 | 25 | const PluginUnixDomainSocket = "unix_domain_socket" 26 | 27 | func init() { 28 | Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin) 29 | } 30 | 31 | type UnixDomainSocketPlugin struct { 32 | UnixAddr *net.UnixAddr 33 | } 34 | 35 | func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) { 36 | unixPath, ok := params["plugin_unix_path"] 37 | if !ok { 38 | err = fmt.Errorf("plugin_unix_path not found") 39 | return 40 | } 41 | 42 | unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath) 43 | if errRet != nil { 44 | err = errRet 45 | return 46 | } 47 | 48 | p = &UnixDomainSocketPlugin{ 49 | UnixAddr: unixAddr, 50 | } 51 | return 52 | } 53 | 54 | func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { 55 | localConn, err := net.DialUnix("unix", nil, uds.UnixAddr) 56 | if err != nil { 57 | return 58 | } 59 | if len(extraBufToLocal) > 0 { 60 | localConn.Write(extraBufToLocal) 61 | } 62 | 63 | frpIo.Join(localConn, conn) 64 | } 65 | 66 | func (uds *UnixDomainSocketPlugin) Name() string { 67 | return PluginUnixDomainSocket 68 | } 69 | 70 | func (uds *UnixDomainSocketPlugin) Close() error { 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/util/tcpmux/httpconnect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 guylewin, guy@lewin.co.il 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tcpmux 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "io" 21 | "net" 22 | "net/http" 23 | "time" 24 | 25 | "github.com/fatedier/frp/pkg/util/util" 26 | "github.com/fatedier/frp/pkg/util/vhost" 27 | ) 28 | 29 | type HTTPConnectTCPMuxer struct { 30 | *vhost.Muxer 31 | } 32 | 33 | func NewHTTPConnectTCPMuxer(listener net.Listener, timeout time.Duration) (*HTTPConnectTCPMuxer, error) { 34 | mux, err := vhost.NewMuxer(listener, getHostFromHTTPConnect, nil, sendHTTPOk, nil, timeout) 35 | return &HTTPConnectTCPMuxer{mux}, err 36 | } 37 | 38 | func readHTTPConnectRequest(rd io.Reader) (host string, err error) { 39 | bufioReader := bufio.NewReader(rd) 40 | 41 | req, err := http.ReadRequest(bufioReader) 42 | if err != nil { 43 | return 44 | } 45 | 46 | if req.Method != "CONNECT" { 47 | err = fmt.Errorf("connections to tcp vhost must be of method CONNECT") 48 | return 49 | } 50 | 51 | host = util.GetHostFromAddr(req.Host) 52 | return 53 | } 54 | 55 | func sendHTTPOk(c net.Conn) error { 56 | return util.OkResponse().Write(c) 57 | } 58 | 59 | func getHostFromHTTPConnect(c net.Conn) (_ net.Conn, _ map[string]string, err error) { 60 | reqInfoMap := make(map[string]string, 0) 61 | host, err := readHTTPConnectRequest(c) 62 | if err != nil { 63 | return nil, reqInfoMap, err 64 | } 65 | reqInfoMap["Host"] = host 66 | reqInfoMap["Scheme"] = "tcp" 67 | return c, reqInfoMap, nil 68 | } 69 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/frpc/src/App.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 40 | 41 | 74 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # frp 2 | 3 | [![Build Status](https://travis-ci.org/fatedier/frp.svg?branch=master)](https://travis-ci.org/fatedier/frp) 4 | [![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases) 5 | 6 | [README](README.md) | [中文文档](README_zh.md) 7 | 8 | frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 9 | 10 | ## 为什么使用 frp ? 11 | 12 | 通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括: 13 | 14 | * 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。 15 | * 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。 16 | * 代理组间的负载均衡。 17 | * 端口复用,多个服务通过同一个服务端端口暴露。 18 | * 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。 19 | * 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。 20 | * 服务端和客户端 UI 页面。 21 | 22 | ## 开发状态 23 | 24 | frp 目前已被很多公司广泛用于测试、生产环境。 25 | 26 | master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。 27 | 28 | 我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。 29 | 30 | 现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。 31 | 32 | ## 文档 33 | 34 | 完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。 35 | 36 | ## 为 frp 做贡献 37 | 38 | frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。 39 | 40 | * 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。 41 | * Bug 的修复可以直接提交 Pull Request 到 dev 分支。 42 | * 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。 43 | * 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。 44 | * 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。 45 | * 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。 46 | 47 | **提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。** 48 | 49 | ## 捐助 50 | 51 | 如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 52 | 53 | ### 知识星球 54 | 55 | 如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群: 56 | 57 | ![zsxq](/doc/pic/zsxq.jpg) 58 | 59 | ### 支付宝扫码捐赠 60 | 61 | ![donate-alipay](/doc/pic/donate-alipay.png) 62 | 63 | ### 微信支付捐赠 64 | 65 | ![donate-wechatpay](/doc/pic/donate-wechatpay.png) 66 | 67 | ### Paypal 捐赠 68 | 69 | 海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。 70 | -------------------------------------------------------------------------------- /pkg/util/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | import ( 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | var version string = "0.38.1" 23 | 24 | func Full() string { 25 | return version 26 | } 27 | 28 | func getSubVersion(v string, position int) int64 { 29 | arr := strings.Split(v, ".") 30 | if len(arr) < 3 { 31 | return 0 32 | } 33 | res, _ := strconv.ParseInt(arr[position], 10, 64) 34 | return res 35 | } 36 | 37 | func Proto(v string) int64 { 38 | return getSubVersion(v, 0) 39 | } 40 | 41 | func Major(v string) int64 { 42 | return getSubVersion(v, 1) 43 | } 44 | 45 | func Minor(v string) int64 { 46 | return getSubVersion(v, 2) 47 | } 48 | 49 | // add every case there if server will not accept client's protocol and return false 50 | func Compat(client string) (ok bool, msg string) { 51 | if LessThan(client, "0.18.0") { 52 | return false, "Please upgrade your frpc version to at least 0.18.0" 53 | } 54 | return true, "" 55 | } 56 | 57 | func LessThan(client string, server string) bool { 58 | vc := Proto(client) 59 | vs := Proto(server) 60 | if vc > vs { 61 | return false 62 | } else if vc < vs { 63 | return true 64 | } 65 | 66 | vc = Major(client) 67 | vs = Major(server) 68 | if vc > vs { 69 | return false 70 | } else if vc < vs { 71 | return true 72 | } 73 | 74 | vc = Minor(client) 75 | vs = Minor(server) 76 | if vc > vs { 77 | return false 78 | } else if vc < vs { 79 | return true 80 | } 81 | return false 82 | } 83 | -------------------------------------------------------------------------------- /pkg/util/util/http.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 util 16 | 17 | import ( 18 | "net" 19 | "net/http" 20 | "strings" 21 | ) 22 | 23 | func OkResponse() *http.Response { 24 | header := make(http.Header) 25 | 26 | res := &http.Response{ 27 | Status: "OK", 28 | StatusCode: 200, 29 | Proto: "HTTP/1.1", 30 | ProtoMajor: 1, 31 | ProtoMinor: 1, 32 | Header: header, 33 | } 34 | return res 35 | } 36 | 37 | // TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func. 38 | func GetHostFromAddr(addr string) (host string) { 39 | strs := strings.Split(addr, ":") 40 | if len(strs) > 1 { 41 | host = strs[0] 42 | } else { 43 | host = addr 44 | } 45 | return 46 | } 47 | 48 | // canonicalHost strips port from host if present and returns the canonicalized 49 | // host name. 50 | func CanonicalHost(host string) (string, error) { 51 | var err error 52 | host = strings.ToLower(host) 53 | if hasPort(host) { 54 | host, _, err = net.SplitHostPort(host) 55 | if err != nil { 56 | return "", err 57 | } 58 | } 59 | if strings.HasSuffix(host, ".") { 60 | // Strip trailing dot from fully qualified domain names. 61 | host = host[:len(host)-1] 62 | } 63 | return host, nil 64 | } 65 | 66 | // hasPort reports whether host contains a port number. host may be a host 67 | // name, an IPv4 or an IPv6 address. 68 | func hasPort(host string) bool { 69 | colons := strings.Count(host, ":") 70 | if colons == 0 { 71 | return false 72 | } 73 | if colons == 1 { 74 | return true 75 | } 76 | return host[0] == '[' && strings.Contains(host, "]:") 77 | } 78 | -------------------------------------------------------------------------------- /pkg/metrics/mem/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mem 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/fatedier/frp/pkg/util/metric" 21 | ) 22 | 23 | const ( 24 | ReserveDays = 7 25 | ) 26 | 27 | type ServerStats struct { 28 | TotalTrafficIn int64 29 | TotalTrafficOut int64 30 | CurConns int64 31 | ClientCounts int64 32 | ProxyTypeCounts map[string]int64 33 | } 34 | 35 | type ProxyStats struct { 36 | Name string 37 | Type string 38 | TodayTrafficIn int64 39 | TodayTrafficOut int64 40 | LastStartTime string 41 | LastCloseTime string 42 | CurConns int64 43 | } 44 | 45 | type ProxyTrafficInfo struct { 46 | Name string 47 | TrafficIn []int64 48 | TrafficOut []int64 49 | } 50 | 51 | type ProxyStatistics struct { 52 | Name string 53 | ProxyType string 54 | TrafficIn metric.DateCounter 55 | TrafficOut metric.DateCounter 56 | CurConns metric.Counter 57 | LastStartTime time.Time 58 | LastCloseTime time.Time 59 | } 60 | 61 | type ServerStatistics struct { 62 | TotalTrafficIn metric.DateCounter 63 | TotalTrafficOut metric.DateCounter 64 | CurConns metric.Counter 65 | 66 | // counter for clients 67 | ClientCounts metric.Counter 68 | 69 | // counter for proxy types 70 | ProxyTypeCounts map[string]metric.Counter 71 | 72 | // statistics for different proxies 73 | // key is proxy name 74 | ProxyStatistics map[string]*ProxyStatistics 75 | } 76 | 77 | type Collector interface { 78 | GetServer() *ServerStats 79 | GetProxiesByType(proxyType string) []*ProxyStats 80 | GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats 81 | GetProxyTraffic(name string) *ProxyTrafficInfo 82 | } 83 | -------------------------------------------------------------------------------- /cmd/frpc/sub/reload.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 sub 16 | 17 | import ( 18 | "encoding/base64" 19 | "fmt" 20 | "io" 21 | "net/http" 22 | "os" 23 | "strings" 24 | 25 | "github.com/fatedier/frp/pkg/config" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | func init() { 31 | rootCmd.AddCommand(reloadCmd) 32 | } 33 | 34 | var reloadCmd = &cobra.Command{ 35 | Use: "reload", 36 | Short: "Hot-Reload frpc configuration", 37 | RunE: func(cmd *cobra.Command, args []string) error { 38 | cfg, _, _, err := config.ParseClientConfig(cfgFile) 39 | if err != nil { 40 | fmt.Println(err) 41 | os.Exit(1) 42 | } 43 | 44 | err = reload(cfg) 45 | if err != nil { 46 | fmt.Printf("frpc reload error: %v\n", err) 47 | os.Exit(1) 48 | } 49 | fmt.Printf("reload success\n") 50 | return nil 51 | }, 52 | } 53 | 54 | func reload(clientCfg config.ClientCommonConf) error { 55 | if clientCfg.AdminPort == 0 { 56 | return fmt.Errorf("admin_port shoud be set if you want to use reload feature") 57 | } 58 | 59 | req, err := http.NewRequest("GET", "http://"+ 60 | clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+ 66 | clientCfg.AdminPwd)) 67 | 68 | req.Header.Add("Authorization", authStr) 69 | resp, err := http.DefaultClient.Do(req) 70 | if err != nil { 71 | return err 72 | } 73 | defer resp.Body.Close() 74 | 75 | if resp.StatusCode == 200 { 76 | return nil 77 | } 78 | 79 | body, err := io.ReadAll(resp.Body) 80 | if err != nil { 81 | return err 82 | } 83 | return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body))) 84 | } 85 | -------------------------------------------------------------------------------- /test/e2e/mock/server/httpserver/server.go: -------------------------------------------------------------------------------- 1 | package httpserver 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | type Server struct { 12 | bindAddr string 13 | bindPort int 14 | handler http.Handler 15 | 16 | l net.Listener 17 | tlsConfig *tls.Config 18 | hs *http.Server 19 | } 20 | 21 | type Option func(*Server) *Server 22 | 23 | func New(options ...Option) *Server { 24 | s := &Server{ 25 | bindAddr: "127.0.0.1", 26 | } 27 | 28 | for _, option := range options { 29 | s = option(s) 30 | } 31 | return s 32 | } 33 | 34 | func WithBindAddr(addr string) Option { 35 | return func(s *Server) *Server { 36 | s.bindAddr = addr 37 | return s 38 | } 39 | } 40 | 41 | func WithBindPort(port int) Option { 42 | return func(s *Server) *Server { 43 | s.bindPort = port 44 | return s 45 | } 46 | } 47 | 48 | func WithTlsConfig(tlsConfig *tls.Config) Option { 49 | return func(s *Server) *Server { 50 | s.tlsConfig = tlsConfig 51 | return s 52 | } 53 | } 54 | 55 | func WithHandler(h http.Handler) Option { 56 | return func(s *Server) *Server { 57 | s.handler = h 58 | return s 59 | } 60 | } 61 | 62 | func WithResponse(resp []byte) Option { 63 | return func(s *Server) *Server { 64 | s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 65 | w.Write(resp) 66 | }) 67 | return s 68 | } 69 | } 70 | 71 | func (s *Server) Run() error { 72 | if err := s.initListener(); err != nil { 73 | return err 74 | } 75 | 76 | addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)) 77 | hs := &http.Server{ 78 | Addr: addr, 79 | Handler: s.handler, 80 | TLSConfig: s.tlsConfig, 81 | } 82 | 83 | s.hs = hs 84 | if s.tlsConfig == nil { 85 | go hs.Serve(s.l) 86 | } else { 87 | go hs.ServeTLS(s.l, "", "") 88 | } 89 | return nil 90 | } 91 | 92 | func (s *Server) Close() error { 93 | if s.hs != nil { 94 | return s.hs.Close() 95 | } 96 | return nil 97 | } 98 | 99 | func (s *Server) initListener() (err error) { 100 | s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort)) 101 | return 102 | } 103 | 104 | func (s *Server) BindAddr() string { 105 | return s.bindAddr 106 | } 107 | 108 | func (s *Server) BindPort() int { 109 | return s.bindPort 110 | } 111 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug to help us improve frp 3 | 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: Bug Description 13 | description: Tell us what issues you ran into 14 | placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better! 15 | validations: 16 | required: true 17 | - type: input 18 | id: frpc-version 19 | attributes: 20 | label: frpc Version 21 | description: Include the output of `frpc -v` 22 | validations: 23 | required: true 24 | - type: input 25 | id: frps-version 26 | attributes: 27 | label: frps Version 28 | description: Include the output of `frps -v` 29 | validations: 30 | required: true 31 | - type: input 32 | id: system-architecture 33 | attributes: 34 | label: System Architecture 35 | description: Include which architecture you used, such as `linux/amd64`, `windows/amd64` 36 | validations: 37 | required: true 38 | - type: textarea 39 | id: config 40 | attributes: 41 | label: Configurations 42 | description: Include what configurrations you used and ran into this problem 43 | placeholder: Pay attention to hiding the token and password in your output 44 | validations: 45 | required: true 46 | - type: textarea 47 | id: log 48 | attributes: 49 | label: Logs 50 | description: Prefer you providing releated error logs here 51 | placeholder: Pay attention to hiding your personal informations 52 | - type: textarea 53 | id: steps-to-reproduce 54 | attributes: 55 | label: Steps to reproduce 56 | description: How to reproduce it? It's important for us to find the bug 57 | value: | 58 | 1. 59 | 2. 60 | 3. 61 | ... 62 | - type: checkboxes 63 | id: area 64 | attributes: 65 | label: Affected area 66 | options: 67 | - label: "Docs" 68 | - label: "Installation" 69 | - label: "Performance and Scalability" 70 | - label: "Security" 71 | - label: "User Experience" 72 | - label: "Test and Release" 73 | - label: "Developer Infrastructure" 74 | - label: "Client Plugin" 75 | - label: "Server Plugin" 76 | - label: "Extensions" 77 | - label: "Others" 78 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/plugin/client/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "net" 21 | "sync" 22 | 23 | "github.com/fatedier/golib/errors" 24 | ) 25 | 26 | // Creators is used for create plugins to handle connections. 27 | var creators = make(map[string]CreatorFn) 28 | 29 | // params has prefix "plugin_" 30 | type CreatorFn func(params map[string]string) (Plugin, error) 31 | 32 | func Register(name string, fn CreatorFn) { 33 | creators[name] = fn 34 | } 35 | 36 | func Create(name string, params map[string]string) (p Plugin, err error) { 37 | if fn, ok := creators[name]; ok { 38 | p, err = fn(params) 39 | } else { 40 | err = fmt.Errorf("plugin [%s] is not registered", name) 41 | } 42 | return 43 | } 44 | 45 | type Plugin interface { 46 | Name() string 47 | 48 | // extraBufToLocal will send to local connection first, then join conn with local connection 49 | Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) 50 | Close() error 51 | } 52 | 53 | type Listener struct { 54 | conns chan net.Conn 55 | closed bool 56 | mu sync.Mutex 57 | } 58 | 59 | func NewProxyListener() *Listener { 60 | return &Listener{ 61 | conns: make(chan net.Conn, 64), 62 | } 63 | } 64 | 65 | func (l *Listener) Accept() (net.Conn, error) { 66 | conn, ok := <-l.conns 67 | if !ok { 68 | return nil, fmt.Errorf("listener closed") 69 | } 70 | return conn, nil 71 | } 72 | 73 | func (l *Listener) PutConn(conn net.Conn) error { 74 | err := errors.PanicToError(func() { 75 | l.conns <- conn 76 | }) 77 | return err 78 | } 79 | 80 | func (l *Listener) Close() error { 81 | l.mu.Lock() 82 | defer l.mu.Unlock() 83 | if !l.closed { 84 | close(l.conns) 85 | l.closed = true 86 | } 87 | return nil 88 | } 89 | 90 | func (l *Listener) Addr() net.Addr { 91 | return (*net.TCPAddr)(nil) 92 | } 93 | -------------------------------------------------------------------------------- /client/admin.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 client 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 | ) 27 | 28 | var ( 29 | httpServerReadTimeout = 10 * time.Second 30 | httpServerWriteTimeout = 10 * time.Second 31 | ) 32 | 33 | func (svr *Service) RunAdminServer(address string) (err error) { 34 | // url router 35 | router := mux.NewRouter() 36 | 37 | router.HandleFunc("/healthz", svr.healthz) 38 | 39 | subRouter := router.NewRoute().Subrouter() 40 | user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd 41 | subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware) 42 | 43 | // api, see admin_api.go 44 | subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET") 45 | subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET") 46 | subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET") 47 | subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT") 48 | 49 | // view 50 | subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET") 51 | subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET") 52 | subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 53 | http.Redirect(w, r, "/static/", http.StatusMovedPermanently) 54 | }) 55 | 56 | server := &http.Server{ 57 | Addr: address, 58 | Handler: router, 59 | ReadTimeout: httpServerReadTimeout, 60 | WriteTimeout: httpServerWriteTimeout, 61 | } 62 | if address == "" { 63 | address = ":http" 64 | } 65 | ln, err := net.Listen("tcp", address) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | go server.Serve(ln) 71 | return 72 | } 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/frpc/sub/tcp.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 sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | 23 | "github.com/fatedier/frp/pkg/config" 24 | "github.com/fatedier/frp/pkg/consts" 25 | ) 26 | 27 | func init() { 28 | RegisterCommonFlags(tcpCmd) 29 | 30 | tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") 31 | tcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip") 32 | tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") 33 | tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port") 34 | tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") 35 | tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") 36 | 37 | rootCmd.AddCommand(tcpCmd) 38 | } 39 | 40 | var tcpCmd = &cobra.Command{ 41 | Use: "tcp", 42 | Short: "Run frpc with a single tcp proxy", 43 | RunE: func(cmd *cobra.Command, args []string) error { 44 | clientCfg, err := parseClientCommonCfgFromCmd() 45 | if err != nil { 46 | fmt.Println(err) 47 | os.Exit(1) 48 | } 49 | 50 | cfg := &config.TCPProxyConf{} 51 | var prefix string 52 | if user != "" { 53 | prefix = user + "." 54 | } 55 | cfg.ProxyName = prefix + proxyName 56 | cfg.ProxyType = consts.TCPProxy 57 | cfg.LocalIP = localIP 58 | cfg.LocalPort = localPort 59 | cfg.RemotePort = remotePort 60 | cfg.UseEncryption = useEncryption 61 | cfg.UseCompression = useCompression 62 | 63 | err = cfg.CheckForCli() 64 | if err != nil { 65 | fmt.Println(err) 66 | os.Exit(1) 67 | } 68 | 69 | proxyConfs := map[string]config.ProxyConf{ 70 | cfg.ProxyName: cfg, 71 | } 72 | err = startService(clientCfg, proxyConfs, nil, "") 73 | if err != nil { 74 | fmt.Println(err) 75 | os.Exit(1) 76 | } 77 | return nil 78 | }, 79 | } 80 | -------------------------------------------------------------------------------- /cmd/frpc/sub/udp.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 sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/fatedier/frp/pkg/config" 22 | "github.com/fatedier/frp/pkg/consts" 23 | 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | func init() { 28 | RegisterCommonFlags(udpCmd) 29 | 30 | udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") 31 | udpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip") 32 | udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") 33 | udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port") 34 | udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") 35 | udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") 36 | 37 | rootCmd.AddCommand(udpCmd) 38 | } 39 | 40 | var udpCmd = &cobra.Command{ 41 | Use: "udp", 42 | Short: "Run frpc with a single udp proxy", 43 | RunE: func(cmd *cobra.Command, args []string) error { 44 | clientCfg, err := parseClientCommonCfgFromCmd() 45 | if err != nil { 46 | fmt.Println(err) 47 | os.Exit(1) 48 | } 49 | 50 | cfg := &config.UDPProxyConf{} 51 | var prefix string 52 | if user != "" { 53 | prefix = user + "." 54 | } 55 | cfg.ProxyName = prefix + proxyName 56 | cfg.ProxyType = consts.UDPProxy 57 | cfg.LocalIP = localIP 58 | cfg.LocalPort = localPort 59 | cfg.RemotePort = remotePort 60 | cfg.UseEncryption = useEncryption 61 | cfg.UseCompression = useCompression 62 | 63 | err = cfg.CheckForCli() 64 | if err != nil { 65 | fmt.Println(err) 66 | os.Exit(1) 67 | } 68 | 69 | proxyConfs := map[string]config.ProxyConf{ 70 | cfg.ProxyName: cfg, 71 | } 72 | err = startService(clientCfg, proxyConfs, nil, "") 73 | if err != nil { 74 | fmt.Println(err) 75 | os.Exit(1) 76 | } 77 | return nil 78 | }, 79 | } 80 | -------------------------------------------------------------------------------- /pkg/util/net/websocket.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | 11 | "golang.org/x/net/websocket" 12 | ) 13 | 14 | var ( 15 | ErrWebsocketListenerClosed = errors.New("websocket listener closed") 16 | ) 17 | 18 | const ( 19 | FrpWebsocketPath = "/~!frp" 20 | ) 21 | 22 | type WebsocketListener struct { 23 | ln net.Listener 24 | acceptCh chan net.Conn 25 | 26 | server *http.Server 27 | httpMutex *http.ServeMux 28 | } 29 | 30 | // NewWebsocketListener to handle websocket connections 31 | // ln: tcp listener for websocket connections 32 | func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { 33 | wl = &WebsocketListener{ 34 | acceptCh: make(chan net.Conn), 35 | } 36 | 37 | muxer := http.NewServeMux() 38 | muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) { 39 | notifyCh := make(chan struct{}) 40 | conn := WrapCloseNotifyConn(c, func() { 41 | close(notifyCh) 42 | }) 43 | wl.acceptCh <- conn 44 | <-notifyCh 45 | })) 46 | 47 | wl.server = &http.Server{ 48 | Addr: ln.Addr().String(), 49 | Handler: muxer, 50 | } 51 | 52 | go wl.server.Serve(ln) 53 | return 54 | } 55 | 56 | func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) { 57 | tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort)) 58 | if err != nil { 59 | return nil, err 60 | } 61 | l := NewWebsocketListener(tcpLn) 62 | return l, nil 63 | } 64 | 65 | func (p *WebsocketListener) Accept() (net.Conn, error) { 66 | c, ok := <-p.acceptCh 67 | if !ok { 68 | return nil, ErrWebsocketListenerClosed 69 | } 70 | return c, nil 71 | } 72 | 73 | func (p *WebsocketListener) Close() error { 74 | return p.server.Close() 75 | } 76 | 77 | func (p *WebsocketListener) Addr() net.Addr { 78 | return p.ln.Addr() 79 | } 80 | 81 | // addr: domain:port 82 | func ConnectWebsocketServer(addr string) (net.Conn, error) { 83 | addr = "ws://" + addr + FrpWebsocketPath 84 | uri, err := url.Parse(addr) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | origin := "http://" + uri.Host 90 | cfg, err := websocket.NewConfig(addr, origin) 91 | if err != nil { 92 | return nil, err 93 | } 94 | cfg.Dialer = &net.Dialer{ 95 | Timeout: 10 * time.Second, 96 | } 97 | 98 | conn, err := websocket.DialConfig(cfg) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return conn, nil 103 | } 104 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/plugin/client/static_file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "io" 19 | "net" 20 | "net/http" 21 | 22 | frpNet "github.com/fatedier/frp/pkg/util/net" 23 | 24 | "github.com/gorilla/mux" 25 | ) 26 | 27 | const PluginStaticFile = "static_file" 28 | 29 | func init() { 30 | Register(PluginStaticFile, NewStaticFilePlugin) 31 | } 32 | 33 | type StaticFilePlugin struct { 34 | localPath string 35 | stripPrefix string 36 | httpUser string 37 | httpPasswd string 38 | 39 | l *Listener 40 | s *http.Server 41 | } 42 | 43 | func NewStaticFilePlugin(params map[string]string) (Plugin, error) { 44 | localPath := params["plugin_local_path"] 45 | stripPrefix := params["plugin_strip_prefix"] 46 | httpUser := params["plugin_http_user"] 47 | httpPasswd := params["plugin_http_passwd"] 48 | 49 | listener := NewProxyListener() 50 | 51 | sp := &StaticFilePlugin{ 52 | localPath: localPath, 53 | stripPrefix: stripPrefix, 54 | httpUser: httpUser, 55 | httpPasswd: httpPasswd, 56 | 57 | l: listener, 58 | } 59 | var prefix string 60 | if stripPrefix != "" { 61 | prefix = "/" + stripPrefix + "/" 62 | } else { 63 | prefix = "/" 64 | } 65 | 66 | router := mux.NewRouter() 67 | router.Use(frpNet.NewHTTPAuthMiddleware(httpUser, httpPasswd).Middleware) 68 | router.PathPrefix(prefix).Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET") 69 | sp.s = &http.Server{ 70 | Handler: router, 71 | } 72 | go sp.s.Serve(listener) 73 | return sp, nil 74 | } 75 | 76 | func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { 77 | wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) 78 | sp.l.PutConn(wrapConn) 79 | } 80 | 81 | func (sp *StaticFilePlugin) Name() string { 82 | return PluginStaticFile 83 | } 84 | 85 | func (sp *StaticFilePlugin) Close() error { 86 | sp.s.Close() 87 | sp.l.Close() 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/util/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/fatedier/beego/logs" 21 | ) 22 | 23 | // Log is the under log object 24 | var Log *logs.BeeLogger 25 | 26 | func init() { 27 | Log = logs.NewLogger(200) 28 | Log.EnableFuncCallDepth(true) 29 | Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1) 30 | } 31 | 32 | func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) { 33 | SetLogFile(logWay, logFile, maxdays, disableLogColor) 34 | SetLogLevel(logLevel) 35 | } 36 | 37 | // SetLogFile to configure log params 38 | // logWay: file or console 39 | func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) { 40 | if logWay == "console" { 41 | params := "" 42 | if disableLogColor { 43 | params = fmt.Sprintf(`{"color": false}`) 44 | } 45 | Log.SetLogger("console", params) 46 | } else { 47 | params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays) 48 | Log.SetLogger("file", params) 49 | } 50 | } 51 | 52 | // SetLogLevel set log level, default is warning 53 | // value: error, warning, info, debug, trace 54 | func SetLogLevel(logLevel string) { 55 | level := 4 // warning 56 | switch logLevel { 57 | case "error": 58 | level = 3 59 | case "warn": 60 | level = 4 61 | case "info": 62 | level = 6 63 | case "debug": 64 | level = 7 65 | case "trace": 66 | level = 8 67 | default: 68 | level = 4 69 | } 70 | Log.SetLevel(level) 71 | } 72 | 73 | // wrap log 74 | 75 | func Error(format string, v ...interface{}) { 76 | Log.Error(format, v...) 77 | } 78 | 79 | func Warn(format string, v ...interface{}) { 80 | Log.Warn(format, v...) 81 | } 82 | 83 | func Info(format string, v ...interface{}) { 84 | Log.Info(format, v...) 85 | } 86 | 87 | func Debug(format string, v ...interface{}) { 88 | Log.Debug(format, v...) 89 | } 90 | 91 | func Trace(format string, v ...interface{}) { 92 | Log.Trace(format, v...) 93 | } 94 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/metrics/aggregate/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package aggregate 16 | 17 | import ( 18 | "github.com/fatedier/frp/pkg/metrics/mem" 19 | "github.com/fatedier/frp/pkg/metrics/prometheus" 20 | "github.com/fatedier/frp/server/metrics" 21 | ) 22 | 23 | // EnableMem start to mark metrics to memory monitor system. 24 | func EnableMem() { 25 | sm.Add(mem.ServerMetrics) 26 | } 27 | 28 | // EnablePrometheus start to mark metrics to prometheus. 29 | func EnablePrometheus() { 30 | sm.Add(prometheus.ServerMetrics) 31 | } 32 | 33 | var sm *serverMetrics = &serverMetrics{} 34 | 35 | func init() { 36 | metrics.Register(sm) 37 | } 38 | 39 | type serverMetrics struct { 40 | ms []metrics.ServerMetrics 41 | } 42 | 43 | func (m *serverMetrics) Add(sm metrics.ServerMetrics) { 44 | m.ms = append(m.ms, sm) 45 | } 46 | 47 | func (m *serverMetrics) NewClient() { 48 | for _, v := range m.ms { 49 | v.NewClient() 50 | } 51 | } 52 | 53 | func (m *serverMetrics) CloseClient() { 54 | for _, v := range m.ms { 55 | v.CloseClient() 56 | } 57 | } 58 | 59 | func (m *serverMetrics) NewProxy(name string, proxyType string) { 60 | for _, v := range m.ms { 61 | v.NewProxy(name, proxyType) 62 | } 63 | } 64 | 65 | func (m *serverMetrics) CloseProxy(name string, proxyType string) { 66 | for _, v := range m.ms { 67 | v.CloseProxy(name, proxyType) 68 | } 69 | } 70 | 71 | func (m *serverMetrics) OpenConnection(name string, proxyType string) { 72 | for _, v := range m.ms { 73 | v.OpenConnection(name, proxyType) 74 | } 75 | } 76 | 77 | func (m *serverMetrics) CloseConnection(name string, proxyType string) { 78 | for _, v := range m.ms { 79 | v.CloseConnection(name, proxyType) 80 | } 81 | } 82 | 83 | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) { 84 | for _, v := range m.ms { 85 | v.AddTrafficIn(name, proxyType, trafficBytes) 86 | } 87 | } 88 | 89 | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) { 90 | for _, v := range m.ms { 91 | v.AddTrafficOut(name, proxyType, trafficBytes) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /web/frps/src/App.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 48 | 49 | 82 | -------------------------------------------------------------------------------- /cmd/frpc/sub/https.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 sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strings" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/fatedier/frp/pkg/config" 25 | "github.com/fatedier/frp/pkg/consts" 26 | ) 27 | 28 | func init() { 29 | RegisterCommonFlags(httpsCmd) 30 | 31 | httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") 32 | httpsCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip") 33 | httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") 34 | httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") 35 | httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") 36 | httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") 37 | httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") 38 | 39 | rootCmd.AddCommand(httpsCmd) 40 | } 41 | 42 | var httpsCmd = &cobra.Command{ 43 | Use: "https", 44 | Short: "Run frpc with a single https proxy", 45 | RunE: func(cmd *cobra.Command, args []string) error { 46 | clientCfg, err := parseClientCommonCfgFromCmd() 47 | if err != nil { 48 | fmt.Println(err) 49 | os.Exit(1) 50 | } 51 | 52 | cfg := &config.HTTPSProxyConf{} 53 | var prefix string 54 | if user != "" { 55 | prefix = user + "." 56 | } 57 | cfg.ProxyName = prefix + proxyName 58 | cfg.ProxyType = consts.HTTPSProxy 59 | cfg.LocalIP = localIP 60 | cfg.LocalPort = localPort 61 | cfg.CustomDomains = strings.Split(customDomains, ",") 62 | cfg.SubDomain = subDomain 63 | cfg.UseEncryption = useEncryption 64 | cfg.UseCompression = useCompression 65 | 66 | err = cfg.CheckForCli() 67 | if err != nil { 68 | fmt.Println(err) 69 | os.Exit(1) 70 | } 71 | 72 | proxyConfs := map[string]config.ProxyConf{ 73 | cfg.ProxyName: cfg, 74 | } 75 | err = startService(clientCfg, proxyConfs, nil, "") 76 | if err != nil { 77 | fmt.Println(err) 78 | os.Exit(1) 79 | } 80 | return nil 81 | }, 82 | } 83 | -------------------------------------------------------------------------------- /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 | router.HandleFunc("/healthz", svr.Healthz) 38 | 39 | subRouter := router.NewRoute().Subrouter() 40 | 41 | user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd 42 | subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware) 43 | 44 | // metrics 45 | if svr.cfg.EnablePrometheus { 46 | subRouter.Handle("/metrics", promhttp.Handler()) 47 | } 48 | 49 | // api, see dashboard_api.go 50 | subRouter.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET") 51 | subRouter.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET") 52 | subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET") 53 | subRouter.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET") 54 | 55 | // view 56 | subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET") 57 | subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET") 58 | 59 | subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 60 | http.Redirect(w, r, "/static/", http.StatusMovedPermanently) 61 | }) 62 | 63 | server := &http.Server{ 64 | Addr: address, 65 | Handler: router, 66 | ReadTimeout: httpServerReadTimeout, 67 | WriteTimeout: httpServerWriteTimeout, 68 | } 69 | if address == "" || address == ":" { 70 | address = ":http" 71 | } 72 | ln, err := net.Listen("tcp", address) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | go server.Serve(ln) 78 | return 79 | } 80 | -------------------------------------------------------------------------------- /pkg/util/net/kcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | 21 | kcp "github.com/fatedier/kcp-go" 22 | ) 23 | 24 | type KCPListener struct { 25 | listener net.Listener 26 | acceptCh chan net.Conn 27 | closeFlag bool 28 | } 29 | 30 | func ListenKcp(address string) (l *KCPListener, err error) { 31 | listener, err := kcp.ListenWithOptions(address, nil, 10, 3) 32 | if err != nil { 33 | return l, err 34 | } 35 | listener.SetReadBuffer(4194304) 36 | listener.SetWriteBuffer(4194304) 37 | 38 | l = &KCPListener{ 39 | listener: listener, 40 | acceptCh: make(chan net.Conn), 41 | closeFlag: false, 42 | } 43 | 44 | go func() { 45 | for { 46 | conn, err := listener.AcceptKCP() 47 | if err != nil { 48 | if l.closeFlag { 49 | close(l.acceptCh) 50 | return 51 | } 52 | continue 53 | } 54 | conn.SetStreamMode(true) 55 | conn.SetWriteDelay(true) 56 | conn.SetNoDelay(1, 20, 2, 1) 57 | conn.SetMtu(1350) 58 | conn.SetWindowSize(1024, 1024) 59 | conn.SetACKNoDelay(false) 60 | 61 | l.acceptCh <- conn 62 | } 63 | }() 64 | return l, err 65 | } 66 | 67 | func (l *KCPListener) Accept() (net.Conn, error) { 68 | conn, ok := <-l.acceptCh 69 | if !ok { 70 | return conn, fmt.Errorf("channel for kcp listener closed") 71 | } 72 | return conn, nil 73 | } 74 | 75 | func (l *KCPListener) Close() error { 76 | if !l.closeFlag { 77 | l.closeFlag = true 78 | l.listener.Close() 79 | } 80 | return nil 81 | } 82 | 83 | func (l *KCPListener) Addr() net.Addr { 84 | return l.listener.Addr() 85 | } 86 | 87 | func NewKCPConnFromUDP(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) { 88 | kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn) 89 | if err != nil { 90 | return nil, err 91 | } 92 | kcpConn.SetStreamMode(true) 93 | kcpConn.SetWriteDelay(true) 94 | kcpConn.SetNoDelay(1, 20, 2, 1) 95 | kcpConn.SetMtu(1350) 96 | kcpConn.SetWindowSize(1024, 1024) 97 | kcpConn.SetACKNoDelay(false) 98 | return kcpConn, nil 99 | } 100 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/frpc/src/components/Overview.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 73 | 74 | 76 | -------------------------------------------------------------------------------- /cmd/frpc/sub/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 sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strings" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/fatedier/frp/pkg/config" 25 | "github.com/fatedier/frp/pkg/consts" 26 | ) 27 | 28 | func init() { 29 | RegisterCommonFlags(tcpMuxCmd) 30 | 31 | tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") 32 | tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip") 33 | tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") 34 | tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") 35 | tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") 36 | tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer") 37 | tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") 38 | tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") 39 | 40 | rootCmd.AddCommand(tcpMuxCmd) 41 | } 42 | 43 | var tcpMuxCmd = &cobra.Command{ 44 | Use: "tcpmux", 45 | Short: "Run frpc with a single tcpmux proxy", 46 | RunE: func(cmd *cobra.Command, args []string) error { 47 | clientCfg, err := parseClientCommonCfgFromCmd() 48 | if err != nil { 49 | fmt.Println(err) 50 | os.Exit(1) 51 | } 52 | 53 | cfg := &config.TCPMuxProxyConf{} 54 | var prefix string 55 | if user != "" { 56 | prefix = user + "." 57 | } 58 | cfg.ProxyName = prefix + proxyName 59 | cfg.ProxyType = consts.TCPMuxProxy 60 | cfg.LocalIP = localIP 61 | cfg.LocalPort = localPort 62 | cfg.CustomDomains = strings.Split(customDomains, ",") 63 | cfg.SubDomain = subDomain 64 | cfg.Multiplexer = multiplexer 65 | cfg.UseEncryption = useEncryption 66 | cfg.UseCompression = useCompression 67 | 68 | err = cfg.CheckForCli() 69 | if err != nil { 70 | fmt.Println(err) 71 | os.Exit(1) 72 | } 73 | 74 | proxyConfs := map[string]config.ProxyConf{ 75 | cfg.ProxyName: cfg, 76 | } 77 | err = startService(clientCfg, proxyConfs, nil, "") 78 | if err != nil { 79 | fmt.Println(err) 80 | os.Exit(1) 81 | } 82 | return nil 83 | }, 84 | } 85 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/util/vhost/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vhost 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "net/http" 21 | "os" 22 | 23 | frpLog "github.com/fatedier/frp/pkg/util/log" 24 | "github.com/fatedier/frp/pkg/util/version" 25 | ) 26 | 27 | var ( 28 | NotFoundPagePath = "" 29 | ) 30 | 31 | const ( 32 | NotFound = ` 33 | 34 | 35 | Not Found 36 | 43 | 44 | 45 |

The page you requested was not found.

46 |

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

48 |

The server is powered by frp.

49 |

Faithfully yours, frp.

50 | 51 | 52 | ` 53 | ) 54 | 55 | func getNotFoundPageContent() []byte { 56 | var ( 57 | buf []byte 58 | err error 59 | ) 60 | if NotFoundPagePath != "" { 61 | buf, err = os.ReadFile(NotFoundPagePath) 62 | if err != nil { 63 | frpLog.Warn("read custom 404 page error: %v", err) 64 | buf = []byte(NotFound) 65 | } 66 | } else { 67 | buf = []byte(NotFound) 68 | } 69 | return buf 70 | } 71 | 72 | func notFoundResponse() *http.Response { 73 | header := make(http.Header) 74 | header.Set("server", "frp/"+version.Full()) 75 | header.Set("Content-Type", "text/html") 76 | 77 | res := &http.Response{ 78 | Status: "Not Found", 79 | StatusCode: 404, 80 | Proto: "HTTP/1.0", 81 | ProtoMajor: 1, 82 | ProtoMinor: 0, 83 | Header: header, 84 | Body: io.NopCloser(bytes.NewReader(getNotFoundPageContent())), 85 | } 86 | return res 87 | } 88 | 89 | func noAuthResponse() *http.Response { 90 | header := make(map[string][]string) 91 | header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`} 92 | res := &http.Response{ 93 | Status: "401 Not authorized", 94 | StatusCode: 401, 95 | Proto: "HTTP/1.1", 96 | ProtoMajor: 1, 97 | ProtoMinor: 1, 98 | Header: header, 99 | } 100 | return res 101 | } 102 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pkg/util/vhost/https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vhost 16 | 17 | import ( 18 | "crypto/tls" 19 | "io" 20 | "net" 21 | "time" 22 | 23 | gnet "github.com/fatedier/golib/net" 24 | ) 25 | 26 | type HTTPSMuxer struct { 27 | *Muxer 28 | } 29 | 30 | func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, error) { 31 | mux, err := NewMuxer(listener, GetHTTPSHostname, nil, nil, nil, timeout) 32 | return &HTTPSMuxer{mux}, err 33 | } 34 | 35 | func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) { 36 | reqInfoMap := make(map[string]string, 0) 37 | sc, rd := gnet.NewSharedConn(c) 38 | 39 | clientHello, err := readClientHello(rd) 40 | if err != nil { 41 | return nil, reqInfoMap, err 42 | } 43 | 44 | reqInfoMap["Host"] = clientHello.ServerName 45 | reqInfoMap["Scheme"] = "https" 46 | return sc, reqInfoMap, nil 47 | } 48 | 49 | func readClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) { 50 | var hello *tls.ClientHelloInfo 51 | 52 | // Note that Handshake always fails because the readOnlyConn is not a real connection. 53 | // As long as the Client Hello is successfully read, the failure should only happen after GetConfigForClient is called, 54 | // so we only care about the error if hello was never set. 55 | err := tls.Server(readOnlyConn{reader: reader}, &tls.Config{ 56 | GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { 57 | hello = &tls.ClientHelloInfo{} 58 | *hello = *argHello 59 | return nil, nil 60 | }, 61 | }).Handshake() 62 | 63 | if hello == nil { 64 | return nil, err 65 | } 66 | return hello, nil 67 | } 68 | 69 | type readOnlyConn struct { 70 | reader io.Reader 71 | } 72 | 73 | func (conn readOnlyConn) Read(p []byte) (int, error) { return conn.reader.Read(p) } 74 | func (conn readOnlyConn) Write(p []byte) (int, error) { return 0, io.ErrClosedPipe } 75 | func (conn readOnlyConn) Close() error { return nil } 76 | func (conn readOnlyConn) LocalAddr() net.Addr { return nil } 77 | func (conn readOnlyConn) RemoteAddr() net.Addr { return nil } 78 | func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil } 79 | func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil } 80 | func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil } 81 | -------------------------------------------------------------------------------- /pkg/config/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "encoding/json" 19 | "errors" 20 | "strconv" 21 | "strings" 22 | ) 23 | 24 | const ( 25 | MB = 1024 * 1024 26 | KB = 1024 27 | ) 28 | 29 | type BandwidthQuantity struct { 30 | s string // MB or KB 31 | 32 | i int64 // bytes 33 | } 34 | 35 | func NewBandwidthQuantity(s string) (BandwidthQuantity, error) { 36 | q := BandwidthQuantity{} 37 | err := q.UnmarshalString(s) 38 | if err != nil { 39 | return q, err 40 | } 41 | return q, nil 42 | } 43 | 44 | func MustBandwidthQuantity(s string) BandwidthQuantity { 45 | q := BandwidthQuantity{} 46 | err := q.UnmarshalString(s) 47 | if err != nil { 48 | panic(err) 49 | } 50 | return q 51 | } 52 | 53 | func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool { 54 | if q == nil && u == nil { 55 | return true 56 | } 57 | if q != nil && u != nil { 58 | return q.i == u.i 59 | } 60 | return false 61 | } 62 | 63 | func (q *BandwidthQuantity) String() string { 64 | return q.s 65 | } 66 | 67 | func (q *BandwidthQuantity) UnmarshalString(s string) error { 68 | s = strings.TrimSpace(s) 69 | if s == "" { 70 | return nil 71 | } 72 | 73 | var ( 74 | base int64 75 | f float64 76 | err error 77 | ) 78 | if strings.HasSuffix(s, "MB") { 79 | base = MB 80 | fstr := strings.TrimSuffix(s, "MB") 81 | f, err = strconv.ParseFloat(fstr, 64) 82 | if err != nil { 83 | return err 84 | } 85 | } else if strings.HasSuffix(s, "KB") { 86 | base = KB 87 | fstr := strings.TrimSuffix(s, "KB") 88 | f, err = strconv.ParseFloat(fstr, 64) 89 | if err != nil { 90 | return err 91 | } 92 | } else { 93 | return errors.New("unit not support") 94 | } 95 | 96 | q.s = s 97 | q.i = int64(f * float64(base)) 98 | return nil 99 | } 100 | 101 | func (q *BandwidthQuantity) UnmarshalJSON(b []byte) error { 102 | if len(b) == 4 && string(b) == "null" { 103 | return nil 104 | } 105 | 106 | var str string 107 | err := json.Unmarshal(b, &str) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | return q.UnmarshalString(str) 113 | } 114 | 115 | func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) { 116 | return []byte("\"" + q.s + "\""), nil 117 | } 118 | 119 | func (q *BandwidthQuantity) Bytes() int64 { 120 | return q.i 121 | } 122 | -------------------------------------------------------------------------------- /pkg/transport/tls.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "math/big" 10 | "os" 11 | ) 12 | 13 | func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) { 14 | tlsCert, err := tls.LoadX509KeyPair(certfile, keyfile) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &tlsCert, nil 19 | } 20 | 21 | func newRandomTLSKeyPair() *tls.Certificate { 22 | key, err := rsa.GenerateKey(rand.Reader, 1024) 23 | if err != nil { 24 | panic(err) 25 | } 26 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 27 | certDER, err := x509.CreateCertificate( 28 | rand.Reader, 29 | &template, 30 | &template, 31 | &key.PublicKey, 32 | key) 33 | if err != nil { 34 | panic(err) 35 | } 36 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 37 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 38 | 39 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 40 | if err != nil { 41 | panic(err) 42 | } 43 | return &tlsCert 44 | } 45 | 46 | // Only support one ca file to add 47 | func newCertPool(caPath string) (*x509.CertPool, error) { 48 | pool := x509.NewCertPool() 49 | 50 | caCrt, err := os.ReadFile(caPath) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | pool.AppendCertsFromPEM(caCrt) 56 | 57 | return pool, nil 58 | } 59 | 60 | func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) { 61 | var base = &tls.Config{} 62 | 63 | if certPath == "" || keyPath == "" { 64 | // server will generate tls conf by itself 65 | cert := newRandomTLSKeyPair() 66 | base.Certificates = []tls.Certificate{*cert} 67 | } else { 68 | cert, err := newCustomTLSKeyPair(certPath, keyPath) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | base.Certificates = []tls.Certificate{*cert} 74 | } 75 | 76 | if caPath != "" { 77 | pool, err := newCertPool(caPath) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | base.ClientAuth = tls.RequireAndVerifyClientCert 83 | base.ClientCAs = pool 84 | } 85 | 86 | return base, nil 87 | } 88 | 89 | func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Config, error) { 90 | var base = &tls.Config{} 91 | 92 | if certPath == "" || keyPath == "" { 93 | // client will not generate tls conf by itself 94 | } else { 95 | cert, err := newCustomTLSKeyPair(certPath, keyPath) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | base.Certificates = []tls.Certificate{*cert} 101 | } 102 | 103 | if caPath != "" { 104 | pool, err := newCertPool(caPath) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | base.RootCAs = pool 110 | base.ServerName = serverName 111 | base.InsecureSkipVerify = false 112 | } else { 113 | base.InsecureSkipVerify = true 114 | } 115 | 116 | return base, nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/config/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | ) 23 | 24 | func ParseClientConfig(filePath string) ( 25 | cfg ClientCommonConf, 26 | pxyCfgs map[string]ProxyConf, 27 | visitorCfgs map[string]VisitorConf, 28 | err error, 29 | ) { 30 | var content []byte 31 | content, err = GetRenderedConfFromFile(filePath) 32 | if err != nil { 33 | return 34 | } 35 | configBuffer := bytes.NewBuffer(nil) 36 | configBuffer.Write(content) 37 | 38 | // Parse common section. 39 | cfg, err = UnmarshalClientConfFromIni(content) 40 | if err != nil { 41 | return 42 | } 43 | cfg.Complete() 44 | if err = cfg.Validate(); err != nil { 45 | err = fmt.Errorf("Parse config error: %v", err) 46 | return 47 | } 48 | 49 | // Aggregate proxy configs from include files. 50 | var buf []byte 51 | buf, err = getIncludeContents(cfg.IncludeConfigFiles) 52 | if err != nil { 53 | err = fmt.Errorf("getIncludeContents error: %v", err) 54 | return 55 | } 56 | configBuffer.WriteString("\n") 57 | configBuffer.Write(buf) 58 | 59 | // Parse all proxy and visitor configs. 60 | pxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start) 61 | if err != nil { 62 | return 63 | } 64 | return 65 | } 66 | 67 | // getIncludeContents renders all configs from paths. 68 | // files format can be a single file path or directory or regex path. 69 | func getIncludeContents(paths []string) ([]byte, error) { 70 | out := bytes.NewBuffer(nil) 71 | for _, path := range paths { 72 | absDir, err := filepath.Abs(filepath.Dir(path)) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if _, err := os.Stat(absDir); os.IsNotExist(err) { 77 | return nil, err 78 | } 79 | files, err := os.ReadDir(absDir) 80 | if err != nil { 81 | return nil, err 82 | } 83 | for _, fi := range files { 84 | if fi.IsDir() { 85 | continue 86 | } 87 | absFile := filepath.Join(absDir, fi.Name()) 88 | if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched { 89 | tmpContent, err := GetRenderedConfFromFile(absFile) 90 | if err != nil { 91 | return nil, fmt.Errorf("render extra config %s error: %v", absFile, err) 92 | } 93 | out.Write(tmpContent) 94 | out.WriteString("\n") 95 | } 96 | } 97 | } 98 | return out.Bytes(), nil 99 | } 100 | -------------------------------------------------------------------------------- /pkg/plugin/client/http2https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "crypto/tls" 19 | "fmt" 20 | "io" 21 | "net" 22 | "net/http" 23 | "net/http/httputil" 24 | "strings" 25 | 26 | frpNet "github.com/fatedier/frp/pkg/util/net" 27 | ) 28 | 29 | const PluginHTTP2HTTPS = "http2https" 30 | 31 | func init() { 32 | Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin) 33 | } 34 | 35 | type HTTP2HTTPSPlugin struct { 36 | hostHeaderRewrite string 37 | localAddr string 38 | headers map[string]string 39 | 40 | l *Listener 41 | s *http.Server 42 | } 43 | 44 | func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) { 45 | localAddr := params["plugin_local_addr"] 46 | hostHeaderRewrite := params["plugin_host_header_rewrite"] 47 | headers := make(map[string]string) 48 | for k, v := range params { 49 | if !strings.HasPrefix(k, "plugin_header_") { 50 | continue 51 | } 52 | if k = strings.TrimPrefix(k, "plugin_header_"); k != "" { 53 | headers[k] = v 54 | } 55 | } 56 | 57 | if localAddr == "" { 58 | return nil, fmt.Errorf("plugin_local_addr is required") 59 | } 60 | 61 | listener := NewProxyListener() 62 | 63 | p := &HTTP2HTTPSPlugin{ 64 | localAddr: localAddr, 65 | hostHeaderRewrite: hostHeaderRewrite, 66 | headers: headers, 67 | l: listener, 68 | } 69 | 70 | tr := &http.Transport{ 71 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 72 | } 73 | 74 | rp := &httputil.ReverseProxy{ 75 | Director: func(req *http.Request) { 76 | req.URL.Scheme = "https" 77 | req.URL.Host = p.localAddr 78 | if p.hostHeaderRewrite != "" { 79 | req.Host = p.hostHeaderRewrite 80 | } 81 | for k, v := range p.headers { 82 | req.Header.Set(k, v) 83 | } 84 | }, 85 | Transport: tr, 86 | } 87 | 88 | p.s = &http.Server{ 89 | Handler: rp, 90 | } 91 | 92 | go p.s.Serve(listener) 93 | 94 | return p, nil 95 | } 96 | 97 | func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { 98 | wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) 99 | p.l.PutConn(wrapConn) 100 | } 101 | 102 | func (p *HTTP2HTTPSPlugin) Name() string { 103 | return PluginHTTP2HTTPS 104 | } 105 | 106 | func (p *HTTP2HTTPSPlugin) Close() error { 107 | if err := p.s.Close(); err != nil { 108 | return err 109 | } 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /test/e2e/framework/process.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "os" 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 = os.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 = os.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 := os.WriteFile(path, []byte(content), 0666) 89 | ExpectNoError(err) 90 | return path 91 | } 92 | --------------------------------------------------------------------------------