├── conf ├── frps.toml └── frpc.toml ├── web ├── frpc │ ├── env.d.ts │ ├── public │ │ └── favicon.ico │ ├── .prettierrc.json │ ├── src │ │ ├── assets │ │ │ └── dark.css │ │ ├── main.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── components │ │ │ ├── Overview.vue │ │ │ └── ClientConfigure.vue │ │ └── App.vue │ ├── Makefile │ ├── auto-imports.d.ts │ ├── tsconfig.node.json │ ├── index.html │ ├── README.md │ ├── .gitignore │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── vite.config.mts │ ├── components.d.ts │ └── package.json └── frps │ ├── env.d.ts │ ├── public │ └── favicon.ico │ ├── .prettierrc.json │ ├── src │ ├── assets │ │ ├── dark.css │ │ └── custom.css │ ├── main.ts │ ├── components │ │ ├── LongSpan.vue │ │ ├── ProxiesTCP.vue │ │ ├── ProxiesUDP.vue │ │ ├── ProxiesSTCP.vue │ │ ├── ProxiesSUDP.vue │ │ ├── Traffic.vue │ │ ├── ProxiesTCPMux.vue │ │ ├── ProxiesHTTP.vue │ │ └── ProxiesHTTPS.vue │ └── router │ │ └── index.ts │ ├── Makefile │ ├── auto-imports.d.ts │ ├── tsconfig.node.json │ ├── index.html │ ├── README.md │ ├── .gitignore │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── vite.config.mts │ ├── package.json │ └── components.d.ts ├── cmd ├── .DS_Store ├── frpc │ ├── main.go │ └── sub │ │ └── verify.go └── frps │ ├── main.go │ └── verify.go ├── doc └── pic │ ├── zsxq.jpg │ ├── dashboard.png │ ├── architecture.png │ ├── sponsor_lokal.png │ ├── sponsor_workos.png │ ├── sponsor_daytona.png │ └── sponsor_terminusos.jpeg ├── assets ├── frpc │ ├── static │ │ ├── favicon.ico │ │ └── index.html │ └── embed.go ├── frps │ ├── static │ │ ├── favicon.ico │ │ └── index.html │ └── embed.go └── assets.go ├── test └── e2e │ ├── mock │ └── server │ │ ├── interface.go │ │ ├── httpserver │ │ └── server.go │ │ └── streamserver │ │ └── server.go │ ├── framework │ ├── client.go │ ├── util.go │ ├── log.go │ ├── consts │ │ └── consts.go │ ├── test_context.go │ ├── cleanup.go │ └── mockservers.go │ ├── suites.go │ ├── examples.go │ ├── pkg │ ├── rpc │ │ └── rpc.go │ ├── plugin │ │ └── plugin.go │ ├── process │ │ └── process.go │ ├── port │ │ ├── util.go │ │ └── port.go │ ├── ssh │ │ └── client.go │ └── cert │ │ └── generator.go │ ├── e2e_test.go │ ├── legacy │ ├── features │ │ ├── heartbeat.go │ │ ├── monitor.go │ │ └── chaos.go │ └── basic │ │ ├── xtcp.go │ │ └── config.go │ ├── v1 │ ├── features │ │ ├── heartbeat.go │ │ ├── monitor.go │ │ └── chaos.go │ └── basic │ │ ├── xtcp.go │ │ └── annotations.go │ └── e2e.go ├── Release.md ├── dockerfiles ├── Dockerfile-for-frpc └── Dockerfile-for-frps ├── client ├── event │ └── event.go └── proxy │ └── general_tcp.go ├── pkg ├── proto │ └── udp │ │ └── udp_test.go ├── util │ ├── metric │ │ ├── counter_test.go │ │ ├── date_counter_test.go │ │ ├── metrics.go │ │ └── counter.go │ ├── version │ │ └── version.go │ ├── util │ │ ├── types.go │ │ └── util_test.go │ ├── vhost │ │ ├── https_test.go │ │ └── resource.go │ ├── system │ │ ├── system.go │ │ └── system_android.go │ ├── net │ │ ├── dns.go │ │ ├── dial.go │ │ ├── websocket.go │ │ ├── tls.go │ │ ├── listener.go │ │ └── http.go │ ├── xlog │ │ └── ctx.go │ └── limit │ │ ├── reader.go │ │ └── writer.go ├── config │ ├── v1 │ │ ├── api.go │ │ ├── server_test.go │ │ ├── client_test.go │ │ ├── proxy_test.go │ │ └── validation │ │ │ ├── common.go │ │ │ ├── validation.go │ │ │ ├── visitor.go │ │ │ ├── server.go │ │ │ └── plugin.go │ ├── legacy │ │ ├── README.md │ │ ├── utils.go │ │ └── value.go │ ├── template.go │ └── types │ │ └── types_test.go ├── errors │ └── errors.go ├── metrics │ ├── metrics.go │ ├── mem │ │ └── types.go │ └── aggregate │ │ └── server.go ├── auth │ ├── pass.go │ └── auth.go ├── plugin │ ├── server │ │ ├── tracer.go │ │ ├── plugin.go │ │ └── types.go │ └── client │ │ ├── socks5.go │ │ ├── unix_domain_socket.go │ │ ├── static_file.go │ │ ├── http2http.go │ │ ├── plugin.go │ │ └── http2https.go ├── ssh │ └── terminal.go ├── msg │ ├── ctl.go │ └── handler.go ├── virtual │ └── client.go └── nathole │ └── utils.go ├── hack ├── run-e2e.sh └── download.sh ├── server ├── group │ └── group.go ├── metrics │ └── metrics.go ├── controller │ └── resource.go └── proxy │ ├── stcp.go │ ├── sudp.go │ ├── xtcp.go │ ├── https.go │ └── tcp.go ├── Makefile ├── Makefile.cross-compiles └── package.sh /conf/frps.toml: -------------------------------------------------------------------------------- 1 | bindPort = 7000 2 | -------------------------------------------------------------------------------- /web/frpc/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/frps/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /cmd/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/cmd/.DS_Store -------------------------------------------------------------------------------- /doc/pic/zsxq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/zsxq.jpg -------------------------------------------------------------------------------- /doc/pic/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/dashboard.png -------------------------------------------------------------------------------- /doc/pic/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/architecture.png -------------------------------------------------------------------------------- /doc/pic/sponsor_lokal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/sponsor_lokal.png -------------------------------------------------------------------------------- /doc/pic/sponsor_workos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/sponsor_workos.png -------------------------------------------------------------------------------- /doc/pic/sponsor_daytona.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/sponsor_daytona.png -------------------------------------------------------------------------------- /web/frpc/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/web/frpc/public/favicon.ico -------------------------------------------------------------------------------- /web/frps/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/web/frps/public/favicon.ico -------------------------------------------------------------------------------- /assets/frpc/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/assets/frpc/static/favicon.ico -------------------------------------------------------------------------------- /assets/frps/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/assets/frps/static/favicon.ico -------------------------------------------------------------------------------- /doc/pic/sponsor_terminusos.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perlh/frp/HEAD/doc/pic/sponsor_terminusos.jpeg -------------------------------------------------------------------------------- /web/frpc/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /web/frps/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /web/frpc/src/assets/dark.css: -------------------------------------------------------------------------------- 1 | html.dark { 2 | --el-bg-color: #343432; 3 | --el-fill-color-blank: #343432; 4 | background-color: #343432; 5 | } 6 | -------------------------------------------------------------------------------- /web/frps/src/assets/dark.css: -------------------------------------------------------------------------------- 1 | html.dark { 2 | --el-bg-color: #343432; 3 | --el-fill-color-blank: #343432; 4 | background-color: #343432; 5 | } 6 | -------------------------------------------------------------------------------- /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.toml: -------------------------------------------------------------------------------- 1 | serverAddr = "127.0.0.1" 2 | serverPort = 7000 3 | 4 | [[proxies]] 5 | name = "test-tcp" 6 | type = "tcp" 7 | localIP = "127.0.0.1" 8 | localPort = 22 9 | remotePort = 6000 10 | -------------------------------------------------------------------------------- /web/frpc/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist build preview lint 2 | 3 | build: 4 | @npm run build 5 | 6 | dev: 7 | @npm run dev 8 | 9 | preview: 10 | @npm run preview 11 | 12 | lint: 13 | @npm run lint 14 | -------------------------------------------------------------------------------- /web/frps/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist build preview lint 2 | 3 | build: 4 | @npm run build 5 | 6 | dev: 7 | @npm run dev 8 | 9 | preview: 10 | @npm run preview 11 | 12 | lint: 13 | @npm run lint 14 | -------------------------------------------------------------------------------- /Release.md: -------------------------------------------------------------------------------- 1 | ### Features 2 | 3 | * Added a new plugin "http2http" which allows forwarding HTTP requests to another HTTP server, supporting options like local address binding, host header rewrite, and custom request headers. 4 | -------------------------------------------------------------------------------- /assets/frpc/embed.go: -------------------------------------------------------------------------------- 1 | package frpc 2 | 3 | import ( 4 | "embed" 5 | 6 | "fxp/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 | "fxp/assets" 7 | ) 8 | 9 | //go:embed static/* 10 | var content embed.FS 11 | 12 | func init() { 13 | assets.Register(content) 14 | } 15 | -------------------------------------------------------------------------------- /web/frpc/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /web/frps/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /test/e2e/framework/client.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | clientsdk "fxp/pkg/sdk/client" 5 | ) 6 | 7 | func (f *Framework) APIClientForFrpc(port int) *clientsdk.Client { 8 | return clientsdk.New("127.0.0.1", port) 9 | } 10 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile-for-frpc: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 AS building 2 | 3 | COPY . /building 4 | WORKDIR /building 5 | 6 | RUN make frpc 7 | 8 | FROM alpine:3 9 | 10 | COPY --from=building /building/bin/frpc /usr/bin/frpc 11 | 12 | ENTRYPOINT ["/usr/bin/frpc"] 13 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile-for-frps: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 AS building 2 | 3 | COPY . /building 4 | WORKDIR /building 5 | 6 | RUN make frps 7 | 8 | FROM alpine:3 9 | 10 | COPY --from=building /building/bin/frps /usr/bin/frps 11 | 12 | ENTRYPOINT ["/usr/bin/frps"] 13 | -------------------------------------------------------------------------------- /web/frpc/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /web/frps/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /web/frpc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frp client admin UI 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/frps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frps dashboard 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/frpc/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import 'element-plus/dist/index.css' 3 | import 'element-plus/theme-chalk/dark/css-vars.css' 4 | import App from './App.vue' 5 | import router from './router' 6 | 7 | import './assets/dark.css' 8 | 9 | const app = createApp(App) 10 | 11 | app.use(router) 12 | 13 | app.mount('#app') 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/frps/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import 'element-plus/dist/index.css' 3 | import 'element-plus/theme-chalk/dark/css-vars.css' 4 | import App from './App.vue' 5 | import router from './router' 6 | 7 | import './assets/custom.css' 8 | import './assets/dark.css' 9 | 10 | const app = createApp(App) 11 | 12 | app.use(router) 13 | 14 | app.mount('#app') 15 | -------------------------------------------------------------------------------- /client/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | 6 | "fxp/pkg/msg" 7 | ) 8 | 9 | var ErrPayloadType = errors.New("error payload type") 10 | 11 | type Handler func(payload interface{}) error 12 | 13 | type StartProxyPayload struct { 14 | NewProxyMsg *msg.NewProxy 15 | } 16 | 17 | type CloseProxyPayload struct { 18 | CloseProxyMsg *msg.CloseProxy 19 | } 20 | -------------------------------------------------------------------------------- /assets/frpc/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frp client admin UI 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/frpc/README.md: -------------------------------------------------------------------------------- 1 | # frpc-dashboard 2 | 3 | ## Project Setup 4 | 5 | ```sh 6 | yarn install 7 | ``` 8 | 9 | ### Compile and Hot-Reload for Development 10 | 11 | ```sh 12 | make dev 13 | ``` 14 | 15 | ### Type-Check, Compile and Minify for Production 16 | 17 | ```sh 18 | make build 19 | ``` 20 | 21 | ### Lint with [ESLint](https://eslint.org/) 22 | 23 | ```sh 24 | make lint 25 | ``` 26 | -------------------------------------------------------------------------------- /web/frps/README.md: -------------------------------------------------------------------------------- 1 | # frps-dashboard 2 | 3 | ## Project Setup 4 | 5 | ```sh 6 | yarn install 7 | ``` 8 | 9 | ### Compile and Hot-Reload for Development 10 | 11 | ```sh 12 | make dev 13 | ``` 14 | 15 | ### Type-Check, Compile and Minify for Production 16 | 17 | ```sh 18 | make build 19 | ``` 20 | 21 | ### Lint with [ESLint](https://eslint.org/) 22 | 23 | ```sh 24 | make lint 25 | ``` 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/frps/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | frps dashboard 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/frps/src/components/LongSpan.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /web/frps/src/assets/custom.css: -------------------------------------------------------------------------------- 1 | .el-form-item span { 2 | margin-left: 15px; 3 | } 4 | 5 | .proxy-table-expand { 6 | font-size: 0; 7 | } 8 | 9 | .proxy-table-expand .el-form-item__label{ 10 | width: 90px; 11 | color: #99a9bf; 12 | } 13 | 14 | .proxy-table-expand .el-form-item { 15 | margin-right: 0; 16 | margin-bottom: 0; 17 | width: 50%; 18 | } 19 | 20 | .el-table .el-table__expanded-cell { 21 | padding: 20px 50px; 22 | } 23 | -------------------------------------------------------------------------------- /web/frpc/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /web/frps/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /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.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router' 2 | import Overview from '../components/Overview.vue' 3 | import ClientConfigure from '../components/ClientConfigure.vue' 4 | 5 | const router = createRouter({ 6 | history: createWebHashHistory(), 7 | routes: [ 8 | { 9 | path: '/', 10 | name: 'Overview', 11 | component: Overview, 12 | }, 13 | { 14 | path: '/configure', 15 | name: 'ClientConfigure', 16 | component: ClientConfigure, 17 | }, 18 | ], 19 | }) 20 | 21 | export default router 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/frps/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier', 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | }, 15 | rules: { 16 | '@typescript-eslint/no-unused-vars': [ 17 | 'warn', 18 | { 19 | argsIgnorePattern: '^_', 20 | varsIgnorePattern: '^_', 21 | }, 22 | ], 23 | 'vue/multi-word-component-names': [ 24 | 'error', 25 | { 26 | ignores: ['Traffic'], 27 | }, 28 | ], 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /web/frpc/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier', 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | }, 15 | rules: { 16 | '@typescript-eslint/no-unused-vars': [ 17 | 'warn', 18 | { 19 | argsIgnorePattern: '^_', 20 | varsIgnorePattern: '^_', 21 | }, 22 | ], 23 | 'vue/multi-word-component-names': [ 24 | 'error', 25 | { 26 | ignores: ['Overview'], 27 | }, 28 | ], 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesTCP.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesUDP.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /pkg/config/v1/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | type APIMetadata struct { 18 | Version string `json:"version"` 19 | } 20 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesSTCP.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesSUDP.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/frpc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /web/frps/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /pkg/util/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | var version = "0.58.1" 18 | 19 | func Full() string { 20 | return version 21 | } 22 | -------------------------------------------------------------------------------- /web/frpc/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import AutoImport from 'unplugin-auto-import/vite' 6 | import Components from 'unplugin-vue-components/vite' 7 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | base: '', 12 | plugins: [ 13 | vue(), 14 | AutoImport({ 15 | resolvers: [ElementPlusResolver()], 16 | }), 17 | Components({ 18 | resolvers: [ElementPlusResolver()], 19 | }), 20 | ], 21 | resolve: { 22 | alias: { 23 | '@': fileURLToPath(new URL('./src', import.meta.url)), 24 | }, 25 | }, 26 | build: { 27 | assetsDir: '', 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /web/frps/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import AutoImport from 'unplugin-auto-import/vite' 6 | import Components from 'unplugin-vue-components/vite' 7 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | base: '', 12 | plugins: [ 13 | vue(), 14 | AutoImport({ 15 | resolvers: [ElementPlusResolver()], 16 | }), 17 | Components({ 18 | resolvers: [ElementPlusResolver()], 19 | }), 20 | ], 21 | resolve: { 22 | alias: { 23 | '@': fileURLToPath(new URL('./src', import.meta.url)), 24 | }, 25 | }, 26 | build: { 27 | assetsDir: '', 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /pkg/util/util/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | func EmptyOr[T comparable](v T, fallback T) T { 18 | var zero T 19 | if zero == v { 20 | return fallback 21 | } 22 | return v 23 | } 24 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | var ( 22 | ErrMsgType = errors.New("message type error") 23 | ErrCtlClosed = errors.New("control is closed") 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metrics 16 | 17 | import ( 18 | "fxp/pkg/metrics/aggregate" 19 | ) 20 | 21 | var ( 22 | EnableMem = aggregate.EnableMem 23 | EnablePrometheus = aggregate.EnablePrometheus 24 | ) 25 | -------------------------------------------------------------------------------- /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 | _ "fxp/assets/frpc" 19 | "fxp/cmd/frpc/sub" 20 | "fxp/pkg/util/system" 21 | ) 22 | 23 | func main() { 24 | system.EnableCompatibilityMode() 25 | sub.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /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 | _ "fxp/assets/frps" 19 | _ "fxp/pkg/metrics" 20 | "fxp/pkg/util/system" 21 | ) 22 | 23 | func main() { 24 | system.EnableCompatibilityMode() 25 | Execute() 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/examples.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/onsi/ginkgo/v2" 7 | 8 | "fxp/test/e2e/framework" 9 | "fxp/test/e2e/framework/consts" 10 | ) 11 | 12 | var _ = ginkgo.Describe("[Feature: Example]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | ginkgo.Describe("TCP", func() { 16 | ginkgo.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 | [[proxies]] 23 | name = "tcp" 24 | type = "tcp" 25 | localPort = {{ .%s }} 26 | remotePort = %d 27 | `, framework.TCPEchoServerPort, remotePort) 28 | 29 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 30 | 31 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 32 | }) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /pkg/config/legacy/README.md: -------------------------------------------------------------------------------- 1 | So far, there is no mature Go project that does well in parsing `*.ini` files. 2 | 3 | By comparison, we have selected an open source project: `https://github.com/go-ini/ini`. 4 | 5 | This library helped us solve most of the key-value matching, but there are still some problems, such as not supporting parsing `map`. 6 | 7 | We add our own logic on the basis of this library. In the current situationwhich, we need to complete the entire `Unmarshal` in two steps: 8 | 9 | * Step#1, use `go-ini` to complete the basic parameter matching; 10 | * Step#2, parse our custom parameters to realize parsing special structure, like `map`, `array`. 11 | 12 | Some of the keywords in `tag`(like inline, extends, etc.) may be different from standard libraries such as `json` and `protobuf` in Go. For details, please refer to the library documentation: https://ini.unknwon.io/docs/intro. -------------------------------------------------------------------------------- /pkg/util/vhost/https_test.go: -------------------------------------------------------------------------------- 1 | package vhost 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestGetHTTPSHostname(t *testing.T) { 13 | require := require.New(t) 14 | 15 | l, err := net.Listen("tcp", "127.0.0.1:") 16 | require.NoError(err) 17 | defer l.Close() 18 | 19 | var conn net.Conn 20 | go func() { 21 | conn, _ = l.Accept() 22 | require.NotNil(conn) 23 | }() 24 | 25 | go func() { 26 | time.Sleep(100 * time.Millisecond) 27 | tls.Dial("tcp", l.Addr().String(), &tls.Config{ 28 | InsecureSkipVerify: true, 29 | ServerName: "example.com", 30 | }) 31 | }() 32 | 33 | time.Sleep(200 * time.Millisecond) 34 | _, infos, err := GetHTTPSHostname(conn) 35 | require.NoError(err) 36 | require.Equal("example.com", infos["Host"]) 37 | require.Equal("https", infos["Scheme"]) 38 | } 39 | -------------------------------------------------------------------------------- /web/frps/src/components/Traffic.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | 33 | -------------------------------------------------------------------------------- /test/e2e/pkg/rpc/rpc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | func WriteBytes(w io.Writer, buf []byte) (int, error) { 12 | out := bytes.NewBuffer(nil) 13 | if err := binary.Write(out, binary.BigEndian, int64(len(buf))); err != nil { 14 | return 0, err 15 | } 16 | 17 | out.Write(buf) 18 | return w.Write(out.Bytes()) 19 | } 20 | 21 | func ReadBytes(r io.Reader) ([]byte, error) { 22 | var length int64 23 | if err := binary.Read(r, binary.BigEndian, &length); err != nil { 24 | return nil, err 25 | } 26 | if length < 0 || length > 10*1024*1024 { 27 | return nil, fmt.Errorf("invalid length") 28 | } 29 | buffer := make([]byte, length) 30 | n, err := io.ReadFull(r, buffer) 31 | if err != nil { 32 | return nil, err 33 | } 34 | if int64(n) != length { 35 | return nil, errors.New("invalid length") 36 | } 37 | return buffer, nil 38 | } 39 | -------------------------------------------------------------------------------- /test/e2e/framework/log.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | ) 9 | 10 | func nowStamp() string { 11 | return time.Now().Format(time.StampMilli) 12 | } 13 | 14 | func log(level string, format string, args ...interface{}) { 15 | fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...) 16 | } 17 | 18 | // Logf logs the info. 19 | func Logf(format string, args ...interface{}) { 20 | log("INFO", format, args...) 21 | } 22 | 23 | // Failf logs the fail info, including a stack trace starts with its direct caller 24 | // (for example, for call chain f -> g -> Failf("foo", ...) error would be logged for "g"). 25 | func Failf(format string, args ...interface{}) { 26 | msg := fmt.Sprintf(format, args...) 27 | skip := 1 28 | ginkgo.Fail(msg, skip) 29 | panic("unreachable") 30 | } 31 | 32 | // Fail is an alias for ginkgo.Fail. 33 | var Fail = ginkgo.Fail 34 | -------------------------------------------------------------------------------- /pkg/util/system/system.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !android 16 | 17 | package system 18 | 19 | // EnableCompatibilityMode enables compatibility mode for different system. 20 | // For example, on Android, the inability to obtain the correct time zone will result in incorrect log time output. 21 | func EnableCompatibilityMode() { 22 | } 23 | -------------------------------------------------------------------------------- /hack/run-e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SCRIPT=$(readlink -f "$0") 4 | ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd) 5 | 6 | ginkgo_command=$(which ginkgo 2>/dev/null) 7 | if [ -z "$ginkgo_command" ]; then 8 | echo "ginkgo not found, try to install..." 9 | go install github.com/onsi/ginkgo/v2/ginkgo@v2.17.1 10 | fi 11 | 12 | debug=false 13 | if [ "x${DEBUG}" = "xtrue" ]; then 14 | debug=true 15 | fi 16 | logLevel=debug 17 | if [ "${LOG_LEVEL}" ]; then 18 | logLevel="${LOG_LEVEL}" 19 | fi 20 | 21 | frpcPath=${ROOT}/bin/frpc 22 | if [ "${FRPC_PATH}" ]; then 23 | frpcPath="${FRPC_PATH}" 24 | fi 25 | frpsPath=${ROOT}/bin/frps 26 | if [ "${FRPS_PATH}" ]; then 27 | frpsPath="${FRPS_PATH}" 28 | fi 29 | concurrency="16" 30 | if [ "${CONCURRENCY}" ]; then 31 | concurrency="${CONCURRENCY}" 32 | fi 33 | 34 | ginkgo -nodes=${concurrency} --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug} 35 | -------------------------------------------------------------------------------- /test/e2e/e2e_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | _ "github.com/onsi/ginkgo/v2" 10 | 11 | "fxp/pkg/util/log" 12 | // test source 13 | "fxp/test/e2e/framework" 14 | _ "fxp/test/e2e/legacy/basic" 15 | _ "fxp/test/e2e/legacy/features" 16 | _ "fxp/test/e2e/legacy/plugin" 17 | _ "fxp/test/e2e/v1/basic" 18 | _ "fxp/test/e2e/v1/features" 19 | _ "fxp/test/e2e/v1/plugin" 20 | ) 21 | 22 | // handleFlags sets up all flags and parses the command line. 23 | func handleFlags() { 24 | framework.RegisterCommonFlags(flag.CommandLine) 25 | flag.Parse() 26 | } 27 | 28 | func TestMain(m *testing.M) { 29 | // Register test flags, then parse flags. 30 | handleFlags() 31 | 32 | if err := framework.ValidateTestContext(&framework.TestContext); err != nil { 33 | fmt.Println(err) 34 | os.Exit(1) 35 | } 36 | 37 | log.InitLogger("console", framework.TestContext.LogLevel, 0, true) 38 | os.Exit(m.Run()) 39 | } 40 | 41 | func TestE2E(t *testing.T) { 42 | RunE2ETests(t) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/auth/pass.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "fxp/pkg/msg" 19 | ) 20 | 21 | var AlwaysPassVerifier = &alwaysPass{} 22 | 23 | var _ Verifier = &alwaysPass{} 24 | 25 | type alwaysPass struct{} 26 | 27 | func (*alwaysPass) VerifyLogin(*msg.Login) error { return nil } 28 | 29 | func (*alwaysPass) VerifyPing(*msg.Ping) error { return nil } 30 | 31 | func (*alwaysPass) VerifyNewWorkConn(*msg.NewWorkConn) error { return nil } 32 | -------------------------------------------------------------------------------- /pkg/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 | -------------------------------------------------------------------------------- /pkg/config/v1/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/samber/lo" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestServerConfigComplete(t *testing.T) { 25 | require := require.New(t) 26 | c := &ServerConfig{} 27 | c.Complete() 28 | 29 | require.EqualValues("token", c.Auth.Method) 30 | require.Equal(true, lo.FromPtr(c.Transport.TCPMux)) 31 | require.Equal(true, lo.FromPtr(c.DetailedErrorsToClient)) 32 | } 33 | -------------------------------------------------------------------------------- /web/frpc/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default'] 11 | ElButton: typeof import('element-plus/es')['ElButton'] 12 | ElCol: typeof import('element-plus/es')['ElCol'] 13 | ElInput: typeof import('element-plus/es')['ElInput'] 14 | ElMenu: typeof import('element-plus/es')['ElMenu'] 15 | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] 16 | ElRow: typeof import('element-plus/es')['ElRow'] 17 | ElSwitch: typeof import('element-plus/es')['ElSwitch'] 18 | ElTable: typeof import('element-plus/es')['ElTable'] 19 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] 20 | Overview: typeof import('./src/components/Overview.vue')['default'] 21 | RouterLink: typeof import('vue-router')['RouterLink'] 22 | RouterView: typeof import('vue-router')['RouterView'] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/ssh/terminal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ssh 16 | 17 | import ( 18 | "fxp/client/proxy" 19 | v1 "fxp/pkg/config/v1" 20 | ) 21 | 22 | func createSuccessInfo(user string, pc v1.ProxyConfigurer, ps *proxy.WorkingStatus) string { 23 | base := pc.GetBaseConfig() 24 | out := "\n" 25 | out += "frp (via SSH) (Ctrl+C to quit)\n\n" 26 | out += "User: " + user + "\n" 27 | out += "ProxyName: " + base.Name + "\n" 28 | out += "Type: " + base.Type + "\n" 29 | out += "RemoteAddress: " + ps.RemoteAddr + "\n" 30 | return out 31 | } 32 | -------------------------------------------------------------------------------- /pkg/util/net/dns.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "context" 19 | "net" 20 | ) 21 | 22 | func SetDefaultDNSAddress(dnsAddress string) { 23 | if _, _, err := net.SplitHostPort(dnsAddress); err != nil { 24 | dnsAddress = net.JoinHostPort(dnsAddress, "53") 25 | } 26 | // Change default dns server 27 | net.DefaultResolver = &net.Resolver{ 28 | PreferGo: true, 29 | Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { 30 | return net.Dial(network, dnsAddress) 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/util/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 | -------------------------------------------------------------------------------- /web/frpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frpc-dashboard", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "run-p type-check build-only", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "element-plus": "^2.5.3", 15 | "vue": "^3.4.15", 16 | "vue-router": "^4.2.5" 17 | }, 18 | "devDependencies": { 19 | "@rushstack/eslint-patch": "^1.7.2", 20 | "@types/node": "^18.11.12", 21 | "@vitejs/plugin-vue": "^5.0.3", 22 | "@vue/eslint-config-prettier": "^9.0.0", 23 | "@vue/eslint-config-typescript": "^12.0.0", 24 | "@vue/tsconfig": "^0.5.1", 25 | "eslint": "^8.56.0", 26 | "eslint-plugin-vue": "^9.21.0", 27 | "npm-run-all": "^4.1.5", 28 | "prettier": "^3.2.4", 29 | "typescript": "~5.3.3", 30 | "unplugin-auto-import": "^0.17.5", 31 | "unplugin-vue-components": "^0.26.0", 32 | "vite": "^5.0.12", 33 | "vue-tsc": "^1.8.27" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/e2e/pkg/plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | 9 | plugin "fxp/pkg/plugin/server" 10 | "fxp/pkg/util/log" 11 | "fxp/test/e2e/mock/server/httpserver" 12 | ) 13 | 14 | type Handler func(req *plugin.Request) *plugin.Response 15 | 16 | type NewPluginRequest func() *plugin.Request 17 | 18 | func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler Handler, 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.Tracef("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.Tracef("plugin response: %s", string(buf)) 38 | _, _ = w.Write(buf) 39 | })), 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesTCPMux.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /pkg/plugin/server/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | const ( 22 | APIVersion = "0.1.0" 23 | 24 | OpLogin = "Login" 25 | OpNewProxy = "NewProxy" 26 | OpCloseProxy = "CloseProxy" 27 | OpPing = "Ping" 28 | OpNewWorkConn = "NewWorkConn" 29 | OpNewUserConn = "NewUserConn" 30 | ) 31 | 32 | type Plugin interface { 33 | Name() string 34 | IsSupport(op string) bool 35 | Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/util/util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRandId(t *testing.T) { 10 | assert := assert.New(t) 11 | id, err := RandID() 12 | assert.NoError(err) 13 | t.Log(id) 14 | assert.Equal(16, len(id)) 15 | } 16 | 17 | func TestGetAuthKey(t *testing.T) { 18 | assert := assert.New(t) 19 | key := GetAuthKey("1234", 1488720000) 20 | assert.Equal("6df41a43725f0c770fd56379e12acf8c", key) 21 | } 22 | 23 | func TestParseRangeNumbers(t *testing.T) { 24 | assert := assert.New(t) 25 | numbers, err := ParseRangeNumbers("2-5") 26 | if assert.NoError(err) { 27 | assert.Equal([]int64{2, 3, 4, 5}, numbers) 28 | } 29 | 30 | numbers, err = ParseRangeNumbers("1") 31 | if assert.NoError(err) { 32 | assert.Equal([]int64{1}, numbers) 33 | } 34 | 35 | numbers, err = ParseRangeNumbers("3-5,8") 36 | if assert.NoError(err) { 37 | assert.Equal([]int64{3, 4, 5, 8}, numbers) 38 | } 39 | 40 | numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ") 41 | if assert.NoError(err) { 42 | assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers) 43 | } 44 | 45 | _, err = ParseRangeNumbers("3-a") 46 | assert.Error(err) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/util/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 | -------------------------------------------------------------------------------- /web/frps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frps-dashboard", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "run-p type-check build-only", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "@types/humanize-plus": "^1.8.0", 15 | "echarts": "^5.4.3", 16 | "element-plus": "^2.5.3", 17 | "humanize-plus": "^1.8.2", 18 | "vue": "^3.4.15", 19 | "vue-router": "^4.2.5" 20 | }, 21 | "devDependencies": { 22 | "@rushstack/eslint-patch": "^1.7.2", 23 | "@types/node": "^18.11.12", 24 | "@vitejs/plugin-vue": "^5.0.3", 25 | "@vue/eslint-config-prettier": "^9.0.0", 26 | "@vue/eslint-config-typescript": "^12.0.0", 27 | "@vue/tsconfig": "^0.5.1", 28 | "eslint": "^8.56.0", 29 | "eslint-plugin-vue": "^9.21.0", 30 | "npm-run-all": "^4.1.5", 31 | "prettier": "^3.2.4", 32 | "typescript": "~5.3.3", 33 | "unplugin-auto-import": "^0.17.5", 34 | "unplugin-vue-components": "^0.26.0", 35 | "vite": "^5.0.12", 36 | "vue-tsc": "^1.8.27" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesHTTP.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/e2e/legacy/features/heartbeat.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | 9 | "fxp/test/e2e/framework" 10 | ) 11 | 12 | var _ = ginkgo.Describe("[Feature: Heartbeat]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | ginkgo.It("disable application layer heartbeat", func() { 16 | serverPort := f.AllocPort() 17 | serverConf := fmt.Sprintf(` 18 | [common] 19 | bind_addr = 0.0.0.0 20 | bind_port = %d 21 | heartbeat_timeout = -1 22 | tcp_mux_keepalive_interval = 2 23 | `, serverPort) 24 | 25 | remotePort := f.AllocPort() 26 | clientConf := fmt.Sprintf(` 27 | [common] 28 | server_port = %d 29 | log_level = trace 30 | heartbeat_interval = -1 31 | heartbeat_timeout = -1 32 | tcp_mux_keepalive_interval = 2 33 | 34 | [tcp] 35 | type = tcp 36 | local_port = %d 37 | remote_port = %d 38 | `, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort) 39 | 40 | // run frps and frpc 41 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 42 | 43 | framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure() 44 | 45 | time.Sleep(5 * time.Second) 46 | framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure() 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /web/frps/src/components/ProxiesHTTPS.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /pkg/config/v1/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/samber/lo" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestClientConfigComplete(t *testing.T) { 25 | require := require.New(t) 26 | c := &ClientConfig{} 27 | c.Complete() 28 | 29 | require.EqualValues("token", c.Auth.Method) 30 | require.Equal(true, lo.FromPtr(c.Transport.TCPMux)) 31 | require.Equal(true, lo.FromPtr(c.LoginFailExit)) 32 | require.Equal(true, lo.FromPtr(c.Transport.TLS.Enable)) 33 | require.Equal(true, lo.FromPtr(c.Transport.TLS.DisableCustomTLSFirstByte)) 34 | require.NotEmpty(c.NatHoleSTUNServer) 35 | } 36 | -------------------------------------------------------------------------------- /server/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ServerMetrics interface { 8 | NewClient() 9 | CloseClient() 10 | NewProxy(name string, proxyType string) 11 | CloseProxy(name string, proxyType string) 12 | OpenConnection(name string, proxyType string) 13 | CloseConnection(name string, proxyType string) 14 | AddTrafficIn(name string, proxyType string, trafficBytes int64) 15 | AddTrafficOut(name string, proxyType string, trafficBytes int64) 16 | } 17 | 18 | var Server ServerMetrics = noopServerMetrics{} 19 | 20 | var registerMetrics sync.Once 21 | 22 | func Register(m ServerMetrics) { 23 | registerMetrics.Do(func() { 24 | Server = m 25 | }) 26 | } 27 | 28 | type noopServerMetrics struct{} 29 | 30 | func (noopServerMetrics) NewClient() {} 31 | func (noopServerMetrics) CloseClient() {} 32 | func (noopServerMetrics) NewProxy(string, string) {} 33 | func (noopServerMetrics) CloseProxy(string, string) {} 34 | func (noopServerMetrics) OpenConnection(string, string) {} 35 | func (noopServerMetrics) CloseConnection(string, string) {} 36 | func (noopServerMetrics) AddTrafficIn(string, string, int64) {} 37 | func (noopServerMetrics) AddTrafficOut(string, string, int64) {} 38 | -------------------------------------------------------------------------------- /test/e2e/v1/features/heartbeat.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | 9 | "fxp/test/e2e/framework" 10 | ) 11 | 12 | var _ = ginkgo.Describe("[Feature: Heartbeat]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | ginkgo.It("disable application layer heartbeat", func() { 16 | serverPort := f.AllocPort() 17 | serverConf := fmt.Sprintf(` 18 | bindAddr = "0.0.0.0" 19 | bindPort = %d 20 | transport.heartbeatTimeout = -1 21 | transport.tcpMuxKeepaliveInterval = 2 22 | `, serverPort) 23 | 24 | remotePort := f.AllocPort() 25 | clientConf := fmt.Sprintf(` 26 | serverPort = %d 27 | log.level = "trace" 28 | transport.heartbeatInterval = -1 29 | transport.heartbeatTimeout = -1 30 | transport.tcpMuxKeepaliveInterval = 2 31 | 32 | [[proxies]] 33 | name = "tcp" 34 | type = "tcp" 35 | localPort = %d 36 | remotePort = %d 37 | `, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort) 38 | 39 | // run frps and frpc 40 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 41 | 42 | framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure() 43 | 44 | time.Sleep(5 * time.Second) 45 | framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure() 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/e2e/legacy/basic/xtcp.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | 9 | "fxp/test/e2e/framework" 10 | "fxp/test/e2e/framework/consts" 11 | "fxp/test/e2e/pkg/port" 12 | "fxp/test/e2e/pkg/request" 13 | ) 14 | 15 | var _ = ginkgo.Describe("[Feature: XTCP]", func() { 16 | f := framework.NewDefaultFramework() 17 | 18 | ginkgo.It("Fallback To STCP", func() { 19 | serverConf := consts.LegacyDefaultServerConfig 20 | clientConf := consts.LegacyDefaultClientConfig 21 | 22 | bindPortName := port.GenName("XTCP") 23 | clientConf += fmt.Sprintf(` 24 | [foo] 25 | type = stcp 26 | local_port = {{ .%s }} 27 | 28 | [foo-visitor] 29 | type = stcp 30 | role = visitor 31 | server_name = foo 32 | bind_port = -1 33 | 34 | [bar-visitor] 35 | type = xtcp 36 | role = visitor 37 | server_name = bar 38 | bind_port = {{ .%s }} 39 | keep_tunnel_open = true 40 | fallback_to = foo-visitor 41 | fallback_timeout_ms = 200 42 | `, framework.TCPEchoServerPort, bindPortName) 43 | 44 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 45 | framework.NewRequestExpect(f). 46 | RequestModify(func(r *request.Request) { 47 | r.Timeout(time.Second) 48 | }). 49 | PortName(bindPortName). 50 | Ensure() 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /pkg/msg/ctl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package msg 16 | 17 | import ( 18 | "io" 19 | 20 | jsonMsg "github.com/fatedier/golib/msg/json" 21 | ) 22 | 23 | type Message = jsonMsg.Message 24 | 25 | var msgCtl *jsonMsg.MsgCtl 26 | 27 | func init() { 28 | msgCtl = jsonMsg.NewMsgCtl() 29 | for typeByte, msg := range msgTypeMap { 30 | msgCtl.RegisterMsg(typeByte, msg) 31 | } 32 | } 33 | 34 | func ReadMsg(c io.Reader) (msg Message, err error) { 35 | return msgCtl.ReadMsg(c) 36 | } 37 | 38 | func ReadMsgInto(c io.Reader, msg Message) (err error) { 39 | return msgCtl.ReadMsgInto(c, msg) 40 | } 41 | 42 | func WriteMsg(c io.Writer, msg interface{}) (err error) { 43 | return msgCtl.WriteMsg(c, msg) 44 | } 45 | -------------------------------------------------------------------------------- /test/e2e/v1/basic/xtcp.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | 9 | "fxp/test/e2e/framework" 10 | "fxp/test/e2e/framework/consts" 11 | "fxp/test/e2e/pkg/port" 12 | "fxp/test/e2e/pkg/request" 13 | ) 14 | 15 | var _ = ginkgo.Describe("[Feature: XTCP]", func() { 16 | f := framework.NewDefaultFramework() 17 | 18 | ginkgo.It("Fallback To STCP", func() { 19 | serverConf := consts.DefaultServerConfig 20 | clientConf := consts.DefaultClientConfig 21 | 22 | bindPortName := port.GenName("XTCP") 23 | clientConf += fmt.Sprintf(` 24 | [[proxies]] 25 | name = "foo" 26 | type = "stcp" 27 | localPort = {{ .%s }} 28 | 29 | [[visitors]] 30 | name = "foo-visitor" 31 | type = "stcp" 32 | serverName = "foo" 33 | bindPort = -1 34 | 35 | [[visitors]] 36 | name = "bar-visitor" 37 | type = "xtcp" 38 | serverName = "bar" 39 | bindPort = {{ .%s }} 40 | keepTunnelOpen = true 41 | fallbackTo = "foo-visitor" 42 | fallbackTimeoutMs = 200 43 | `, framework.TCPEchoServerPort, bindPortName) 44 | 45 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 46 | framework.NewRequestExpect(f). 47 | RequestModify(func(r *request.Request) { 48 | r.Timeout(time.Second) 49 | }). 50 | PortName(bindPortName). 51 | Ensure() 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /pkg/util/limit/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package limit 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "golang.org/x/time/rate" 22 | ) 23 | 24 | type Reader struct { 25 | r io.Reader 26 | limiter *rate.Limiter 27 | } 28 | 29 | func NewReader(r io.Reader, limiter *rate.Limiter) *Reader { 30 | return &Reader{ 31 | r: r, 32 | limiter: limiter, 33 | } 34 | } 35 | 36 | func (r *Reader) Read(p []byte) (n int, err error) { 37 | b := r.limiter.Burst() 38 | if b < len(p) { 39 | p = p[:b] 40 | } 41 | n, err = r.r.Read(p) 42 | if err != nil { 43 | return 44 | } 45 | 46 | err = r.limiter.WaitN(context.Background(), n) 47 | if err != nil { 48 | return 49 | } 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /pkg/util/net/dial.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/url" 7 | 8 | libnet "github.com/fatedier/golib/net" 9 | "golang.org/x/net/websocket" 10 | ) 11 | 12 | func DialHookCustomTLSHeadByte(enableTLS bool, disableCustomTLSHeadByte bool) libnet.AfterHookFunc { 13 | return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) { 14 | if enableTLS && !disableCustomTLSHeadByte { 15 | _, err := c.Write([]byte{byte(FRPTLSHeadByte)}) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | } 20 | return ctx, c, nil 21 | } 22 | } 23 | 24 | func DialHookWebsocket(protocol string, host string) libnet.AfterHookFunc { 25 | return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) { 26 | if protocol != "wss" { 27 | protocol = "ws" 28 | } 29 | if host == "" { 30 | host = addr 31 | } 32 | addr = protocol + "://" + host + FrpWebsocketPath 33 | uri, err := url.Parse(addr) 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | 38 | origin := "http://" + uri.Host 39 | cfg, err := websocket.NewConfig(addr, origin) 40 | if err != nil { 41 | return nil, nil, err 42 | } 43 | 44 | conn, err := websocket.NewClient(cfg, c) 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | return ctx, conn, nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/config/legacy/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package legacy 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string { 22 | m := make(map[string]string) 23 | 24 | for key, value := range set { 25 | if strings.HasPrefix(key, prefix) { 26 | m[strings.TrimPrefix(key, prefix)] = value 27 | } 28 | } 29 | 30 | if len(m) == 0 { 31 | return nil 32 | } 33 | 34 | return m 35 | } 36 | 37 | func GetMapByPrefix(set map[string]string, prefix string) map[string]string { 38 | m := make(map[string]string) 39 | 40 | for key, value := range set { 41 | if strings.HasPrefix(key, prefix) { 42 | m[key] = value 43 | } 44 | } 45 | 46 | if len(m) == 0 { 47 | return nil 48 | } 49 | 50 | return m 51 | } 52 | -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package assets 16 | 17 | import ( 18 | "io/fs" 19 | "net/http" 20 | ) 21 | 22 | var ( 23 | // read-only filesystem created by "embed" for embedded files 24 | content fs.FS 25 | 26 | FileSystem http.FileSystem 27 | 28 | // if prefix is not empty, we get file content from disk 29 | prefixPath string 30 | ) 31 | 32 | // if path is empty, load assets in memory 33 | // or set FileSystem using disk files 34 | func Load(path string) { 35 | prefixPath = path 36 | if prefixPath != "" { 37 | FileSystem = http.Dir(prefixPath) 38 | } else { 39 | FileSystem = http.FS(content) 40 | } 41 | } 42 | 43 | func Register(fileSystem fs.FS) { 44 | subFs, err := fs.Sub(fileSystem, "static") 45 | if err == nil { 46 | content = subFs 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/e2e/framework/consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "fxp/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 | bindPort = {{ .%s }} 22 | log.level = "trace" 23 | ` 24 | 25 | DefaultClientConfig = ` 26 | serverAddr = "127.0.0.1" 27 | serverPort = {{ .%s }} 28 | loginFailExit = false 29 | log.level = "trace" 30 | ` 31 | 32 | LegacyDefaultServerConfig = ` 33 | [common] 34 | bind_port = {{ .%s }} 35 | log_level = trace 36 | ` 37 | 38 | LegacyDefaultClientConfig = ` 39 | [common] 40 | server_addr = 127.0.0.1 41 | server_port = {{ .%s }} 42 | login_fail_exit = false 43 | log_level = trace 44 | ` 45 | ) 46 | 47 | func init() { 48 | PortServerName = port.GenName("Server") 49 | PortClientAdmin = port.GenName("ClientAdmin") 50 | LegacyDefaultServerConfig = fmt.Sprintf(LegacyDefaultServerConfig, port.GenName("Server")) 51 | LegacyDefaultClientConfig = fmt.Sprintf(LegacyDefaultClientConfig, port.GenName("Server")) 52 | 53 | DefaultServerConfig = fmt.Sprintf(DefaultServerConfig, port.GenName("Server")) 54 | DefaultClientConfig = fmt.Sprintf(DefaultClientConfig, port.GenName("Server")) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/util/limit/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package limit 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "golang.org/x/time/rate" 22 | ) 23 | 24 | type Writer struct { 25 | w io.Writer 26 | limiter *rate.Limiter 27 | } 28 | 29 | func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer { 30 | return &Writer{ 31 | w: w, 32 | limiter: limiter, 33 | } 34 | } 35 | 36 | func (w *Writer) Write(p []byte) (n int, err error) { 37 | var nn int 38 | b := w.limiter.Burst() 39 | for { 40 | end := len(p) 41 | if end == 0 { 42 | break 43 | } 44 | if b < len(p) { 45 | end = b 46 | } 47 | err = w.limiter.WaitN(context.Background(), end) 48 | if err != nil { 49 | return 50 | } 51 | 52 | nn, err = w.w.Write(p[:end]) 53 | n += nn 54 | if err != nil { 55 | return 56 | } 57 | p = p[end:] 58 | } 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /pkg/config/v1/proxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestUnmarshalTypedProxyConfig(t *testing.T) { 25 | require := require.New(t) 26 | proxyConfigs := struct { 27 | Proxies []TypedProxyConfig `json:"proxies,omitempty"` 28 | }{} 29 | 30 | strs := `{ 31 | "proxies": [ 32 | { 33 | "type": "tcp", 34 | "localPort": 22, 35 | "remotePort": 6000 36 | }, 37 | { 38 | "type": "http", 39 | "localPort": 80, 40 | "customDomains": ["www.example.com"] 41 | } 42 | ] 43 | }` 44 | err := json.Unmarshal([]byte(strs), &proxyConfigs) 45 | require.NoError(err) 46 | 47 | require.IsType(&TCPProxyConfig{}, proxyConfigs.Proxies[0].ProxyConfigurer) 48 | require.IsType(&HTTPProxyConfig{}, proxyConfigs.Proxies[1].ProxyConfigurer) 49 | } 50 | -------------------------------------------------------------------------------- /client/proxy/general_tcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | 20 | v1 "fxp/pkg/config/v1" 21 | ) 22 | 23 | func init() { 24 | pxyConfs := []v1.ProxyConfigurer{ 25 | &v1.TCPProxyConfig{}, 26 | &v1.HTTPProxyConfig{}, 27 | &v1.HTTPSProxyConfig{}, 28 | &v1.STCPProxyConfig{}, 29 | &v1.TCPMuxProxyConfig{}, 30 | } 31 | for _, cfg := range pxyConfs { 32 | RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy) 33 | } 34 | } 35 | 36 | // GeneralTCPProxy is a general implementation of Proxy interface for TCP protocol. 37 | // If the default GeneralTCPProxy cannot meet the requirements, you can customize 38 | // the implementation of the Proxy interface. 39 | type GeneralTCPProxy struct { 40 | *BaseProxy 41 | } 42 | 43 | func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy { 44 | return &GeneralTCPProxy{ 45 | BaseProxy: baseProxy, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/e2e/pkg/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os/exec" 7 | ) 8 | 9 | type Process struct { 10 | cmd *exec.Cmd 11 | cancel context.CancelFunc 12 | errorOutput *bytes.Buffer 13 | stdOutput *bytes.Buffer 14 | 15 | beforeStopHandler func() 16 | stopped bool 17 | } 18 | 19 | func New(path string, params []string) *Process { 20 | return NewWithEnvs(path, params, nil) 21 | } 22 | 23 | func NewWithEnvs(path string, params []string, envs []string) *Process { 24 | ctx, cancel := context.WithCancel(context.Background()) 25 | cmd := exec.CommandContext(ctx, path, params...) 26 | cmd.Env = envs 27 | p := &Process{ 28 | cmd: cmd, 29 | cancel: cancel, 30 | } 31 | p.errorOutput = bytes.NewBufferString("") 32 | p.stdOutput = bytes.NewBufferString("") 33 | cmd.Stderr = p.errorOutput 34 | cmd.Stdout = p.stdOutput 35 | return p 36 | } 37 | 38 | func (p *Process) Start() error { 39 | return p.cmd.Start() 40 | } 41 | 42 | func (p *Process) Stop() error { 43 | if p.stopped { 44 | return nil 45 | } 46 | defer func() { 47 | p.stopped = true 48 | }() 49 | if p.beforeStopHandler != nil { 50 | p.beforeStopHandler() 51 | } 52 | p.cancel() 53 | return p.cmd.Wait() 54 | } 55 | 56 | func (p *Process) ErrorOutput() string { 57 | return p.errorOutput.String() 58 | } 59 | 60 | func (p *Process) StdOutput() string { 61 | return p.stdOutput.String() 62 | } 63 | 64 | func (p *Process) SetBeforeStopHandler(fn func()) { 65 | p.beforeStopHandler = fn 66 | } 67 | -------------------------------------------------------------------------------- /test/e2e/legacy/features/monitor.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/onsi/ginkgo/v2" 9 | 10 | "fxp/pkg/util/log" 11 | "fxp/test/e2e/framework" 12 | "fxp/test/e2e/framework/consts" 13 | "fxp/test/e2e/pkg/request" 14 | ) 15 | 16 | var _ = ginkgo.Describe("[Feature: Monitor]", func() { 17 | f := framework.NewDefaultFramework() 18 | 19 | ginkgo.It("Prometheus metrics", func() { 20 | dashboardPort := f.AllocPort() 21 | serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(` 22 | enable_prometheus = true 23 | dashboard_addr = 0.0.0.0 24 | dashboard_port = %d 25 | `, dashboardPort) 26 | 27 | clientConf := consts.LegacyDefaultClientConfig 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.Tracef("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 | -------------------------------------------------------------------------------- /test/e2e/v1/features/monitor.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/onsi/ginkgo/v2" 9 | 10 | "fxp/pkg/util/log" 11 | "fxp/test/e2e/framework" 12 | "fxp/test/e2e/framework/consts" 13 | "fxp/test/e2e/pkg/request" 14 | ) 15 | 16 | var _ = ginkgo.Describe("[Feature: Monitor]", func() { 17 | f := framework.NewDefaultFramework() 18 | 19 | ginkgo.It("Prometheus metrics", func() { 20 | dashboardPort := f.AllocPort() 21 | serverConf := consts.DefaultServerConfig + fmt.Sprintf(` 22 | enablePrometheus = true 23 | webServer.addr = "0.0.0.0" 24 | webServer.port = %d 25 | `, dashboardPort) 26 | 27 | clientConf := consts.DefaultClientConfig 28 | remotePort := f.AllocPort() 29 | clientConf += fmt.Sprintf(` 30 | [[proxies]] 31 | name = "tcp" 32 | type = "tcp" 33 | localPort = {{ .%s }} 34 | remotePort = %d 35 | `, framework.TCPEchoServerPort, remotePort) 36 | 37 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 38 | 39 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 40 | time.Sleep(500 * time.Millisecond) 41 | 42 | framework.NewRequestExpect(f).RequestModify(func(r *request.Request) { 43 | r.HTTP().Port(dashboardPort).HTTPPath("/metrics") 44 | }).Ensure(func(resp *request.Response) bool { 45 | log.Tracef("prometheus metrics response: \n%s", resp.Content) 46 | if resp.Code != 200 { 47 | return false 48 | } 49 | if !strings.Contains(string(resp.Content), "traffic_in") { 50 | return false 51 | } 52 | return true 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /pkg/util/metric/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metric 16 | 17 | import ( 18 | "sync/atomic" 19 | ) 20 | 21 | type Counter interface { 22 | Count() int32 23 | Inc(int32) 24 | Dec(int32) 25 | Snapshot() Counter 26 | Clear() 27 | } 28 | 29 | func NewCounter() Counter { 30 | return &StandardCounter{ 31 | count: 0, 32 | } 33 | } 34 | 35 | type StandardCounter struct { 36 | count int32 37 | } 38 | 39 | func (c *StandardCounter) Count() int32 { 40 | return atomic.LoadInt32(&c.count) 41 | } 42 | 43 | func (c *StandardCounter) Inc(count int32) { 44 | atomic.AddInt32(&c.count, count) 45 | } 46 | 47 | func (c *StandardCounter) Dec(count int32) { 48 | atomic.AddInt32(&c.count, -count) 49 | } 50 | 51 | func (c *StandardCounter) Snapshot() Counter { 52 | tmp := &StandardCounter{ 53 | count: atomic.LoadInt32(&c.count), 54 | } 55 | return tmp 56 | } 57 | 58 | func (c *StandardCounter) Clear() { 59 | atomic.StoreInt32(&c.count, 0) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/util/net/websocket.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "golang.org/x/net/websocket" 10 | ) 11 | 12 | var ErrWebsocketListenerClosed = errors.New("websocket listener closed") 13 | 14 | const ( 15 | FrpWebsocketPath = "/~!frp" 16 | ) 17 | 18 | type WebsocketListener struct { 19 | ln net.Listener 20 | acceptCh chan net.Conn 21 | 22 | server *http.Server 23 | } 24 | 25 | // NewWebsocketListener to handle websocket connections 26 | // ln: tcp listener for websocket connections 27 | func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { 28 | wl = &WebsocketListener{ 29 | acceptCh: make(chan net.Conn), 30 | } 31 | 32 | muxer := http.NewServeMux() 33 | muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) { 34 | notifyCh := make(chan struct{}) 35 | conn := WrapCloseNotifyConn(c, func() { 36 | close(notifyCh) 37 | }) 38 | wl.acceptCh <- conn 39 | <-notifyCh 40 | })) 41 | 42 | wl.server = &http.Server{ 43 | Addr: ln.Addr().String(), 44 | Handler: muxer, 45 | ReadHeaderTimeout: 60 * time.Second, 46 | } 47 | 48 | go func() { 49 | _ = wl.server.Serve(ln) 50 | }() 51 | return 52 | } 53 | 54 | func (p *WebsocketListener) Accept() (net.Conn, error) { 55 | c, ok := <-p.acceptCh 56 | if !ok { 57 | return nil, ErrWebsocketListenerClosed 58 | } 59 | return c, nil 60 | } 61 | 62 | func (p *WebsocketListener) Close() error { 63 | return p.server.Close() 64 | } 65 | 66 | func (p *WebsocketListener) Addr() net.Addr { 67 | return p.ln.Addr() 68 | } 69 | -------------------------------------------------------------------------------- /test/e2e/v1/basic/annotations.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/onsi/ginkgo/v2" 9 | "github.com/tidwall/gjson" 10 | 11 | "fxp/test/e2e/framework" 12 | "fxp/test/e2e/framework/consts" 13 | ) 14 | 15 | var _ = ginkgo.Describe("[Feature: Annotations]", func() { 16 | f := framework.NewDefaultFramework() 17 | 18 | ginkgo.It("Set Proxy Annotations", func() { 19 | webPort := f.AllocPort() 20 | 21 | serverConf := consts.DefaultServerConfig + fmt.Sprintf(` 22 | webServer.port = %d 23 | `, webPort) 24 | 25 | p1Port := f.AllocPort() 26 | 27 | clientConf := consts.DefaultClientConfig + fmt.Sprintf(` 28 | [[proxies]] 29 | name = "p1" 30 | type = "tcp" 31 | localPort = {{ .%s }} 32 | remotePort = %d 33 | [proxies.annotations] 34 | "frp.e2e.test/foo" = "value1" 35 | "frp.e2e.test/bar" = "value2" 36 | `, framework.TCPEchoServerPort, p1Port) 37 | 38 | f.RunProcesses([]string{serverConf}, []string{clientConf}) 39 | 40 | framework.NewRequestExpect(f).Port(p1Port).Ensure() 41 | 42 | // check annotations in frps 43 | resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/api/proxy/tcp/%s", webPort, "p1")) 44 | framework.ExpectNoError(err) 45 | framework.ExpectEqual(resp.StatusCode, 200) 46 | defer resp.Body.Close() 47 | content, err := io.ReadAll(resp.Body) 48 | framework.ExpectNoError(err) 49 | 50 | annotations := gjson.Get(string(content), "conf.annotations").Map() 51 | framework.ExpectEqual("value1", annotations["frp.e2e.test/foo"].String()) 52 | framework.ExpectEqual("value2", annotations["frp.e2e.test/bar"].String()) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/e2e/framework/test_context.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type TestContextType struct { 10 | FRPClientPath string 11 | FRPServerPath string 12 | LogLevel string 13 | Debug bool 14 | } 15 | 16 | var TestContext TestContextType 17 | 18 | // RegisterCommonFlags registers flags common to all e2e test suites. 19 | // The flag set can be flag.CommandLine (if desired) or a custom 20 | // flag set that then gets passed to viperconfig.ViperizeFlags. 21 | // 22 | // The other Register*Flags methods below can be used to add more 23 | // test-specific flags. However, those settings then get added 24 | // regardless whether the test is actually in the test suite. 25 | func RegisterCommonFlags(flags *flag.FlagSet) { 26 | flags.StringVar(&TestContext.FRPClientPath, "frpc-path", "../../bin/frpc", "The frp client binary to use.") 27 | flags.StringVar(&TestContext.FRPServerPath, "frps-path", "../../bin/frps", "The frp server binary to use.") 28 | flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.") 29 | flags.BoolVar(&TestContext.Debug, "debug", false, "Enable debug mode to print detail info.") 30 | } 31 | 32 | func ValidateTestContext(t *TestContextType) error { 33 | if t.FRPClientPath == "" || t.FRPServerPath == "" { 34 | return fmt.Errorf("frpc and frps binary path can't be empty") 35 | } 36 | if _, err := os.Stat(t.FRPClientPath); err != nil { 37 | return fmt.Errorf("load frpc-path error: %v", err) 38 | } 39 | if _, err := os.Stat(t.FRPServerPath); err != nil { 40 | return fmt.Errorf("load frps-path error: %v", err) 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /cmd/frps/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | 23 | "fxp/pkg/config" 24 | "fxp/pkg/config/v1/validation" 25 | ) 26 | 27 | func init() { 28 | rootCmd.AddCommand(verifyCmd) 29 | } 30 | 31 | var verifyCmd = &cobra.Command{ 32 | Use: "verify", 33 | Short: "Verify that the configures is valid", 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | if cfgFile == "" { 36 | fmt.Println("frps: the configuration file is not specified") 37 | return nil 38 | } 39 | svrCfg, _, err := config.LoadServerConfig(cfgFile, strictConfigMode) 40 | if err != nil { 41 | fmt.Println(err) 42 | os.Exit(1) 43 | } 44 | 45 | warning, err := validation.ValidateServerConfig(svrCfg) 46 | if warning != nil { 47 | fmt.Printf("WARNING: %v\n", warning) 48 | } 49 | if err != nil { 50 | fmt.Println(err) 51 | os.Exit(1) 52 | } 53 | fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile) 54 | return nil 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /web/frps/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router' 2 | import ServerOverview from '../components/ServerOverview.vue' 3 | import ProxiesTCP from '../components/ProxiesTCP.vue' 4 | import ProxiesUDP from '../components/ProxiesUDP.vue' 5 | import ProxiesHTTP from '../components/ProxiesHTTP.vue' 6 | import ProxiesHTTPS from '../components/ProxiesHTTPS.vue' 7 | import ProxiesTCPMux from '../components/ProxiesTCPMux.vue' 8 | import ProxiesSTCP from '../components/ProxiesSTCP.vue' 9 | import ProxiesSUDP from '../components/ProxiesSUDP.vue' 10 | 11 | const router = createRouter({ 12 | history: createWebHashHistory(), 13 | routes: [ 14 | { 15 | path: '/', 16 | name: 'ServerOverview', 17 | component: ServerOverview, 18 | }, 19 | { 20 | path: '/proxies/tcp', 21 | name: 'ProxiesTCP', 22 | component: ProxiesTCP, 23 | }, 24 | { 25 | path: '/proxies/udp', 26 | name: 'ProxiesUDP', 27 | component: ProxiesUDP, 28 | }, 29 | { 30 | path: '/proxies/http', 31 | name: 'ProxiesHTTP', 32 | component: ProxiesHTTP, 33 | }, 34 | { 35 | path: '/proxies/https', 36 | name: 'ProxiesHTTPS', 37 | component: ProxiesHTTPS, 38 | }, 39 | { 40 | path: '/proxies/tcpmux', 41 | name: 'ProxiesTCPMux', 42 | component: ProxiesTCPMux, 43 | }, 44 | { 45 | path: '/proxies/stcp', 46 | name: 'ProxiesSTCP', 47 | component: ProxiesSTCP, 48 | }, 49 | { 50 | path: '/proxies/sudp', 51 | name: 'ProxiesSUDP', 52 | component: ProxiesSUDP, 53 | }, 54 | ], 55 | }) 56 | 57 | export default router 58 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "fmt" 19 | "slices" 20 | 21 | v1 "fxp/pkg/config/v1" 22 | ) 23 | 24 | func validateWebServerConfig(c *v1.WebServerConfig) error { 25 | if c.TLS != nil { 26 | if c.TLS.CertFile == "" { 27 | return fmt.Errorf("tls.certFile must be specified when tls is enabled") 28 | } 29 | if c.TLS.KeyFile == "" { 30 | return fmt.Errorf("tls.keyFile must be specified when tls is enabled") 31 | } 32 | } 33 | 34 | return ValidatePort(c.Port, "webServer.port") 35 | } 36 | 37 | // ValidatePort checks that the network port is in range 38 | func ValidatePort(port int, fieldPath string) error { 39 | if 0 <= port && port <= 65535 { 40 | return nil 41 | } 42 | return fmt.Errorf("%s: port number %d must be in the range 0..65535", fieldPath, port) 43 | } 44 | 45 | func validateLogConfig(c *v1.LogConfig) error { 46 | if !slices.Contains(SupportedLogLevels, c.Level) { 47 | return fmt.Errorf("invalid log level, optional values are %v", SupportedLogLevels) 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "errors" 19 | 20 | v1 "fxp/pkg/config/v1" 21 | splugin "fxp/pkg/plugin/server" 22 | ) 23 | 24 | var ( 25 | SupportedTransportProtocols = []string{ 26 | "tcp", 27 | "kcp", 28 | "quic", 29 | "websocket", 30 | "wss", 31 | } 32 | 33 | SupportedAuthMethods = []v1.AuthMethod{ 34 | "token", 35 | "oidc", 36 | } 37 | 38 | SupportedAuthAdditionalScopes = []v1.AuthScope{ 39 | "HeartBeats", 40 | "NewWorkConns", 41 | } 42 | 43 | SupportedLogLevels = []string{ 44 | "trace", 45 | "debug", 46 | "info", 47 | "warn", 48 | "error", 49 | } 50 | 51 | SupportedHTTPPluginOps = []string{ 52 | splugin.OpLogin, 53 | splugin.OpNewProxy, 54 | splugin.OpCloseProxy, 55 | splugin.OpPing, 56 | splugin.OpNewWorkConn, 57 | splugin.OpNewUserConn, 58 | } 59 | ) 60 | 61 | type Warning error 62 | 63 | func AppendError(err error, errs ...error) error { 64 | if len(errs) == 0 { 65 | return err 66 | } 67 | return errors.Join(append([]error{err}, errs...)...) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/config/template.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "fmt" 19 | 20 | "fxp/pkg/util/util" 21 | ) 22 | 23 | type NumberPair struct { 24 | First int64 25 | Second int64 26 | } 27 | 28 | func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]NumberPair, error) { 29 | firstRangeNumbers, err := util.ParseRangeNumbers(firstRangeStr) 30 | if err != nil { 31 | return nil, err 32 | } 33 | secondRangeNumbers, err := util.ParseRangeNumbers(secondRangeStr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if len(firstRangeNumbers) != len(secondRangeNumbers) { 38 | return nil, fmt.Errorf("first and second range numbers are not in pairs") 39 | } 40 | pairs := make([]NumberPair, 0, len(firstRangeNumbers)) 41 | for i := 0; i < len(firstRangeNumbers); i++ { 42 | pairs = append(pairs, NumberPair{ 43 | First: firstRangeNumbers[i], 44 | Second: secondRangeNumbers[i], 45 | }) 46 | } 47 | return pairs, nil 48 | } 49 | 50 | func parseNumberRange(firstRangeStr string) ([]int64, error) { 51 | return util.ParseRangeNumbers(firstRangeStr) 52 | } 53 | -------------------------------------------------------------------------------- /cmd/frpc/sub/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sub 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | 23 | "fxp/pkg/config" 24 | "fxp/pkg/config/v1/validation" 25 | ) 26 | 27 | func init() { 28 | rootCmd.AddCommand(verifyCmd) 29 | } 30 | 31 | var verifyCmd = &cobra.Command{ 32 | Use: "verify", 33 | Short: "Verify that the configures is valid", 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | if cfgFile == "" { 36 | fmt.Println("frpc: the configuration file is not specified") 37 | return nil 38 | } 39 | 40 | cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode) 41 | if err != nil { 42 | fmt.Println(err) 43 | os.Exit(1) 44 | } 45 | warning, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs) 46 | if warning != nil { 47 | fmt.Printf("WARNING: %v\n", warning) 48 | } 49 | if err != nil { 50 | fmt.Println(err) 51 | os.Exit(1) 52 | } 53 | 54 | fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile) 55 | return nil 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PATH := $(PATH):`go env GOPATH`/bin 2 | export GO111MODULE=on 3 | LDFLAGS := -s -w 4 | 5 | all: env fmt build 6 | 7 | build: frps frpc 8 | 9 | env: 10 | @go version 11 | 12 | # compile assets into binary file 13 | file: 14 | rm -rf ./assets/frps/static/* 15 | rm -rf ./assets/frpc/static/* 16 | cp -rf ./web/frps/dist/* ./assets/frps/static 17 | cp -rf ./web/frpc/dist/* ./assets/frpc/static 18 | 19 | fmt: 20 | go fmt ./... 21 | 22 | fmt-more: 23 | gofumpt -l -w . 24 | 25 | gci: 26 | gci write -s standard -s default -s "prefix(fxp/)" ./ 27 | 28 | vet: 29 | go vet ./... 30 | 31 | frps: 32 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o bin/frps ./cmd/frps 33 | 34 | frpc: 35 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o bin/frpc ./cmd/frpc 36 | 37 | test: gotest 38 | 39 | gotest: 40 | go test -v --cover ./assets/... 41 | go test -v --cover ./cmd/... 42 | go test -v --cover ./client/... 43 | go test -v --cover ./server/... 44 | go test -v --cover ./pkg/... 45 | 46 | e2e: 47 | ./hack/run-e2e.sh 48 | 49 | e2e-trace: 50 | DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh 51 | 52 | e2e-compatibility-last-frpc: 53 | if [ ! -d "./lastversion" ]; then \ 54 | TARGET_DIRNAME=lastversion ./hack/download.sh; \ 55 | fi 56 | FRPC_PATH="`pwd`/lastversion/frpc" ./hack/run-e2e.sh 57 | rm -r ./lastversion 58 | 59 | e2e-compatibility-last-frps: 60 | if [ ! -d "./lastversion" ]; then \ 61 | TARGET_DIRNAME=lastversion ./hack/download.sh; \ 62 | fi 63 | FRPS_PATH="`pwd`/lastversion/frps" ./hack/run-e2e.sh 64 | rm -r ./lastversion 65 | 66 | alltest: vet gotest e2e 67 | 68 | clean: 69 | rm -f ./bin/frpc 70 | rm -f ./bin/frps 71 | rm -rf ./lastversion 72 | -------------------------------------------------------------------------------- /pkg/util/net/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "crypto/tls" 19 | "fmt" 20 | "net" 21 | "time" 22 | 23 | libnet "github.com/fatedier/golib/net" 24 | ) 25 | 26 | // var FRPTLSHeadByte = 0x17 27 | 28 | var FRPTLSHeadByte = 0x91 29 | 30 | func CheckAndEnableTLSServerConnWithTimeout( 31 | c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration, 32 | ) (out net.Conn, isTLS bool, custom bool, err error) { 33 | sc, r := libnet.NewSharedConnSize(c, 2) 34 | buf := make([]byte, 1) 35 | var n int 36 | _ = c.SetReadDeadline(time.Now().Add(timeout)) 37 | n, err = r.Read(buf) 38 | _ = c.SetReadDeadline(time.Time{}) 39 | if err != nil { 40 | return 41 | } 42 | 43 | switch { 44 | case n == 1 && int(buf[0]) == FRPTLSHeadByte: 45 | out = tls.Server(c, tlsConfig) 46 | isTLS = true 47 | custom = true 48 | case n == 1 && int(buf[0]) == 0x16: 49 | out = tls.Server(sc, tlsConfig) 50 | isTLS = true 51 | default: 52 | if tlsOnly { 53 | err = fmt.Errorf("non-TLS connection received on a TlsOnly server") 54 | return 55 | } 56 | out = sc 57 | } 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 guylewin, guy@lewin.co.il 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "fmt" 19 | 20 | v1 "fxp/pkg/config/v1" 21 | "fxp/pkg/msg" 22 | ) 23 | 24 | type Setter interface { 25 | SetLogin(*msg.Login) error 26 | SetPing(*msg.Ping) error 27 | SetNewWorkConn(*msg.NewWorkConn) error 28 | } 29 | 30 | func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) { 31 | switch cfg.Method { 32 | case v1.AuthMethodToken: 33 | authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) 34 | case v1.AuthMethodOIDC: 35 | authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC) 36 | default: 37 | panic(fmt.Sprintf("wrong method: '%s'", cfg.Method)) 38 | } 39 | return authProvider 40 | } 41 | 42 | type Verifier interface { 43 | VerifyLogin(*msg.Login) error 44 | VerifyPing(*msg.Ping) error 45 | VerifyNewWorkConn(*msg.NewWorkConn) error 46 | } 47 | 48 | func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) { 49 | switch cfg.Method { 50 | case v1.AuthMethodToken: 51 | authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) 52 | case v1.AuthMethodOIDC: 53 | authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC) 54 | } 55 | return authVerifier 56 | } 57 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/visitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "slices" 21 | 22 | v1 "fxp/pkg/config/v1" 23 | ) 24 | 25 | func ValidateVisitorConfigurer(c v1.VisitorConfigurer) error { 26 | base := c.GetBaseConfig() 27 | if err := validateVisitorBaseConfig(base); err != nil { 28 | return err 29 | } 30 | 31 | switch v := c.(type) { 32 | case *v1.STCPVisitorConfig: 33 | case *v1.SUDPVisitorConfig: 34 | case *v1.XTCPVisitorConfig: 35 | return validateXTCPVisitorConfig(v) 36 | default: 37 | return errors.New("unknown visitor config type") 38 | } 39 | return nil 40 | } 41 | 42 | func validateVisitorBaseConfig(c *v1.VisitorBaseConfig) error { 43 | if c.Name == "" { 44 | return errors.New("name is required") 45 | } 46 | 47 | if c.ServerName == "" { 48 | return errors.New("server name is required") 49 | } 50 | 51 | if c.BindPort == 0 { 52 | return errors.New("bind port is required") 53 | } 54 | return nil 55 | } 56 | 57 | func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error { 58 | if !slices.Contains([]string{"kcp", "quic"}, c.Protocol) { 59 | return fmt.Errorf("protocol should be kcp or quic") 60 | } 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /Makefile.cross-compiles: -------------------------------------------------------------------------------- 1 | export PATH := $(PATH):`go env GOPATH`/bin 2 | export GO111MODULE=on 3 | LDFLAGS := -s -w 4 | 5 | os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 android:arm64 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 | extra=$(shell echo "$(n)" | cut -d : -f 3); \ 16 | flags=''; \ 17 | target_suffix=$${os}_$${arch}; \ 18 | if [ "$${os}" = "linux" ] && [ "$${arch}" = "arm" ] && [ "$${extra}" != "" ] ; then \ 19 | if [ "$${extra}" = "7" ]; then \ 20 | flags=GOARM=7; \ 21 | target_suffix=$${os}_arm_hf; \ 22 | elif [ "$${extra}" = "5" ]; then \ 23 | flags=GOARM=5; \ 24 | target_suffix=$${os}_arm; \ 25 | fi; \ 26 | elif [ "$${os}" = "linux" ] && ([ "$${arch}" = "mips" ] || [ "$${arch}" = "mipsle" ]) && [ "$${extra}" != "" ] ; then \ 27 | flags=GOMIPS=$${extra}; \ 28 | fi; \ 29 | echo "Build $${os}-$${arch}$${extra:+ ($${extra})}..."; \ 30 | env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $${flags} go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o ./release/frpc_$${target_suffix} ./cmd/frpc; \ 31 | env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $${flags} go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o ./release/frps_$${target_suffix} ./cmd/frps; \ 32 | echo "Build $${os}-$${arch}$${extra:+ ($${extra})} done"; \ 33 | ) 34 | @mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe 35 | @mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe 36 | @mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe 37 | @mv ./release/frps_windows_arm64 ./release/frps_windows_arm64.exe 38 | -------------------------------------------------------------------------------- /pkg/config/legacy/value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package legacy 16 | 17 | import ( 18 | "bytes" 19 | "os" 20 | "strings" 21 | "text/template" 22 | ) 23 | 24 | var glbEnvs map[string]string 25 | 26 | func init() { 27 | glbEnvs = make(map[string]string) 28 | envs := os.Environ() 29 | for _, env := range envs { 30 | pair := strings.SplitN(env, "=", 2) 31 | if len(pair) != 2 { 32 | continue 33 | } 34 | glbEnvs[pair[0]] = pair[1] 35 | } 36 | } 37 | 38 | type Values struct { 39 | Envs map[string]string // environment vars 40 | } 41 | 42 | func GetValues() *Values { 43 | return &Values{ 44 | Envs: glbEnvs, 45 | } 46 | } 47 | 48 | func RenderContent(in []byte) (out []byte, err error) { 49 | tmpl, errRet := template.New("frp").Parse(string(in)) 50 | if errRet != nil { 51 | err = errRet 52 | return 53 | } 54 | 55 | buffer := bytes.NewBufferString("") 56 | v := GetValues() 57 | err = tmpl.Execute(buffer, v) 58 | if err != nil { 59 | return 60 | } 61 | out = buffer.Bytes() 62 | return 63 | } 64 | 65 | func GetRenderedConfFromFile(path string) (out []byte, err error) { 66 | var b []byte 67 | b, err = os.ReadFile(path) 68 | if err != nil { 69 | return 70 | } 71 | 72 | out, err = RenderContent(b) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /hack/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | OS="$(go env GOOS)" 4 | ARCH="$(go env GOARCH)" 5 | 6 | if [ "${TARGET_OS}" ]; then 7 | OS="${TARGET_OS}" 8 | fi 9 | if [ "${TARGET_ARCH}" ]; then 10 | ARCH="${TARGET_ARCH}" 11 | fi 12 | 13 | # Determine the latest version by version number ignoring alpha, beta, and rc versions. 14 | if [ "${FRP_VERSION}" = "" ] ; then 15 | FRP_VERSION="$(curl -sL https://fxp/releases | \ 16 | grep -o 'releases/tag/v[0-9]*.[0-9]*.[0-9]*"' | sort -V | \ 17 | tail -1 | awk -F'/' '{ print $3}')" 18 | FRP_VERSION="${FRP_VERSION%?}" 19 | FRP_VERSION="${FRP_VERSION#?}" 20 | fi 21 | 22 | if [ "${FRP_VERSION}" = "" ] ; then 23 | printf "Unable to get latest frp version. Set FRP_VERSION env var and re-run. For example: export FRP_VERSION=1.0.0" 24 | exit 1; 25 | fi 26 | 27 | SUFFIX=".tar.gz" 28 | if [ "${OS}" = "windows" ] ; then 29 | SUFFIX=".zip" 30 | fi 31 | NAME="frp_${FRP_VERSION}_${OS}_${ARCH}${SUFFIX}" 32 | DIR_NAME="frp_${FRP_VERSION}_${OS}_${ARCH}" 33 | URL="https://fxp/releases/download/v${FRP_VERSION}/${NAME}" 34 | 35 | download_and_extract() { 36 | printf "Downloading %s from %s ...\n" "$NAME" "${URL}" 37 | if ! curl -o /dev/null -sIf "${URL}"; then 38 | printf "\n%s is not found, please specify a valid FRP_VERSION\n" "${URL}" 39 | exit 1 40 | fi 41 | curl -fsLO "${URL}" 42 | filename=$NAME 43 | 44 | if [ "${OS}" = "windows" ]; then 45 | unzip "${filename}" 46 | else 47 | tar -xzf "${filename}" 48 | fi 49 | rm "${filename}" 50 | 51 | if [ "${TARGET_DIRNAME}" ]; then 52 | mv "${DIR_NAME}" "${TARGET_DIRNAME}" 53 | DIR_NAME="${TARGET_DIRNAME}" 54 | fi 55 | } 56 | 57 | download_and_extract 58 | 59 | printf "" 60 | printf "\nfrp %s Download Complete!\n" "$FRP_VERSION" 61 | printf "\n" 62 | printf "frp has been successfully downloaded into the %s folder on your system.\n" "$DIR_NAME" 63 | printf "\n" 64 | -------------------------------------------------------------------------------- /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 | fromPort, err := strconv.Atoi(arrs[2]) 30 | if err != nil { 31 | return nil, fmt.Errorf("error range port from") 32 | } 33 | builder.rangePortFrom = fromPort 34 | 35 | toPort, err := strconv.Atoi(arrs[3]) 36 | if err != nil { 37 | return nil, fmt.Errorf("error range port to") 38 | } 39 | builder.rangePortTo = toPort 40 | default: 41 | return nil, fmt.Errorf("error port name format") 42 | } 43 | return &builder, nil 44 | } 45 | 46 | func (builder *nameBuilder) String() string { 47 | name := fmt.Sprintf("Port%s%s", NameDelimiter, builder.name) 48 | if builder.rangePortFrom > 0 && builder.rangePortTo > 0 && builder.rangePortTo > builder.rangePortFrom { 49 | name += fmt.Sprintf("%s%d%s%d", NameDelimiter, builder.rangePortFrom, NameDelimiter, builder.rangePortTo) 50 | } 51 | return name 52 | } 53 | 54 | func WithRangePorts(from, to int) NameOption { 55 | return func(builder *nameBuilder) *nameBuilder { 56 | builder.rangePortFrom = from 57 | builder.rangePortTo = to 58 | return builder 59 | } 60 | } 61 | 62 | func GenName(name string, options ...NameOption) string { 63 | name = strings.ReplaceAll(name, "-", "") 64 | name = strings.ReplaceAll(name, "_", "") 65 | builder := &nameBuilder{name: name} 66 | for _, option := range options { 67 | builder = option(builder) 68 | } 69 | return builder.String() 70 | } 71 | -------------------------------------------------------------------------------- /pkg/plugin/client/socks5.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "io" 21 | "log" 22 | "net" 23 | 24 | gosocks5 "github.com/armon/go-socks5" 25 | 26 | v1 "fxp/pkg/config/v1" 27 | netpkg "fxp/pkg/util/net" 28 | ) 29 | 30 | func init() { 31 | Register(v1.PluginSocks5, NewSocks5Plugin) 32 | } 33 | 34 | type Socks5Plugin struct { 35 | Server *gosocks5.Server 36 | } 37 | 38 | func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) { 39 | opts := options.(*v1.Socks5PluginOptions) 40 | 41 | cfg := &gosocks5.Config{ 42 | Logger: log.New(io.Discard, "", log.LstdFlags), 43 | } 44 | if opts.Username != "" || opts.Password != "" { 45 | cfg.Credentials = gosocks5.StaticCredentials(map[string]string{opts.Username: opts.Password}) 46 | } 47 | sp := &Socks5Plugin{} 48 | sp.Server, err = gosocks5.New(cfg) 49 | p = sp 50 | return 51 | } 52 | 53 | func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 54 | defer conn.Close() 55 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 56 | _ = sp.Server.ServeConn(wrapConn) 57 | } 58 | 59 | func (sp *Socks5Plugin) Name() string { 60 | return v1.PluginSocks5 61 | } 62 | 63 | func (sp *Socks5Plugin) Close() error { 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/config/types/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package types 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | type Wrap struct { 25 | B BandwidthQuantity `json:"b"` 26 | Int int `json:"int"` 27 | } 28 | 29 | func TestBandwidthQuantity(t *testing.T) { 30 | require := require.New(t) 31 | 32 | var w Wrap 33 | err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w) 34 | require.NoError(err) 35 | require.EqualValues(1*KB, w.B.Bytes()) 36 | 37 | buf, err := json.Marshal(&w) 38 | require.NoError(err) 39 | require.Equal(`{"b":"1KB","int":5}`, string(buf)) 40 | } 41 | 42 | func TestPortsRangeSlice2String(t *testing.T) { 43 | require := require.New(t) 44 | 45 | ports := []PortsRange{ 46 | { 47 | Start: 1000, 48 | End: 2000, 49 | }, 50 | { 51 | Single: 3000, 52 | }, 53 | } 54 | str := PortsRangeSlice(ports).String() 55 | require.Equal("1000-2000,3000", str) 56 | } 57 | 58 | func TestNewPortsRangeSliceFromString(t *testing.T) { 59 | require := require.New(t) 60 | 61 | ports, err := NewPortsRangeSliceFromString("1000-2000,3000") 62 | require.NoError(err) 63 | require.Equal([]PortsRange{ 64 | { 65 | Start: 1000, 66 | End: 2000, 67 | }, 68 | { 69 | Single: 3000, 70 | }, 71 | }, ports) 72 | } 73 | -------------------------------------------------------------------------------- /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 | 10 | type cleanupFuncHandle struct { 11 | actionHandle CleanupActionHandle 12 | actionHook func() 13 | } 14 | 15 | var ( 16 | cleanupActionsLock sync.Mutex 17 | cleanupHookList = []cleanupFuncHandle{} 18 | ) 19 | 20 | // AddCleanupAction installs a function that will be called in the event of the 21 | // whole test being terminated. This allows arbitrary pieces of the overall 22 | // test to hook into SynchronizedAfterSuite(). 23 | // The hooks are called in last-in-first-out order. 24 | func AddCleanupAction(fn func()) CleanupActionHandle { 25 | p := CleanupActionHandle(new(int)) 26 | cleanupActionsLock.Lock() 27 | defer cleanupActionsLock.Unlock() 28 | c := cleanupFuncHandle{actionHandle: p, actionHook: fn} 29 | cleanupHookList = append([]cleanupFuncHandle{c}, cleanupHookList...) 30 | return p 31 | } 32 | 33 | // RemoveCleanupAction removes a function that was installed by 34 | // AddCleanupAction. 35 | func RemoveCleanupAction(p CleanupActionHandle) { 36 | cleanupActionsLock.Lock() 37 | defer cleanupActionsLock.Unlock() 38 | for i, item := range cleanupHookList { 39 | if item.actionHandle == p { 40 | cleanupHookList = append(cleanupHookList[:i], cleanupHookList[i+1:]...) 41 | break 42 | } 43 | } 44 | } 45 | 46 | // RunCleanupActions runs all functions installed by AddCleanupAction. It does 47 | // not remove them (see RemoveCleanupAction) but it does run unlocked, so they 48 | // may remove themselves. 49 | func RunCleanupActions() { 50 | list := []func(){} 51 | func() { 52 | cleanupActionsLock.Lock() 53 | defer cleanupActionsLock.Unlock() 54 | for _, p := range cleanupHookList { 55 | list = append(list, p.actionHook) 56 | } 57 | }() 58 | // Run unlocked. 59 | for _, fn := range list { 60 | fn() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/e2e/pkg/ssh/client.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "net" 5 | 6 | libio "github.com/fatedier/golib/io" 7 | "golang.org/x/crypto/ssh" 8 | ) 9 | 10 | type TunnelClient struct { 11 | localAddr string 12 | sshServer string 13 | commands string 14 | 15 | sshConn *ssh.Client 16 | ln net.Listener 17 | } 18 | 19 | func NewTunnelClient(localAddr string, sshServer string, commands string) *TunnelClient { 20 | return &TunnelClient{ 21 | localAddr: localAddr, 22 | sshServer: sshServer, 23 | commands: commands, 24 | } 25 | } 26 | 27 | func (c *TunnelClient) Start() error { 28 | config := &ssh.ClientConfig{ 29 | User: "v0", 30 | HostKeyCallback: func(string, net.Addr, ssh.PublicKey) error { return nil }, 31 | } 32 | 33 | conn, err := ssh.Dial("tcp", c.sshServer, config) 34 | if err != nil { 35 | return err 36 | } 37 | c.sshConn = conn 38 | 39 | l, err := conn.Listen("tcp", "0.0.0.0:80") 40 | if err != nil { 41 | return err 42 | } 43 | c.ln = l 44 | ch, req, err := conn.OpenChannel("session", []byte("")) 45 | if err != nil { 46 | return err 47 | } 48 | defer ch.Close() 49 | go ssh.DiscardRequests(req) 50 | 51 | type command struct { 52 | Cmd string 53 | } 54 | _, err = ch.SendRequest("exec", false, ssh.Marshal(command{Cmd: c.commands})) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | go c.serveListener() 60 | return nil 61 | } 62 | 63 | func (c *TunnelClient) Close() { 64 | if c.sshConn != nil { 65 | _ = c.sshConn.Close() 66 | } 67 | if c.ln != nil { 68 | _ = c.ln.Close() 69 | } 70 | } 71 | 72 | func (c *TunnelClient) serveListener() { 73 | for { 74 | conn, err := c.ln.Accept() 75 | if err != nil { 76 | return 77 | } 78 | go c.hanldeConn(conn) 79 | } 80 | } 81 | 82 | func (c *TunnelClient) hanldeConn(conn net.Conn) { 83 | defer conn.Close() 84 | local, err := net.Dial("tcp", c.localAddr) 85 | if err != nil { 86 | return 87 | } 88 | _, _, _ = libio.Join(local, conn) 89 | } 90 | -------------------------------------------------------------------------------- /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 | "fxp/pkg/nathole" 19 | plugin "fxp/pkg/plugin/server" 20 | "fxp/pkg/util/tcpmux" 21 | "fxp/pkg/util/vhost" 22 | "fxp/server/group" 23 | "fxp/server/ports" 24 | "fxp/server/visitor" 25 | ) 26 | 27 | // All resource managers and controllers 28 | type ResourceController struct { 29 | // Manage all visitor listeners 30 | VisitorManager *visitor.Manager 31 | 32 | // TCP Group Controller 33 | TCPGroupCtl *group.TCPGroupCtl 34 | 35 | // HTTP Group Controller 36 | HTTPGroupCtl *group.HTTPGroupController 37 | 38 | // TCP Mux Group Controller 39 | TCPMuxGroupCtl *group.TCPMuxGroupCtl 40 | 41 | // Manage all TCP ports 42 | TCPPortManager *ports.Manager 43 | 44 | // Manage all UDP ports 45 | UDPPortManager *ports.Manager 46 | 47 | // For HTTP proxies, forwarding HTTP requests 48 | HTTPReverseProxy *vhost.HTTPReverseProxy 49 | 50 | // For HTTPS proxies, route requests to different clients by hostname and other information 51 | VhostHTTPSMuxer *vhost.HTTPSMuxer 52 | 53 | // Controller for nat hole connections 54 | NatHoleController *nathole.Controller 55 | 56 | // TCPMux HTTP CONNECT multiplexer 57 | TCPMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxer 58 | 59 | // All server manager plugin 60 | PluginManager *plugin.Manager 61 | } 62 | -------------------------------------------------------------------------------- /server/proxy/stcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | 20 | v1 "fxp/pkg/config/v1" 21 | ) 22 | 23 | func init() { 24 | RegisterProxyFactory(reflect.TypeOf(&v1.STCPProxyConfig{}), NewSTCPProxy) 25 | } 26 | 27 | type STCPProxy struct { 28 | *BaseProxy 29 | cfg *v1.STCPProxyConfig 30 | } 31 | 32 | func NewSTCPProxy(baseProxy *BaseProxy) Proxy { 33 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.STCPProxyConfig) 34 | if !ok { 35 | return nil 36 | } 37 | return &STCPProxy{ 38 | BaseProxy: baseProxy, 39 | cfg: unwrapped, 40 | } 41 | } 42 | 43 | func (pxy *STCPProxy) Run() (remoteAddr string, err error) { 44 | xl := pxy.xl 45 | allowUsers := pxy.cfg.AllowUsers 46 | // if allowUsers is empty, only allow same user from proxy 47 | if len(allowUsers) == 0 { 48 | allowUsers = []string{pxy.GetUserInfo().User} 49 | } 50 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers) 51 | if errRet != nil { 52 | err = errRet 53 | return 54 | } 55 | pxy.listeners = append(pxy.listeners, listener) 56 | xl.Infof("stcp proxy custom listen success") 57 | 58 | pxy.startCommonTCPListenersHandler() 59 | return 60 | } 61 | 62 | func (pxy *STCPProxy) Close() { 63 | pxy.BaseProxy.Close() 64 | pxy.rc.VisitorManager.CloseListener(pxy.GetName()) 65 | } 66 | -------------------------------------------------------------------------------- /server/proxy/sudp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | 20 | v1 "fxp/pkg/config/v1" 21 | ) 22 | 23 | func init() { 24 | RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy) 25 | } 26 | 27 | type SUDPProxy struct { 28 | *BaseProxy 29 | cfg *v1.SUDPProxyConfig 30 | } 31 | 32 | func NewSUDPProxy(baseProxy *BaseProxy) Proxy { 33 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.SUDPProxyConfig) 34 | if !ok { 35 | return nil 36 | } 37 | return &SUDPProxy{ 38 | BaseProxy: baseProxy, 39 | cfg: unwrapped, 40 | } 41 | } 42 | 43 | func (pxy *SUDPProxy) Run() (remoteAddr string, err error) { 44 | xl := pxy.xl 45 | allowUsers := pxy.cfg.AllowUsers 46 | // if allowUsers is empty, only allow same user from proxy 47 | if len(allowUsers) == 0 { 48 | allowUsers = []string{pxy.GetUserInfo().User} 49 | } 50 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers) 51 | if errRet != nil { 52 | err = errRet 53 | return 54 | } 55 | pxy.listeners = append(pxy.listeners, listener) 56 | xl.Infof("sudp proxy custom listen success") 57 | 58 | pxy.startCommonTCPListenersHandler() 59 | return 60 | } 61 | 62 | func (pxy *SUDPProxy) Close() { 63 | pxy.BaseProxy.Close() 64 | pxy.rc.VisitorManager.CloseListener(pxy.GetName()) 65 | } 66 | -------------------------------------------------------------------------------- /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 | // Generator is an interface to provision the serving certificate. 26 | type Generator 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 | -------------------------------------------------------------------------------- /test/e2e/v1/features/chaos.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | 9 | "fxp/test/e2e/framework" 10 | ) 11 | 12 | var _ = ginkgo.Describe("[Feature: Chaos]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | ginkgo.It("reconnect after frps restart", func() { 16 | serverPort := f.AllocPort() 17 | serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 18 | bindAddr = "0.0.0.0" 19 | bindPort = %d 20 | `, serverPort)) 21 | 22 | remotePort := f.AllocPort() 23 | clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(` 24 | serverPort = %d 25 | log.level = "trace" 26 | 27 | [[proxies]] 28 | name = "tcp" 29 | type = "tcp" 30 | localPort = %d 31 | remotePort = %d 32 | `, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort)) 33 | 34 | // 1. start frps and frpc, expect request success 35 | ps, _, err := f.RunFrps("-c", serverConfigPath) 36 | framework.ExpectNoError(err) 37 | 38 | pc, _, err := f.RunFrpc("-c", clientConfigPath) 39 | framework.ExpectNoError(err) 40 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 41 | 42 | // 2. stop frps, expect request failed 43 | _ = ps.Stop() 44 | time.Sleep(200 * time.Millisecond) 45 | framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure() 46 | 47 | // 3. restart frps, expect request success 48 | _, _, err = f.RunFrps("-c", serverConfigPath) 49 | framework.ExpectNoError(err) 50 | time.Sleep(2 * time.Second) 51 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 52 | 53 | // 4. stop frpc, expect request failed 54 | _ = pc.Stop() 55 | time.Sleep(200 * time.Millisecond) 56 | framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure() 57 | 58 | // 5. restart frpc, expect request success 59 | _, _, err = f.RunFrpc("-c", clientConfigPath) 60 | framework.ExpectNoError(err) 61 | time.Sleep(time.Second) 62 | framework.NewRequestExpect(f).Port(remotePort).Ensure() 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /test/e2e/legacy/features/chaos.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | 9 | "fxp/test/e2e/framework" 10 | ) 11 | 12 | var _ = ginkgo.Describe("[Feature: Chaos]", func() { 13 | f := framework.NewDefaultFramework() 14 | 15 | ginkgo.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 | -------------------------------------------------------------------------------- /pkg/plugin/client/unix_domain_socket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "io" 21 | "net" 22 | 23 | libio "github.com/fatedier/golib/io" 24 | 25 | v1 "fxp/pkg/config/v1" 26 | ) 27 | 28 | func init() { 29 | Register(v1.PluginUnixDomainSocket, NewUnixDomainSocketPlugin) 30 | } 31 | 32 | type UnixDomainSocketPlugin struct { 33 | UnixAddr *net.UnixAddr 34 | } 35 | 36 | func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) { 37 | opts := options.(*v1.UnixDomainSocketPluginOptions) 38 | 39 | unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath) 40 | if errRet != nil { 41 | err = errRet 42 | return 43 | } 44 | 45 | p = &UnixDomainSocketPlugin{ 46 | UnixAddr: unixAddr, 47 | } 48 | return 49 | } 50 | 51 | func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) { 52 | localConn, err := net.DialUnix("unix", nil, uds.UnixAddr) 53 | if err != nil { 54 | return 55 | } 56 | if extra.ProxyProtocolHeader != nil { 57 | if _, err := extra.ProxyProtocolHeader.WriteTo(localConn); err != nil { 58 | return 59 | } 60 | } 61 | 62 | libio.Join(localConn, conn) 63 | } 64 | 65 | func (uds *UnixDomainSocketPlugin) Name() string { 66 | return v1.PluginUnixDomainSocket 67 | } 68 | 69 | func (uds *UnixDomainSocketPlugin) Close() error { 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/plugin/server/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 | "fxp/pkg/msg" 19 | ) 20 | 21 | type Request struct { 22 | Version string `json:"version"` 23 | Op string `json:"op"` 24 | Content interface{} `json:"content"` 25 | } 26 | 27 | type Response struct { 28 | Reject bool `json:"reject"` 29 | RejectReason string `json:"reject_reason"` 30 | Unchange bool `json:"unchange"` 31 | Content interface{} `json:"content"` 32 | } 33 | 34 | type LoginContent struct { 35 | msg.Login 36 | 37 | ClientAddress string `json:"client_address,omitempty"` 38 | } 39 | 40 | type UserInfo struct { 41 | User string `json:"user"` 42 | Metas map[string]string `json:"metas"` 43 | RunID string `json:"run_id"` 44 | } 45 | 46 | type NewProxyContent struct { 47 | User UserInfo `json:"user"` 48 | msg.NewProxy 49 | } 50 | 51 | type CloseProxyContent struct { 52 | User UserInfo `json:"user"` 53 | msg.CloseProxy 54 | } 55 | 56 | type PingContent struct { 57 | User UserInfo `json:"user"` 58 | msg.Ping 59 | } 60 | 61 | type NewWorkConnContent struct { 62 | User UserInfo `json:"user"` 63 | msg.NewWorkConn 64 | } 65 | 66 | type NewUserConnContent struct { 67 | User UserInfo `json:"user"` 68 | ProxyName string `json:"proxy_name"` 69 | ProxyType string `json:"proxy_type"` 70 | RemoteAddr string `json:"remote_addr"` 71 | } 72 | -------------------------------------------------------------------------------- /pkg/util/net/listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "sync" 21 | 22 | "github.com/fatedier/golib/errors" 23 | ) 24 | 25 | // InternalListener is a listener that can be used to accept connections from 26 | // other goroutines. 27 | type InternalListener struct { 28 | acceptCh chan net.Conn 29 | closed bool 30 | mu sync.Mutex 31 | } 32 | 33 | func NewInternalListener() *InternalListener { 34 | return &InternalListener{ 35 | acceptCh: make(chan net.Conn, 128), 36 | } 37 | } 38 | 39 | func (l *InternalListener) Accept() (net.Conn, error) { 40 | conn, ok := <-l.acceptCh 41 | if !ok { 42 | return nil, fmt.Errorf("listener closed") 43 | } 44 | return conn, nil 45 | } 46 | 47 | func (l *InternalListener) PutConn(conn net.Conn) error { 48 | err := errors.PanicToError(func() { 49 | select { 50 | case l.acceptCh <- conn: 51 | default: 52 | conn.Close() 53 | } 54 | }) 55 | if err != nil { 56 | return fmt.Errorf("put conn error: listener is closed") 57 | } 58 | return nil 59 | } 60 | 61 | func (l *InternalListener) Close() error { 62 | l.mu.Lock() 63 | defer l.mu.Unlock() 64 | if !l.closed { 65 | close(l.acceptCh) 66 | l.closed = true 67 | } 68 | return nil 69 | } 70 | 71 | func (l *InternalListener) Addr() net.Addr { 72 | return &InternalAddr{} 73 | } 74 | 75 | type InternalAddr struct{} 76 | 77 | func (ia *InternalAddr) Network() string { 78 | return "internal" 79 | } 80 | 81 | func (ia *InternalAddr) String() string { 82 | return "internal" 83 | } 84 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "fmt" 19 | "slices" 20 | 21 | "github.com/samber/lo" 22 | 23 | v1 "fxp/pkg/config/v1" 24 | ) 25 | 26 | func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) { 27 | var ( 28 | warnings Warning 29 | errs error 30 | ) 31 | if !slices.Contains(SupportedAuthMethods, c.Auth.Method) { 32 | errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods)) 33 | } 34 | if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) { 35 | errs = AppendError(errs, fmt.Errorf("invalid auth additional scopes, optional values are %v", SupportedAuthAdditionalScopes)) 36 | } 37 | 38 | if err := validateLogConfig(&c.Log); err != nil { 39 | errs = AppendError(errs, err) 40 | } 41 | 42 | if err := validateWebServerConfig(&c.WebServer); err != nil { 43 | errs = AppendError(errs, err) 44 | } 45 | 46 | errs = AppendError(errs, ValidatePort(c.BindPort, "bindPort")) 47 | errs = AppendError(errs, ValidatePort(c.KCPBindPort, "kcpBindPort")) 48 | errs = AppendError(errs, ValidatePort(c.QUICBindPort, "quicBindPort")) 49 | errs = AppendError(errs, ValidatePort(c.VhostHTTPPort, "vhostHTTPPort")) 50 | errs = AppendError(errs, ValidatePort(c.VhostHTTPSPort, "vhostHTTPSPort")) 51 | errs = AppendError(errs, ValidatePort(c.TCPMuxHTTPConnectPort, "tcpMuxHTTPConnectPort")) 52 | 53 | for _, p := range c.HTTPPlugins { 54 | if !lo.Every(SupportedHTTPPluginOps, p.Ops) { 55 | errs = AppendError(errs, fmt.Errorf("invalid http plugin ops, optional values are %v", SupportedHTTPPluginOps)) 56 | } 57 | } 58 | return warnings, errs 59 | } 60 | -------------------------------------------------------------------------------- /pkg/config/v1/validation/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "errors" 19 | 20 | v1 "fxp/pkg/config/v1" 21 | ) 22 | 23 | func ValidateClientPluginOptions(c v1.ClientPluginOptions) error { 24 | switch v := c.(type) { 25 | case *v1.HTTP2HTTPSPluginOptions: 26 | return validateHTTP2HTTPSPluginOptions(v) 27 | case *v1.HTTPS2HTTPPluginOptions: 28 | return validateHTTPS2HTTPPluginOptions(v) 29 | case *v1.HTTPS2HTTPSPluginOptions: 30 | return validateHTTPS2HTTPSPluginOptions(v) 31 | case *v1.StaticFilePluginOptions: 32 | return validateStaticFilePluginOptions(v) 33 | case *v1.UnixDomainSocketPluginOptions: 34 | return validateUnixDomainSocketPluginOptions(v) 35 | } 36 | return nil 37 | } 38 | 39 | func validateHTTP2HTTPSPluginOptions(c *v1.HTTP2HTTPSPluginOptions) error { 40 | if c.LocalAddr == "" { 41 | return errors.New("localAddr is required") 42 | } 43 | return nil 44 | } 45 | 46 | func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error { 47 | if c.LocalAddr == "" { 48 | return errors.New("localAddr is required") 49 | } 50 | return nil 51 | } 52 | 53 | func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) error { 54 | if c.LocalAddr == "" { 55 | return errors.New("localAddr is required") 56 | } 57 | return nil 58 | } 59 | 60 | func validateStaticFilePluginOptions(c *v1.StaticFilePluginOptions) error { 61 | if c.LocalPath == "" { 62 | return errors.New("localPath is required") 63 | } 64 | return nil 65 | } 66 | 67 | func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions) error { 68 | if c.UnixPath == "" { 69 | return errors.New("unixPath is required") 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/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 | "fxp/pkg/util/metric" 21 | ) 22 | 23 | const ( 24 | ReserveDays = 7 25 | ) 26 | 27 | type ServerStats struct { 28 | TotalTrafficIn int64 29 | TotalTrafficOut int64 30 | CurConns int64 31 | ClientCounts int64 32 | ProxyTypeCounts map[string]int64 33 | } 34 | 35 | type ProxyStats struct { 36 | Name string 37 | Type string 38 | TodayTrafficIn int64 39 | TodayTrafficOut int64 40 | LastStartTime string 41 | LastCloseTime string 42 | CurConns int64 43 | } 44 | 45 | type ProxyTrafficInfo struct { 46 | Name string 47 | TrafficIn []int64 48 | TrafficOut []int64 49 | } 50 | 51 | type ProxyStatistics struct { 52 | Name string 53 | ProxyType string 54 | TrafficIn metric.DateCounter 55 | TrafficOut metric.DateCounter 56 | CurConns metric.Counter 57 | LastStartTime time.Time 58 | LastCloseTime time.Time 59 | } 60 | 61 | type ServerStatistics struct { 62 | TotalTrafficIn metric.DateCounter 63 | TotalTrafficOut metric.DateCounter 64 | CurConns metric.Counter 65 | 66 | // counter for clients 67 | ClientCounts metric.Counter 68 | 69 | // counter for proxy types 70 | ProxyTypeCounts map[string]metric.Counter 71 | 72 | // statistics for different proxies 73 | // key is proxy name 74 | ProxyStatistics map[string]*ProxyStatistics 75 | } 76 | 77 | type Collector interface { 78 | GetServer() *ServerStats 79 | GetProxiesByType(proxyType string) []*ProxyStats 80 | GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats 81 | GetProxyTraffic(name string) *ProxyTrafficInfo 82 | ClearOfflineProxies() (int, int) 83 | } 84 | -------------------------------------------------------------------------------- /package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # compile for version 5 | make 6 | if [ $? -ne 0 ]; then 7 | echo "make error" 8 | exit 1 9 | fi 10 | 11 | frp_version=`./bin/frps --version` 12 | echo "build version: $frp_version" 13 | 14 | # cross_compiles 15 | make -f ./Makefile.cross-compiles 16 | 17 | rm -rf ./release/packages 18 | mkdir -p ./release/packages 19 | 20 | os_all='linux windows darwin freebsd android' 21 | arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64' 22 | extra_all='_ hf' 23 | 24 | cd ./release 25 | 26 | for os in $os_all; do 27 | for arch in $arch_all; do 28 | for extra in $extra_all; do 29 | suffix="${os}_${arch}" 30 | if [ "x${extra}" != x"_" ]; then 31 | suffix="${os}_${arch}_${extra}" 32 | fi 33 | frp_dir_name="frp_${frp_version}_${suffix}" 34 | frp_path="./packages/frp_${frp_version}_${suffix}" 35 | 36 | if [ "x${os}" = x"windows" ]; then 37 | if [ ! -f "./frpc_${os}_${arch}.exe" ]; then 38 | continue 39 | fi 40 | if [ ! -f "./frps_${os}_${arch}.exe" ]; then 41 | continue 42 | fi 43 | mkdir ${frp_path} 44 | mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe 45 | mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe 46 | else 47 | if [ ! -f "./frpc_${suffix}" ]; then 48 | continue 49 | fi 50 | if [ ! -f "./frps_${suffix}" ]; then 51 | continue 52 | fi 53 | mkdir ${frp_path} 54 | mv ./frpc_${suffix} ${frp_path}/frpc 55 | mv ./frps_${suffix} ${frp_path}/frps 56 | fi 57 | cp ../LICENSE ${frp_path} 58 | cp -f ../conf/frpc.toml ${frp_path} 59 | cp -f ../conf/frps.toml ${frp_path} 60 | 61 | # packages 62 | cd ./packages 63 | if [ "x${os}" = x"windows" ]; then 64 | zip -rq ${frp_dir_name}.zip ${frp_dir_name} 65 | else 66 | tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name} 67 | fi 68 | cd .. 69 | rm -rf ${frp_path} 70 | done 71 | done 72 | done 73 | 74 | cd - 75 | -------------------------------------------------------------------------------- /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 | "fxp/pkg/util/log" 24 | "fxp/pkg/util/version" 25 | ) 26 | 27 | var NotFoundPagePath = "" 28 | 29 | const ( 30 | NotFound = ` 31 | 32 | 33 | Not Found 34 | 41 | 42 | 43 |

The page you requested was not found.

44 |

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

46 |

The server is powered by frp.

47 |

Faithfully yours, frp.

48 | 49 | 50 | ` 51 | ) 52 | 53 | func getNotFoundPageContent() []byte { 54 | var ( 55 | buf []byte 56 | err error 57 | ) 58 | if NotFoundPagePath != "" { 59 | buf, err = os.ReadFile(NotFoundPagePath) 60 | if err != nil { 61 | log.Warnf("read custom 404 page error: %v", err) 62 | buf = []byte(NotFound) 63 | } 64 | } else { 65 | buf = []byte(NotFound) 66 | } 67 | return buf 68 | } 69 | 70 | func NotFoundResponse() *http.Response { 71 | header := make(http.Header) 72 | header.Set("server", "frp/"+version.Full()) 73 | header.Set("Content-Type", "text/html") 74 | 75 | content := getNotFoundPageContent() 76 | res := &http.Response{ 77 | Status: "Not Found", 78 | StatusCode: 404, 79 | Proto: "HTTP/1.1", 80 | ProtoMajor: 1, 81 | ProtoMinor: 1, 82 | Header: header, 83 | Body: io.NopCloser(bytes.NewReader(content)), 84 | ContentLength: int64(len(content)), 85 | } 86 | return res 87 | } 88 | -------------------------------------------------------------------------------- /web/frpc/src/components/Overview.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /test/e2e/legacy/basic/config.go: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/onsi/ginkgo/v2" 7 | 8 | "fxp/test/e2e/framework" 9 | "fxp/test/e2e/framework/consts" 10 | "fxp/test/e2e/pkg/port" 11 | ) 12 | 13 | var _ = ginkgo.Describe("[Feature: Config]", func() { 14 | f := framework.NewDefaultFramework() 15 | 16 | ginkgo.Describe("Template", func() { 17 | ginkgo.It("render by env", func() { 18 | serverConf := consts.LegacyDefaultServerConfig 19 | clientConf := consts.LegacyDefaultClientConfig 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 | ginkgo.Describe("Includes", func() { 43 | ginkgo.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 | -------------------------------------------------------------------------------- /pkg/plugin/client/static_file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "io" 21 | "net" 22 | "net/http" 23 | "time" 24 | 25 | "github.com/gorilla/mux" 26 | 27 | v1 "fxp/pkg/config/v1" 28 | netpkg "fxp/pkg/util/net" 29 | ) 30 | 31 | func init() { 32 | Register(v1.PluginStaticFile, NewStaticFilePlugin) 33 | } 34 | 35 | type StaticFilePlugin struct { 36 | opts *v1.StaticFilePluginOptions 37 | 38 | l *Listener 39 | s *http.Server 40 | } 41 | 42 | func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) { 43 | opts := options.(*v1.StaticFilePluginOptions) 44 | 45 | listener := NewProxyListener() 46 | 47 | sp := &StaticFilePlugin{ 48 | opts: opts, 49 | 50 | l: listener, 51 | } 52 | var prefix string 53 | if opts.StripPrefix != "" { 54 | prefix = "/" + opts.StripPrefix + "/" 55 | } else { 56 | prefix = "/" 57 | } 58 | 59 | router := mux.NewRouter() 60 | router.Use(netpkg.NewHTTPAuthMiddleware(opts.HTTPUser, opts.HTTPPassword).SetAuthFailDelay(200 * time.Millisecond).Middleware) 61 | router.PathPrefix(prefix).Handler(netpkg.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(opts.LocalPath))))).Methods("GET") 62 | sp.s = &http.Server{ 63 | Handler: router, 64 | ReadHeaderTimeout: 60 * time.Second, 65 | } 66 | go func() { 67 | _ = sp.s.Serve(listener) 68 | }() 69 | return sp, nil 70 | } 71 | 72 | func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 73 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 74 | _ = sp.l.PutConn(wrapConn) 75 | } 76 | 77 | func (sp *StaticFilePlugin) Name() string { 78 | return v1.PluginStaticFile 79 | } 80 | 81 | func (sp *StaticFilePlugin) Close() error { 82 | sp.s.Close() 83 | sp.l.Close() 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/plugin/client/http2http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "io" 19 | stdlog "log" 20 | "net" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/fatedier/golib/pool" 25 | 26 | v1 "fxp/pkg/config/v1" 27 | "fxp/pkg/util/log" 28 | netpkg "fxp/pkg/util/net" 29 | ) 30 | 31 | func init() { 32 | Register(v1.PluginHTTP2HTTP, NewHTTP2HTTPPlugin) 33 | } 34 | 35 | type HTTP2HTTPPlugin struct { 36 | opts *v1.HTTP2HTTPPluginOptions 37 | 38 | l *Listener 39 | s *http.Server 40 | } 41 | 42 | func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) { 43 | opts := options.(*v1.HTTP2HTTPPluginOptions) 44 | 45 | listener := NewProxyListener() 46 | 47 | p := &HTTP2HTTPPlugin{ 48 | opts: opts, 49 | l: listener, 50 | } 51 | 52 | rp := &httputil.ReverseProxy{ 53 | Rewrite: func(r *httputil.ProxyRequest) { 54 | req := r.Out 55 | req.URL.Scheme = "http" 56 | req.URL.Host = p.opts.LocalAddr 57 | if p.opts.HostHeaderRewrite != "" { 58 | req.Host = p.opts.HostHeaderRewrite 59 | } 60 | for k, v := range p.opts.RequestHeaders.Set { 61 | req.Header.Set(k, v) 62 | } 63 | }, 64 | BufferPool: pool.NewBuffer(32 * 1024), 65 | ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), 66 | } 67 | 68 | p.s = &http.Server{ 69 | Handler: rp, 70 | ReadHeaderTimeout: 0, 71 | } 72 | 73 | go func() { 74 | _ = p.s.Serve(listener) 75 | }() 76 | 77 | return p, nil 78 | } 79 | 80 | func (p *HTTP2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 81 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 82 | _ = p.l.PutConn(wrapConn) 83 | } 84 | 85 | func (p *HTTP2HTTPPlugin) Name() string { 86 | return v1.PluginHTTP2HTTP 87 | } 88 | 89 | func (p *HTTP2HTTPPlugin) Close() error { 90 | return p.s.Close() 91 | } 92 | -------------------------------------------------------------------------------- /web/frps/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | ElButton: typeof import('element-plus/es')['ElButton'] 11 | ElCol: typeof import('element-plus/es')['ElCol'] 12 | ElDialog: typeof import('element-plus/es')['ElDialog'] 13 | ElDivider: typeof import('element-plus/es')['ElDivider'] 14 | ElForm: typeof import('element-plus/es')['ElForm'] 15 | ElFormItem: typeof import('element-plus/es')['ElFormItem'] 16 | ElMenu: typeof import('element-plus/es')['ElMenu'] 17 | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] 18 | ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] 19 | ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'] 20 | ElRow: typeof import('element-plus/es')['ElRow'] 21 | ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] 22 | ElSwitch: typeof import('element-plus/es')['ElSwitch'] 23 | ElTable: typeof import('element-plus/es')['ElTable'] 24 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] 25 | ElTag: typeof import('element-plus/es')['ElTag'] 26 | ElText: typeof import('element-plus/es')['ElText'] 27 | ElTooltip: typeof import('element-plus/es')['ElTooltip'] 28 | LongSpan: typeof import('./src/components/LongSpan.vue')['default'] 29 | ProxiesHTTP: typeof import('./src/components/ProxiesHTTP.vue')['default'] 30 | ProxiesHTTPS: typeof import('./src/components/ProxiesHTTPS.vue')['default'] 31 | ProxiesSTCP: typeof import('./src/components/ProxiesSTCP.vue')['default'] 32 | ProxiesSUDP: typeof import('./src/components/ProxiesSUDP.vue')['default'] 33 | ProxiesTCP: typeof import('./src/components/ProxiesTCP.vue')['default'] 34 | ProxiesTCPMux: typeof import('./src/components/ProxiesTCPMux.vue')['default'] 35 | ProxiesUDP: typeof import('./src/components/ProxiesUDP.vue')['default'] 36 | ProxyView: typeof import('./src/components/ProxyView.vue')['default'] 37 | ProxyViewExpand: typeof import('./src/components/ProxyViewExpand.vue')['default'] 38 | RouterLink: typeof import('vue-router')['RouterLink'] 39 | RouterView: typeof import('vue-router')['RouterView'] 40 | ServerOverview: typeof import('./src/components/ServerOverview.vue')['default'] 41 | Traffic: typeof import('./src/components/Traffic.vue')['default'] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/e2e/mock/server/httpserver/server.go: -------------------------------------------------------------------------------- 1 | package httpserver 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | "strconv" 8 | "time" 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 | ReadHeaderTimeout: time.Minute, 82 | } 83 | 84 | s.hs = hs 85 | if s.tlsConfig == nil { 86 | go func() { 87 | _ = hs.Serve(s.l) 88 | }() 89 | } else { 90 | go func() { 91 | _ = hs.ServeTLS(s.l, "", "") 92 | }() 93 | } 94 | return nil 95 | } 96 | 97 | func (s *Server) Close() error { 98 | if s.hs != nil { 99 | return s.hs.Close() 100 | } 101 | return nil 102 | } 103 | 104 | func (s *Server) initListener() (err error) { 105 | s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))) 106 | return 107 | } 108 | 109 | func (s *Server) BindAddr() string { 110 | return s.bindAddr 111 | } 112 | 113 | func (s *Server) BindPort() int { 114 | return s.bindPort 115 | } 116 | -------------------------------------------------------------------------------- /server/proxy/xtcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | 21 | "github.com/fatedier/golib/errors" 22 | 23 | v1 "fxp/pkg/config/v1" 24 | "fxp/pkg/msg" 25 | ) 26 | 27 | func init() { 28 | RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy) 29 | } 30 | 31 | type XTCPProxy struct { 32 | *BaseProxy 33 | cfg *v1.XTCPProxyConfig 34 | 35 | closeCh chan struct{} 36 | } 37 | 38 | func NewXTCPProxy(baseProxy *BaseProxy) Proxy { 39 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.XTCPProxyConfig) 40 | if !ok { 41 | return nil 42 | } 43 | return &XTCPProxy{ 44 | BaseProxy: baseProxy, 45 | cfg: unwrapped, 46 | } 47 | } 48 | 49 | func (pxy *XTCPProxy) Run() (remoteAddr string, err error) { 50 | xl := pxy.xl 51 | 52 | if pxy.rc.NatHoleController == nil { 53 | err = fmt.Errorf("xtcp is not supported in frps") 54 | return 55 | } 56 | allowUsers := pxy.cfg.AllowUsers 57 | // if allowUsers is empty, only allow same user from proxy 58 | if len(allowUsers) == 0 { 59 | allowUsers = []string{pxy.GetUserInfo().User} 60 | } 61 | sidCh, err := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Secretkey, allowUsers) 62 | if err != nil { 63 | return "", err 64 | } 65 | go func() { 66 | for { 67 | select { 68 | case <-pxy.closeCh: 69 | return 70 | case sid := <-sidCh: 71 | workConn, errRet := pxy.GetWorkConnFromPool(nil, nil) 72 | if errRet != nil { 73 | continue 74 | } 75 | m := &msg.NatHoleSid{ 76 | Sid: sid, 77 | } 78 | errRet = msg.WriteMsg(workConn, m) 79 | if errRet != nil { 80 | xl.Warnf("write nat hole sid package error, %v", errRet) 81 | } 82 | workConn.Close() 83 | } 84 | } 85 | }() 86 | return 87 | } 88 | 89 | func (pxy *XTCPProxy) Close() { 90 | pxy.BaseProxy.Close() 91 | pxy.rc.NatHoleController.CloseClient(pxy.GetName()) 92 | _ = errors.PanicToError(func() { 93 | close(pxy.closeCh) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /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 | "fxp/pkg/metrics/mem" 19 | "fxp/pkg/metrics/prometheus" 20 | "fxp/server/metrics" 21 | ) 22 | 23 | // EnableMem start to mark metrics to memory monitor system. 24 | func EnableMem() { 25 | sm.Add(mem.ServerMetrics) 26 | } 27 | 28 | // EnablePrometheus start to mark metrics to prometheus. 29 | func EnablePrometheus() { 30 | sm.Add(prometheus.ServerMetrics) 31 | } 32 | 33 | var sm = &serverMetrics{} 34 | 35 | func init() { 36 | metrics.Register(sm) 37 | } 38 | 39 | type serverMetrics struct { 40 | ms []metrics.ServerMetrics 41 | } 42 | 43 | func (m *serverMetrics) Add(sm metrics.ServerMetrics) { 44 | m.ms = append(m.ms, sm) 45 | } 46 | 47 | func (m *serverMetrics) NewClient() { 48 | for _, v := range m.ms { 49 | v.NewClient() 50 | } 51 | } 52 | 53 | func (m *serverMetrics) CloseClient() { 54 | for _, v := range m.ms { 55 | v.CloseClient() 56 | } 57 | } 58 | 59 | func (m *serverMetrics) NewProxy(name string, proxyType string) { 60 | for _, v := range m.ms { 61 | v.NewProxy(name, proxyType) 62 | } 63 | } 64 | 65 | func (m *serverMetrics) CloseProxy(name string, proxyType string) { 66 | for _, v := range m.ms { 67 | v.CloseProxy(name, proxyType) 68 | } 69 | } 70 | 71 | func (m *serverMetrics) OpenConnection(name string, proxyType string) { 72 | for _, v := range m.ms { 73 | v.OpenConnection(name, proxyType) 74 | } 75 | } 76 | 77 | func (m *serverMetrics) CloseConnection(name string, proxyType string) { 78 | for _, v := range m.ms { 79 | v.CloseConnection(name, proxyType) 80 | } 81 | } 82 | 83 | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) { 84 | for _, v := range m.ms { 85 | v.AddTrafficIn(name, proxyType, trafficBytes) 86 | } 87 | } 88 | 89 | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) { 90 | for _, v := range m.ms { 91 | v.AddTrafficOut(name, proxyType, trafficBytes) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pkg/util/system/system_android.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package system 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "os/exec" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | func EnableCompatibilityMode() { 26 | fixTimezone() 27 | fixDNSResolver() 28 | } 29 | 30 | // fixTimezone is used to try our best to fix timezone issue on some Android devices. 31 | func fixTimezone() { 32 | out, err := exec.Command("/system/bin/getprop", "persist.sys.timezone").Output() 33 | if err != nil { 34 | return 35 | } 36 | loc, err := time.LoadLocation(strings.TrimSpace(string(out))) 37 | if err != nil { 38 | return 39 | } 40 | time.Local = loc 41 | } 42 | 43 | // fixDNSResolver will first attempt to resolve google.com to check if the current DNS is available. 44 | // If it is not available, it will default to using 8.8.8.8 as the DNS server. 45 | // This is a workaround for the issue that golang can't get the default DNS servers on Android. 46 | func fixDNSResolver() { 47 | // First, we attempt to resolve a domain. If resolution is successful, no modifications are necessary. 48 | // In real-world scenarios, users may have already configured /etc/resolv.conf, or compiled directly 49 | // in the Android environment instead of using cross-platform compilation, so this issue does not arise. 50 | if net.DefaultResolver != nil { 51 | timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second) 52 | defer cancel() 53 | _, err := net.DefaultResolver.LookupHost(timeoutCtx, "google.com") 54 | if err == nil { 55 | return 56 | } 57 | } 58 | // If the resolution fails, use 8.8.8.8 as the DNS server. 59 | // Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially. 60 | net.DefaultResolver = &net.Resolver{ 61 | PreferGo: true, 62 | Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { 63 | if addr == "127.0.0.1:53" || addr == "[::1]:53" { 64 | addr = "8.8.8.8:53" 65 | } 66 | var d net.Dialer 67 | return d.DialContext(ctx, network, addr) 68 | }, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/e2e/framework/mockservers.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | "fxp/test/e2e/framework/consts" 9 | "fxp/test/e2e/mock/server" 10 | "fxp/test/e2e/mock/server/httpserver" 11 | "fxp/test/e2e/mock/server/streamserver" 12 | "fxp/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), 37 | httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 38 | _, _ = w.Write([]byte(consts.TestString)) 39 | })), 40 | ) 41 | 42 | udsIndex := portAllocator.Get() 43 | udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex) 44 | os.Remove(udsAddr) 45 | s.udsEchoServer = streamserver.New(streamserver.Unix, streamserver.WithBindAddr(udsAddr)) 46 | return s 47 | } 48 | 49 | func (m *MockServers) Run() error { 50 | if err := m.tcpEchoServer.Run(); err != nil { 51 | return err 52 | } 53 | if err := m.udpEchoServer.Run(); err != nil { 54 | return err 55 | } 56 | if err := m.udsEchoServer.Run(); err != nil { 57 | return err 58 | } 59 | return m.httpSimpleServer.Run() 60 | } 61 | 62 | func (m *MockServers) Close() { 63 | m.tcpEchoServer.Close() 64 | m.udpEchoServer.Close() 65 | m.udsEchoServer.Close() 66 | m.httpSimpleServer.Close() 67 | os.Remove(m.udsEchoServer.BindAddr()) 68 | } 69 | 70 | func (m *MockServers) GetTemplateParams() map[string]interface{} { 71 | ret := make(map[string]interface{}) 72 | ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort() 73 | ret[UDPEchoServerPort] = m.udpEchoServer.BindPort() 74 | ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr() 75 | ret[HTTPSimpleServerPort] = m.httpSimpleServer.BindPort() 76 | return ret 77 | } 78 | 79 | func (m *MockServers) GetParam(key string) interface{} { 80 | params := m.GetTemplateParams() 81 | if v, ok := params[key]; ok { 82 | return v 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /server/proxy/https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxy 16 | 17 | import ( 18 | "reflect" 19 | "strings" 20 | 21 | v1 "fxp/pkg/config/v1" 22 | "fxp/pkg/util/util" 23 | "fxp/pkg/util/vhost" 24 | ) 25 | 26 | func init() { 27 | RegisterProxyFactory(reflect.TypeOf(&v1.HTTPSProxyConfig{}), NewHTTPSProxy) 28 | } 29 | 30 | type HTTPSProxy struct { 31 | *BaseProxy 32 | cfg *v1.HTTPSProxyConfig 33 | } 34 | 35 | func NewHTTPSProxy(baseProxy *BaseProxy) Proxy { 36 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.HTTPSProxyConfig) 37 | if !ok { 38 | return nil 39 | } 40 | return &HTTPSProxy{ 41 | BaseProxy: baseProxy, 42 | cfg: unwrapped, 43 | } 44 | } 45 | 46 | func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) { 47 | xl := pxy.xl 48 | routeConfig := &vhost.RouteConfig{} 49 | 50 | defer func() { 51 | if err != nil { 52 | pxy.Close() 53 | } 54 | }() 55 | addrs := make([]string, 0) 56 | for _, domain := range pxy.cfg.CustomDomains { 57 | if domain == "" { 58 | continue 59 | } 60 | 61 | routeConfig.Domain = domain 62 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig) 63 | if errRet != nil { 64 | err = errRet 65 | return 66 | } 67 | xl.Infof("https proxy listen for host [%s]", routeConfig.Domain) 68 | pxy.listeners = append(pxy.listeners, l) 69 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) 70 | } 71 | 72 | if pxy.cfg.SubDomain != "" { 73 | routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost 74 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig) 75 | if errRet != nil { 76 | err = errRet 77 | return 78 | } 79 | xl.Infof("https proxy listen for host [%s]", routeConfig.Domain) 80 | pxy.listeners = append(pxy.listeners, l) 81 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) 82 | } 83 | 84 | pxy.startCommonTCPListenersHandler() 85 | remoteAddr = strings.Join(addrs, ",") 86 | return 87 | } 88 | 89 | func (pxy *HTTPSProxy) Close() { 90 | pxy.BaseProxy.Close() 91 | } 92 | -------------------------------------------------------------------------------- /test/e2e/pkg/port/port.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "sync" 8 | 9 | "k8s.io/apimachinery/pkg/util/sets" 10 | ) 11 | 12 | type Allocator struct { 13 | reserved sets.Set[int] 14 | used sets.Set[int] 15 | mu sync.Mutex 16 | } 17 | 18 | // NewAllocator return a port allocator for testing. 19 | // Example: from: 10, to: 20, mod 4, index 1 20 | // Reserved ports: 13, 17 21 | func NewAllocator(from int, to int, mod int, index int) *Allocator { 22 | pa := &Allocator{ 23 | reserved: sets.New[int](), 24 | used: sets.New[int](), 25 | } 26 | 27 | for i := from; i <= to; i++ { 28 | if i%mod == index { 29 | pa.reserved.Insert(i) 30 | } 31 | } 32 | return pa 33 | } 34 | 35 | func (pa *Allocator) Get() int { 36 | return pa.GetByName("") 37 | } 38 | 39 | func (pa *Allocator) GetByName(portName string) int { 40 | var builder *nameBuilder 41 | if portName == "" { 42 | builder = &nameBuilder{} 43 | } else { 44 | var err error 45 | builder, err = unmarshalFromName(portName) 46 | if err != nil { 47 | fmt.Println(err, portName) 48 | return 0 49 | } 50 | } 51 | 52 | pa.mu.Lock() 53 | defer pa.mu.Unlock() 54 | 55 | for i := 0; i < 20; i++ { 56 | port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo) 57 | if port == 0 { 58 | return 0 59 | } 60 | 61 | l, err := net.Listen("tcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(port))) 62 | if err != nil { 63 | // Maybe not controlled by us, mark it used. 64 | pa.used.Insert(port) 65 | continue 66 | } 67 | l.Close() 68 | 69 | udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("0.0.0.0", strconv.Itoa(port))) 70 | if err != nil { 71 | continue 72 | } 73 | udpConn, err := net.ListenUDP("udp", udpAddr) 74 | if err != nil { 75 | // Maybe not controlled by us, mark it used. 76 | pa.used.Insert(port) 77 | continue 78 | } 79 | udpConn.Close() 80 | 81 | pa.used.Insert(port) 82 | pa.reserved.Delete(port) 83 | return port 84 | } 85 | return 0 86 | } 87 | 88 | func (pa *Allocator) getByRange(from, to int) int { 89 | if from <= 0 { 90 | port, _ := pa.reserved.PopAny() 91 | return port 92 | } 93 | 94 | // choose a random port between from - to 95 | ports := pa.reserved.UnsortedList() 96 | for _, port := range ports { 97 | if port >= from && port <= to { 98 | return port 99 | } 100 | } 101 | return 0 102 | } 103 | 104 | func (pa *Allocator) Release(port int) { 105 | if port <= 0 { 106 | return 107 | } 108 | 109 | pa.mu.Lock() 110 | defer pa.mu.Unlock() 111 | 112 | if pa.used.Has(port) { 113 | pa.used.Delete(port) 114 | pa.reserved.Insert(port) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /test/e2e/e2e.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/onsi/ginkgo/v2" 7 | "github.com/onsi/gomega" 8 | 9 | "fxp/pkg/util/log" 10 | "fxp/test/e2e/framework" 11 | ) 12 | 13 | var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { 14 | setupSuite() 15 | return nil 16 | }, func(data []byte) { 17 | // Run on all Ginkgo nodes 18 | setupSuitePerGinkgoNode() 19 | }) 20 | 21 | var _ = ginkgo.SynchronizedAfterSuite(func() { 22 | CleanupSuite() 23 | }, func() { 24 | AfterSuiteActions() 25 | }) 26 | 27 | // RunE2ETests checks configuration parameters (specified through flags) and then runs 28 | // E2E tests using the Ginkgo runner. 29 | // If a "report directory" is specified, one or more JUnit test reports will be 30 | // generated in this directory, and cluster logs will also be saved. 31 | // This function is called on each Ginkgo node in parallel mode. 32 | func RunE2ETests(t *testing.T) { 33 | gomega.RegisterFailHandler(framework.Fail) 34 | 35 | suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() 36 | // Turn on EmitSpecProgress to get spec progress (especially on interrupt) 37 | suiteConfig.EmitSpecProgress = true 38 | // Randomize specs as well as suites 39 | suiteConfig.RandomizeAllSpecs = true 40 | 41 | log.Infof("Starting e2e run %q on Ginkgo node %d of total %d", 42 | framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal) 43 | ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig) 44 | } 45 | 46 | // setupSuite is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step. 47 | // There are certain operations we only want to run once per overall test invocation 48 | // (such as deleting old namespaces, or verifying that all system pods are running. 49 | // Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite 50 | // to ensure that these operations only run on the first parallel Ginkgo node. 51 | // 52 | // This function takes two parameters: one function which runs on only the first Ginkgo node, 53 | // returning an opaque byte array, and then a second function which runs on all Ginkgo nodes, 54 | // accepting the byte array. 55 | func setupSuite() { 56 | // Run only on Ginkgo node 1 57 | } 58 | 59 | // setupSuitePerGinkgoNode is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step. 60 | // There are certain operations we only want to run once per overall test invocation on each Ginkgo node 61 | // such as making some global variables accessible to all parallel executions 62 | // Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite 63 | // Ref: https://onsi.github.io/ginkgo/#parallel-specs 64 | func setupSuitePerGinkgoNode() { 65 | // config.GinkgoConfig.ParallelNode 66 | } 67 | -------------------------------------------------------------------------------- /pkg/util/net/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package net 16 | 17 | import ( 18 | "compress/gzip" 19 | "io" 20 | "net/http" 21 | "strings" 22 | "time" 23 | 24 | "fxp/pkg/util/util" 25 | ) 26 | 27 | type HTTPAuthMiddleware struct { 28 | user string 29 | passwd string 30 | authFailDelay time.Duration 31 | } 32 | 33 | func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware { 34 | return &HTTPAuthMiddleware{ 35 | user: user, 36 | passwd: passwd, 37 | } 38 | } 39 | 40 | func (authMid *HTTPAuthMiddleware) SetAuthFailDelay(delay time.Duration) *HTTPAuthMiddleware { 41 | authMid.authFailDelay = delay 42 | return authMid 43 | } 44 | 45 | func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler { 46 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 47 | reqUser, reqPasswd, hasAuth := r.BasicAuth() 48 | if (authMid.user == "" && authMid.passwd == "") || 49 | (hasAuth && util.ConstantTimeEqString(reqUser, authMid.user) && 50 | util.ConstantTimeEqString(reqPasswd, authMid.passwd)) { 51 | next.ServeHTTP(w, r) 52 | } else { 53 | if authMid.authFailDelay > 0 { 54 | time.Sleep(authMid.authFailDelay) 55 | } 56 | w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) 57 | http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 58 | } 59 | }) 60 | } 61 | 62 | type HTTPGzipWrapper struct { 63 | h http.Handler 64 | } 65 | 66 | func (gw *HTTPGzipWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { 67 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 68 | gw.h.ServeHTTP(w, r) 69 | return 70 | } 71 | w.Header().Set("Content-Encoding", "gzip") 72 | gz := gzip.NewWriter(w) 73 | defer gz.Close() 74 | gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w} 75 | gw.h.ServeHTTP(gzr, r) 76 | } 77 | 78 | func MakeHTTPGzipHandler(h http.Handler) http.Handler { 79 | return &HTTPGzipWrapper{ 80 | h: h, 81 | } 82 | } 83 | 84 | type gzipResponseWriter struct { 85 | io.Writer 86 | http.ResponseWriter 87 | } 88 | 89 | func (w gzipResponseWriter) Write(b []byte) (int, error) { 90 | return w.Writer.Write(b) 91 | } 92 | -------------------------------------------------------------------------------- /pkg/msg/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package msg 16 | 17 | import ( 18 | "io" 19 | "reflect" 20 | ) 21 | 22 | func AsyncHandler(f func(Message)) func(Message) { 23 | return func(m Message) { 24 | go f(m) 25 | } 26 | } 27 | 28 | // Dispatcher is used to send messages to net.Conn or register handlers for messages read from net.Conn. 29 | type Dispatcher struct { 30 | rw io.ReadWriter 31 | 32 | sendCh chan Message 33 | doneCh chan struct{} 34 | msgHandlers map[reflect.Type]func(Message) 35 | defaultHandler func(Message) 36 | } 37 | 38 | func NewDispatcher(rw io.ReadWriter) *Dispatcher { 39 | return &Dispatcher{ 40 | rw: rw, 41 | sendCh: make(chan Message, 100), 42 | doneCh: make(chan struct{}), 43 | msgHandlers: make(map[reflect.Type]func(Message)), 44 | } 45 | } 46 | 47 | // Run will block until io.EOF or some error occurs. 48 | func (d *Dispatcher) Run() { 49 | go d.sendLoop() 50 | go d.readLoop() 51 | } 52 | 53 | func (d *Dispatcher) sendLoop() { 54 | for { 55 | select { 56 | case <-d.doneCh: 57 | return 58 | case m := <-d.sendCh: 59 | _ = WriteMsg(d.rw, m) 60 | } 61 | } 62 | } 63 | 64 | func (d *Dispatcher) readLoop() { 65 | for { 66 | m, err := ReadMsg(d.rw) 67 | if err != nil { 68 | close(d.doneCh) 69 | return 70 | } 71 | 72 | if handler, ok := d.msgHandlers[reflect.TypeOf(m)]; ok { 73 | handler(m) 74 | } else if d.defaultHandler != nil { 75 | d.defaultHandler(m) 76 | } 77 | } 78 | } 79 | 80 | func (d *Dispatcher) Send(m Message) error { 81 | select { 82 | case <-d.doneCh: 83 | return io.EOF 84 | case d.sendCh <- m: 85 | return nil 86 | } 87 | } 88 | 89 | func (d *Dispatcher) SendChannel() chan Message { 90 | return d.sendCh 91 | } 92 | 93 | func (d *Dispatcher) RegisterHandler(msg Message, handler func(Message)) { 94 | d.msgHandlers[reflect.TypeOf(msg)] = handler 95 | } 96 | 97 | func (d *Dispatcher) RegisterDefaultHandler(handler func(Message)) { 98 | d.defaultHandler = handler 99 | } 100 | 101 | func (d *Dispatcher) Done() chan struct{} { 102 | return d.doneCh 103 | } 104 | -------------------------------------------------------------------------------- /pkg/plugin/client/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugin 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "net" 21 | "sync" 22 | 23 | "github.com/fatedier/golib/errors" 24 | pp "github.com/pires/go-proxyproto" 25 | 26 | v1 "fxp/pkg/config/v1" 27 | ) 28 | 29 | // Creators is used for create plugins to handle connections. 30 | var creators = make(map[string]CreatorFn) 31 | 32 | // params has prefix "plugin_" 33 | type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error) 34 | 35 | func Register(name string, fn CreatorFn) { 36 | if _, exist := creators[name]; exist { 37 | panic(fmt.Sprintf("plugin [%s] is already registered", name)) 38 | } 39 | creators[name] = fn 40 | } 41 | 42 | func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) { 43 | if fn, ok := creators[name]; ok { 44 | p, err = fn(options) 45 | } else { 46 | err = fmt.Errorf("plugin [%s] is not registered", name) 47 | } 48 | return 49 | } 50 | 51 | type ExtraInfo struct { 52 | ProxyProtocolHeader *pp.Header 53 | SrcAddr net.Addr 54 | DstAddr net.Addr 55 | } 56 | 57 | type Plugin interface { 58 | Name() string 59 | 60 | Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) 61 | Close() error 62 | } 63 | 64 | type Listener struct { 65 | conns chan net.Conn 66 | closed bool 67 | mu sync.Mutex 68 | } 69 | 70 | func NewProxyListener() *Listener { 71 | return &Listener{ 72 | conns: make(chan net.Conn, 64), 73 | } 74 | } 75 | 76 | func (l *Listener) Accept() (net.Conn, error) { 77 | conn, ok := <-l.conns 78 | if !ok { 79 | return nil, fmt.Errorf("listener closed") 80 | } 81 | return conn, nil 82 | } 83 | 84 | func (l *Listener) PutConn(conn net.Conn) error { 85 | err := errors.PanicToError(func() { 86 | l.conns <- conn 87 | }) 88 | return err 89 | } 90 | 91 | func (l *Listener) Close() error { 92 | l.mu.Lock() 93 | defer l.mu.Unlock() 94 | if !l.closed { 95 | close(l.conns) 96 | l.closed = true 97 | } 98 | return nil 99 | } 100 | 101 | func (l *Listener) Addr() net.Addr { 102 | return (*net.TCPAddr)(nil) 103 | } 104 | -------------------------------------------------------------------------------- /web/frpc/src/components/ClientConfigure.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 97 | 98 | 103 | -------------------------------------------------------------------------------- /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 | "reflect" 21 | "strconv" 22 | 23 | v1 "fxp/pkg/config/v1" 24 | ) 25 | 26 | func init() { 27 | RegisterProxyFactory(reflect.TypeOf(&v1.TCPProxyConfig{}), NewTCPProxy) 28 | } 29 | 30 | type TCPProxy struct { 31 | *BaseProxy 32 | cfg *v1.TCPProxyConfig 33 | 34 | realBindPort int 35 | } 36 | 37 | func NewTCPProxy(baseProxy *BaseProxy) Proxy { 38 | unwrapped, ok := baseProxy.GetConfigurer().(*v1.TCPProxyConfig) 39 | if !ok { 40 | return nil 41 | } 42 | baseProxy.usedPortsNum = 1 43 | return &TCPProxy{ 44 | BaseProxy: baseProxy, 45 | cfg: unwrapped, 46 | } 47 | } 48 | 49 | func (pxy *TCPProxy) Run() (remoteAddr string, err error) { 50 | xl := pxy.xl 51 | if pxy.cfg.LoadBalancer.Group != "" { 52 | l, realBindPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.LoadBalancer.Group, pxy.cfg.LoadBalancer.GroupKey, 53 | pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort) 54 | if errRet != nil { 55 | err = errRet 56 | return 57 | } 58 | defer func() { 59 | if err != nil { 60 | l.Close() 61 | } 62 | }() 63 | pxy.realBindPort = realBindPort 64 | pxy.listeners = append(pxy.listeners, l) 65 | xl.Infof("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.LoadBalancer.Group) 66 | } else { 67 | pxy.realBindPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) 68 | if err != nil { 69 | return 70 | } 71 | defer func() { 72 | if err != nil { 73 | pxy.rc.TCPPortManager.Release(pxy.realBindPort) 74 | } 75 | }() 76 | listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realBindPort))) 77 | if errRet != nil { 78 | err = errRet 79 | return 80 | } 81 | pxy.listeners = append(pxy.listeners, listener) 82 | xl.Infof("tcp proxy listen port [%d]", pxy.cfg.RemotePort) 83 | } 84 | 85 | pxy.cfg.RemotePort = pxy.realBindPort 86 | remoteAddr = fmt.Sprintf(":%d", pxy.realBindPort) 87 | pxy.startCommonTCPListenersHandler() 88 | return 89 | } 90 | 91 | func (pxy *TCPProxy) Close() { 92 | pxy.BaseProxy.Close() 93 | if pxy.cfg.LoadBalancer.Group == "" { 94 | pxy.rc.TCPPortManager.Release(pxy.realBindPort) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkg/virtual/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package virtual 16 | 17 | import ( 18 | "context" 19 | "net" 20 | 21 | "fxp/client" 22 | v1 "fxp/pkg/config/v1" 23 | "fxp/pkg/msg" 24 | netpkg "fxp/pkg/util/net" 25 | ) 26 | 27 | type ClientOptions struct { 28 | Common *v1.ClientCommonConfig 29 | Spec *msg.ClientSpec 30 | HandleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool 31 | } 32 | 33 | type Client struct { 34 | l *netpkg.InternalListener 35 | svr *client.Service 36 | } 37 | 38 | func NewClient(options ClientOptions) (*Client, error) { 39 | if options.Common != nil { 40 | options.Common.Complete() 41 | } 42 | 43 | ln := netpkg.NewInternalListener() 44 | 45 | serviceOptions := client.ServiceOptions{ 46 | Common: options.Common, 47 | ClientSpec: options.Spec, 48 | ConnectorCreator: func(context.Context, *v1.ClientCommonConfig) client.Connector { 49 | return &pipeConnector{ 50 | peerListener: ln, 51 | } 52 | }, 53 | HandleWorkConnCb: options.HandleWorkConnCb, 54 | } 55 | svr, err := client.NewService(serviceOptions) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return &Client{ 60 | l: ln, 61 | svr: svr, 62 | }, nil 63 | } 64 | 65 | func (c *Client) PeerListener() net.Listener { 66 | return c.l 67 | } 68 | 69 | func (c *Client) UpdateProxyConfigurer(proxyCfgs []v1.ProxyConfigurer) { 70 | _ = c.svr.UpdateAllConfigurer(proxyCfgs, nil) 71 | } 72 | 73 | func (c *Client) Run(ctx context.Context) error { 74 | return c.svr.Run(ctx) 75 | } 76 | 77 | func (c *Client) Service() *client.Service { 78 | return c.svr 79 | } 80 | 81 | func (c *Client) Close() { 82 | c.svr.Close() 83 | c.l.Close() 84 | } 85 | 86 | type pipeConnector struct { 87 | peerListener *netpkg.InternalListener 88 | } 89 | 90 | func (pc *pipeConnector) Open() error { 91 | return nil 92 | } 93 | 94 | func (pc *pipeConnector) Connect() (net.Conn, error) { 95 | c1, c2 := net.Pipe() 96 | if err := pc.peerListener.PutConn(c1); err != nil { 97 | c1.Close() 98 | c2.Close() 99 | return nil, err 100 | } 101 | return c2, nil 102 | } 103 | 104 | func (pc *pipeConnector) Close() error { 105 | pc.peerListener.Close() 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /web/frpc/src/App.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 64 | 65 | 117 | -------------------------------------------------------------------------------- /pkg/nathole/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The frp Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nathole 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "net" 21 | "strconv" 22 | 23 | "github.com/fatedier/golib/crypto" 24 | "github.com/pion/stun/v2" 25 | 26 | "fxp/pkg/msg" 27 | ) 28 | 29 | func EncodeMessage(m msg.Message, key []byte) ([]byte, error) { 30 | buffer := bytes.NewBuffer(nil) 31 | if err := msg.WriteMsg(buffer, m); err != nil { 32 | return nil, err 33 | } 34 | 35 | buf, err := crypto.Encode(buffer.Bytes(), key) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return buf, nil 40 | } 41 | 42 | func DecodeMessageInto(data, key []byte, m msg.Message) error { 43 | buf, err := crypto.Decode(data, key) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return msg.ReadMsgInto(bytes.NewReader(buf), m) 49 | } 50 | 51 | type ChangedAddress struct { 52 | IP net.IP 53 | Port int 54 | } 55 | 56 | func (s *ChangedAddress) GetFrom(m *stun.Message) error { 57 | a := (*stun.MappedAddress)(s) 58 | return a.GetFromAs(m, stun.AttrChangedAddress) 59 | } 60 | 61 | func (s *ChangedAddress) String() string { 62 | return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port)) 63 | } 64 | 65 | func ListAllLocalIPs() ([]net.IP, error) { 66 | addrs, err := net.InterfaceAddrs() 67 | if err != nil { 68 | return nil, err 69 | } 70 | ips := make([]net.IP, 0, len(addrs)) 71 | for _, addr := range addrs { 72 | ip, _, err := net.ParseCIDR(addr.String()) 73 | if err != nil { 74 | continue 75 | } 76 | ips = append(ips, ip) 77 | } 78 | return ips, nil 79 | } 80 | 81 | func ListLocalIPsForNatHole(max int) ([]string, error) { 82 | if max <= 0 { 83 | return nil, fmt.Errorf("max must be greater than 0") 84 | } 85 | 86 | ips, err := ListAllLocalIPs() 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | filtered := make([]string, 0, max) 92 | for _, ip := range ips { 93 | if len(filtered) >= max { 94 | break 95 | } 96 | 97 | // ignore ipv6 address 98 | if ip.To4() == nil { 99 | continue 100 | } 101 | // ignore localhost IP 102 | if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { 103 | continue 104 | } 105 | 106 | filtered = append(filtered, ip.String()) 107 | } 108 | return filtered, nil 109 | } 110 | -------------------------------------------------------------------------------- /test/e2e/mock/server/streamserver/server.go: -------------------------------------------------------------------------------- 1 | package streamserver 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | "strconv" 9 | 10 | libnet "fxp/pkg/util/net" 11 | "fxp/test/e2e/pkg/rpc" 12 | ) 13 | 14 | type Type string 15 | 16 | const ( 17 | TCP Type = "tcp" 18 | UDP Type = "udp" 19 | Unix Type = "unix" 20 | ) 21 | 22 | type Server struct { 23 | netType Type 24 | bindAddr string 25 | bindPort int 26 | respContent []byte 27 | 28 | handler func(net.Conn) 29 | 30 | l net.Listener 31 | } 32 | 33 | type Option func(*Server) *Server 34 | 35 | func New(netType Type, options ...Option) *Server { 36 | s := &Server{ 37 | netType: netType, 38 | bindAddr: "127.0.0.1", 39 | } 40 | s.handler = s.handle 41 | 42 | for _, option := range options { 43 | s = option(s) 44 | } 45 | return s 46 | } 47 | 48 | func WithBindAddr(addr string) Option { 49 | return func(s *Server) *Server { 50 | s.bindAddr = addr 51 | return s 52 | } 53 | } 54 | 55 | func WithBindPort(port int) Option { 56 | return func(s *Server) *Server { 57 | s.bindPort = port 58 | return s 59 | } 60 | } 61 | 62 | func WithRespContent(content []byte) Option { 63 | return func(s *Server) *Server { 64 | s.respContent = content 65 | return s 66 | } 67 | } 68 | 69 | func WithCustomHandler(handler func(net.Conn)) Option { 70 | return func(s *Server) *Server { 71 | s.handler = handler 72 | return s 73 | } 74 | } 75 | 76 | func (s *Server) Run() error { 77 | if err := s.initListener(); err != nil { 78 | return err 79 | } 80 | 81 | go func() { 82 | for { 83 | c, err := s.l.Accept() 84 | if err != nil { 85 | return 86 | } 87 | go s.handler(c) 88 | } 89 | }() 90 | return nil 91 | } 92 | 93 | func (s *Server) Close() error { 94 | if s.l != nil { 95 | return s.l.Close() 96 | } 97 | return nil 98 | } 99 | 100 | func (s *Server) initListener() (err error) { 101 | switch s.netType { 102 | case TCP: 103 | s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))) 104 | case UDP: 105 | s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort) 106 | case Unix: 107 | s.l, err = net.Listen("unix", s.bindAddr) 108 | default: 109 | return fmt.Errorf("unknown server type: %s", s.netType) 110 | } 111 | return err 112 | } 113 | 114 | func (s *Server) handle(c net.Conn) { 115 | defer c.Close() 116 | 117 | var reader io.Reader = c 118 | if s.netType == UDP { 119 | reader = bufio.NewReader(c) 120 | } 121 | for { 122 | buf, err := rpc.ReadBytes(reader) 123 | if err != nil { 124 | return 125 | } 126 | 127 | if len(s.respContent) > 0 { 128 | buf = s.respContent 129 | } 130 | _, _ = rpc.WriteBytes(c, buf) 131 | } 132 | } 133 | 134 | func (s *Server) BindAddr() string { 135 | return s.bindAddr 136 | } 137 | 138 | func (s *Server) BindPort() int { 139 | return s.bindPort 140 | } 141 | -------------------------------------------------------------------------------- /pkg/plugin/client/http2https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 fatedier, fatedier@gmail.com 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !frps 16 | 17 | package plugin 18 | 19 | import ( 20 | "crypto/tls" 21 | "io" 22 | stdlog "log" 23 | "net" 24 | "net/http" 25 | "net/http/httputil" 26 | 27 | "github.com/fatedier/golib/pool" 28 | 29 | v1 "fxp/pkg/config/v1" 30 | "fxp/pkg/util/log" 31 | netpkg "fxp/pkg/util/net" 32 | ) 33 | 34 | func init() { 35 | Register(v1.PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin) 36 | } 37 | 38 | type HTTP2HTTPSPlugin struct { 39 | opts *v1.HTTP2HTTPSPluginOptions 40 | 41 | l *Listener 42 | s *http.Server 43 | } 44 | 45 | func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) { 46 | opts := options.(*v1.HTTP2HTTPSPluginOptions) 47 | 48 | listener := NewProxyListener() 49 | 50 | p := &HTTP2HTTPSPlugin{ 51 | opts: opts, 52 | l: listener, 53 | } 54 | 55 | tr := &http.Transport{ 56 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 57 | } 58 | 59 | rp := &httputil.ReverseProxy{ 60 | Rewrite: func(r *httputil.ProxyRequest) { 61 | r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] 62 | r.Out.Header["X-Forwarded-Host"] = r.In.Header["X-Forwarded-Host"] 63 | r.Out.Header["X-Forwarded-Proto"] = r.In.Header["X-Forwarded-Proto"] 64 | req := r.Out 65 | req.URL.Scheme = "https" 66 | req.URL.Host = p.opts.LocalAddr 67 | if p.opts.HostHeaderRewrite != "" { 68 | req.Host = p.opts.HostHeaderRewrite 69 | } 70 | for k, v := range p.opts.RequestHeaders.Set { 71 | req.Header.Set(k, v) 72 | } 73 | }, 74 | Transport: tr, 75 | BufferPool: pool.NewBuffer(32 * 1024), 76 | ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), 77 | } 78 | 79 | p.s = &http.Server{ 80 | Handler: rp, 81 | ReadHeaderTimeout: 0, 82 | } 83 | 84 | go func() { 85 | _ = p.s.Serve(listener) 86 | }() 87 | 88 | return p, nil 89 | } 90 | 91 | func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { 92 | wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) 93 | _ = p.l.PutConn(wrapConn) 94 | } 95 | 96 | func (p *HTTP2HTTPSPlugin) Name() string { 97 | return v1.PluginHTTP2HTTPS 98 | } 99 | 100 | func (p *HTTP2HTTPSPlugin) Close() error { 101 | return p.s.Close() 102 | } 103 | --------------------------------------------------------------------------------