├── 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 |
2 |
3 | {{ content.slice(0, length) }}...
6 |
7 | {{ content }}
8 |
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 |
2 |
3 |
4 |
5 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/web/frps/src/components/ProxiesUDP.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/web/frps/src/components/ProxiesSUDP.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
12 |
17 |
23 |
29 |
35 |
40 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
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 |
2 |
3 |
4 | Refresh
5 | Upload
6 |
7 |
13 |
14 |
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 |
2 |
3 |
23 |
24 |
25 |
26 |
33 | Overview
34 | Configure
35 | Help
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
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 |
--------------------------------------------------------------------------------