├── conf
├── frps.ini
├── frpc.ini
└── systemd
│ ├── frps.service
│ ├── frps@.service
│ ├── frpc@.service
│ └── frpc.service
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.yaml
│ └── bug_report.yaml
├── FUNDING.yml
└── workflows
│ ├── goreleaser.yml
│ └── stale.yml
├── Release.md
├── doc
└── pic
│ ├── zsxq.jpg
│ ├── dashboard.png
│ ├── architecture.png
│ ├── donate-alipay.png
│ └── donate-wechatpay.png
├── assets
├── frpc
│ ├── static
│ │ ├── favicon.ico
│ │ ├── 535877f50039c0cb49a6196a5b7517cd.woff
│ │ ├── 732389ded34cb9c52dd88271f1345af9.ttf
│ │ ├── index.html
│ │ └── manifest.js
│ └── embed.go
├── frps
│ ├── static
│ │ ├── favicon.ico
│ │ ├── 535877f50039c0cb49a6196a5b7517cd.woff
│ │ ├── 732389ded34cb9c52dd88271f1345af9.ttf
│ │ ├── index.html
│ │ └── manifest.js
│ └── embed.go
└── assets.go
├── web
├── frpc
│ ├── Makefile
│ ├── src
│ │ ├── assets
│ │ │ └── favicon.ico
│ │ ├── index.html
│ │ ├── utils
│ │ │ ├── less
│ │ │ │ └── custom.less
│ │ │ └── status.js
│ │ ├── router
│ │ │ └── index.js
│ │ ├── main.js
│ │ ├── App.vue
│ │ └── components
│ │ │ └── Overview.vue
│ ├── .gitignore
│ ├── postcss.config.js
│ ├── .babelrc
│ └── package.json
└── frps
│ ├── src
│ ├── assets
│ │ └── favicon.ico
│ ├── index.html
│ ├── utils
│ │ └── less
│ │ │ └── custom.less
│ ├── main.js
│ ├── components
│ │ └── Traffic.vue
│ ├── router
│ │ └── index.js
│ └── App.vue
│ ├── .gitignore
│ ├── Makefile
│ ├── postcss.config.js
│ ├── .babelrc
│ └── package.json
├── test
└── e2e
│ ├── mock
│ └── server
│ │ ├── interface.go
│ │ ├── httpserver
│ │ └── server.go
│ │ └── streamserver
│ │ └── server.go
│ ├── framework
│ ├── client.go
│ ├── util.go
│ ├── consts
│ │ └── consts.go
│ ├── test_context.go
│ ├── cleanup.go
│ ├── ginkgowrapper
│ │ └── wrapper.go
│ ├── mockservers.go
│ └── process.go
│ ├── pkg
│ ├── utils
│ │ └── utils.go
│ ├── rpc
│ │ └── rpc.go
│ ├── process
│ │ └── process.go
│ ├── port
│ │ ├── util.go
│ │ └── port.go
│ └── cert
│ │ └── generator.go
│ ├── suites.go
│ ├── examples.go
│ ├── e2e_test.go
│ ├── plugin
│ └── utils.go
│ ├── features
│ ├── bandwidth_limit.go
│ ├── monitor.go
│ └── chaos.go
│ ├── basic
│ └── config.go
│ └── e2e.go
├── pkg
├── metrics
│ ├── metrics.go
│ ├── mem
│ │ └── types.go
│ └── aggregate
│ │ └── server.go
├── proto
│ └── udp
│ │ └── udp_test.go
├── util
│ ├── metric
│ │ ├── counter_test.go
│ │ ├── date_counter_test.go
│ │ ├── metrics.go
│ │ └── counter.go
│ ├── vhost
│ │ ├── https_test.go
│ │ ├── router.go
│ │ ├── resource.go
│ │ └── https.go
│ ├── util
│ │ ├── util_test.go
│ │ └── http.go
│ ├── xlog
│ │ ├── ctx.go
│ │ └── xlog.go
│ ├── limit
│ │ ├── reader.go
│ │ └── writer.go
│ ├── net
│ │ ├── listener.go
│ │ ├── tls.go
│ │ ├── websocket.go
│ │ └── kcp.go
│ ├── version
│ │ ├── version_test.go
│ │ └── version.go
│ ├── tcpmux
│ │ └── httpconnect.go
│ └── log
│ │ └── log.go
├── errors
│ └── errors.go
├── config
│ ├── README.md
│ ├── types_test.go
│ ├── utils.go
│ ├── value.go
│ ├── types.go
│ └── parse.go
├── plugin
│ ├── server
│ │ ├── tracer.go
│ │ ├── plugin.go
│ │ └── types.go
│ └── client
│ │ ├── socks5.go
│ │ ├── unix_domain_socket.go
│ │ ├── plugin.go
│ │ ├── static_file.go
│ │ └── http2https.go
├── msg
│ └── ctl.go
├── consts
│ └── consts.go
└── transport
│ └── tls.go
├── dockerfiles
├── Dockerfile-for-frpc
└── Dockerfile-for-frps
├── .circleci
└── config.yml
├── .gitignore
├── client
├── event
│ └── event.go
└── admin.go
├── .goreleaser.yml
├── hack
└── run-e2e.sh
├── cmd
├── frpc
│ ├── main.go
│ └── sub
│ │ ├── verify.go
│ │ ├── reload.go
│ │ ├── tcp.go
│ │ ├── udp.go
│ │ ├── https.go
│ │ └── tcpmux.go
└── frps
│ ├── main.go
│ └── verify.go
├── Makefile
├── server
├── group
│ └── group.go
├── proxy
│ ├── stcp.go
│ ├── sudp.go
│ ├── tcp.go
│ ├── https.go
│ ├── xtcp.go
│ └── tcpmux.go
├── metrics
│ └── metrics.go
├── controller
│ └── resource.go
├── dashboard.go
└── visitor
│ └── visitor.go
├── Makefile.cross-compiles
├── go.mod
├── package.sh
└── README_zh.md
/conf/frps.ini:
--------------------------------------------------------------------------------
1 | [common]
2 | bind_port = 7000
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/Release.md:
--------------------------------------------------------------------------------
1 | ### Improve
2 |
3 | * Remove authentication for healthz api.
4 |
--------------------------------------------------------------------------------
/doc/pic/zsxq.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/doc/pic/zsxq.jpg
--------------------------------------------------------------------------------
/doc/pic/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/doc/pic/dashboard.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [fatedier]
4 |
--------------------------------------------------------------------------------
/doc/pic/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/doc/pic/architecture.png
--------------------------------------------------------------------------------
/doc/pic/donate-alipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/doc/pic/donate-alipay.png
--------------------------------------------------------------------------------
/doc/pic/donate-wechatpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/doc/pic/donate-wechatpay.png
--------------------------------------------------------------------------------
/assets/frpc/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/assets/frpc/static/favicon.ico
--------------------------------------------------------------------------------
/assets/frps/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/assets/frps/static/favicon.ico
--------------------------------------------------------------------------------
/web/frpc/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: dist build
2 | build:
3 | @npm run build
4 |
5 | dev:
6 | @npm run dev
7 |
--------------------------------------------------------------------------------
/web/frpc/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/web/frpc/src/assets/favicon.ico
--------------------------------------------------------------------------------
/web/frps/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/web/frps/src/assets/favicon.ico
--------------------------------------------------------------------------------
/web/frpc/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | .idea
6 | .vscode/settings.json
7 |
--------------------------------------------------------------------------------
/web/frpc/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')()
4 | ]
5 | }
--------------------------------------------------------------------------------
/web/frps/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | .idea
6 | .vscode/settings.json
7 |
--------------------------------------------------------------------------------
/web/frps/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: dist build
2 |
3 | build:
4 | @npm run build
5 |
6 | dev: install
7 | @npm run dev
8 |
--------------------------------------------------------------------------------
/web/frps/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')()
4 | ]
5 | }
--------------------------------------------------------------------------------
/assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff
--------------------------------------------------------------------------------
/assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf
--------------------------------------------------------------------------------
/assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff
--------------------------------------------------------------------------------
/assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/product/frp/dev/assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf
--------------------------------------------------------------------------------
/test/e2e/mock/server/interface.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | type Server interface {
4 | Run() error
5 | Close() error
6 | BindAddr() string
7 | BindPort() int
8 | }
9 |
--------------------------------------------------------------------------------
/conf/frpc.ini:
--------------------------------------------------------------------------------
1 | [common]
2 | server_addr = 127.0.0.1
3 | server_port = 7000
4 |
5 | [ssh]
6 | type = tcp
7 | local_ip = 127.0.0.1
8 | local_port = 22
9 | remote_port = 6000
10 |
--------------------------------------------------------------------------------
/pkg/metrics/metrics.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "github.com/fatedier/frp/pkg/metrics/aggregate"
5 | )
6 |
7 | var EnableMem = aggregate.EnableMem
8 | var EnablePrometheus = aggregate.EnablePrometheus
9 |
--------------------------------------------------------------------------------
/test/e2e/framework/client.go:
--------------------------------------------------------------------------------
1 | package framework
2 |
3 | type FRPClient struct {
4 | port int
5 | }
6 |
7 | func (f *Framework) FRPClient(port int) *FRPClient {
8 | return &FRPClient{
9 | port: port,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/assets/frpc/embed.go:
--------------------------------------------------------------------------------
1 | package frpc
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/fatedier/frp/assets"
7 | )
8 |
9 | //go:embed static/*
10 | var content embed.FS
11 |
12 | func init() {
13 | assets.Register(content)
14 | }
15 |
--------------------------------------------------------------------------------
/assets/frps/embed.go:
--------------------------------------------------------------------------------
1 | package frpc
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/fatedier/frp/assets"
7 | )
8 |
9 | //go:embed static/*
10 | var content embed.FS
11 |
12 | func init() {
13 | assets.Register(content)
14 | }
15 |
--------------------------------------------------------------------------------
/test/e2e/pkg/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/base64"
5 | )
6 |
7 | func BasicAuth(username, passwd string) string {
8 | auth := username + ":" + passwd
9 | return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
10 | }
11 |
--------------------------------------------------------------------------------
/dockerfiles/Dockerfile-for-frpc:
--------------------------------------------------------------------------------
1 | FROM alpine:3.12.0 AS temp
2 |
3 | COPY bin/frpc /tmp
4 |
5 | RUN chmod -R 777 /tmp/frpc
6 |
7 |
8 | FROM alpine:3.12.0
9 |
10 | WORKDIR /app
11 |
12 | COPY --from=temp /tmp/frpc /usr/bin
13 |
14 | ENTRYPOINT ["/usr/bin/frpc"]
15 |
--------------------------------------------------------------------------------
/dockerfiles/Dockerfile-for-frps:
--------------------------------------------------------------------------------
1 | FROM alpine:3.12.0 AS temp
2 |
3 | COPY bin/frps /tmp
4 |
5 | RUN chmod -R 777 /tmp/frps
6 |
7 |
8 | FROM alpine:3.12.0
9 |
10 | WORKDIR /app
11 |
12 | COPY --from=temp /tmp/frps /usr/bin
13 |
14 | ENTRYPOINT ["/usr/bin/frps"]
15 |
--------------------------------------------------------------------------------
/conf/systemd/frps.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Frp Server Service
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User=nobody
8 | Restart=on-failure
9 | RestartSec=5s
10 | ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
11 | LimitNOFILE=1048576
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------
/conf/systemd/frps@.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Frp Server Service
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User=nobody
8 | Restart=on-failure
9 | RestartSec=5s
10 | ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
11 | LimitNOFILE=1048576
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------
/web/frpc/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", { "modules": false }]
4 | ],
5 | "plugins": [
6 | [
7 | "component",
8 | {
9 | "libraryName": "element-ui",
10 | "styleLibraryName": "theme-chalk"
11 | }
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/web/frps/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", { "modules": false }]
4 | ],
5 | "plugins": [
6 | [
7 | "component",
8 | {
9 | "libraryName": "element-ui",
10 | "styleLibraryName": "theme-chalk"
11 | }
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/assets/frps/static/index.html:
--------------------------------------------------------------------------------
1 |
frps dashboard
--------------------------------------------------------------------------------
/assets/frpc/static/index.html:
--------------------------------------------------------------------------------
1 | frp client admin UI
--------------------------------------------------------------------------------
/test/e2e/framework/util.go:
--------------------------------------------------------------------------------
1 | package framework
2 |
3 | import (
4 | "github.com/google/uuid"
5 | )
6 |
7 | // RunID is a unique identifier of the e2e run.
8 | // Beware that this ID is not the same for all tests in the e2e run, because each Ginkgo node creates it separately.
9 | var RunID string
10 |
11 | func init() {
12 | RunID = uuid.NewString()
13 | }
14 |
--------------------------------------------------------------------------------
/conf/systemd/frpc@.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Frp Client Service
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User=nobody
8 | Restart=on-failure
9 | RestartSec=5s
10 | ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
11 | ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
12 | LimitNOFILE=1048576
13 |
14 | [Install]
15 | WantedBy=multi-user.target
16 |
--------------------------------------------------------------------------------
/conf/systemd/frpc.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Frp Client Service
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User=nobody
8 | Restart=on-failure
9 | RestartSec=5s
10 | ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
11 | ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
12 | LimitNOFILE=1048576
13 |
14 | [Install]
15 | WantedBy=multi-user.target
16 |
--------------------------------------------------------------------------------
/pkg/proto/udp/udp_test.go:
--------------------------------------------------------------------------------
1 | package udp
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestUdpPacket(t *testing.T) {
10 | assert := assert.New(t)
11 |
12 | buf := []byte("hello world")
13 | udpMsg := NewUDPPacket(buf, nil, nil)
14 |
15 | newBuf, err := GetContent(udpMsg)
16 | assert.NoError(err)
17 | assert.EqualValues(buf, newBuf)
18 | }
19 |
--------------------------------------------------------------------------------
/web/frps/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | frps dashboard
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web/frpc/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | frp client admin UI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web/frpc/src/utils/less/custom.less:
--------------------------------------------------------------------------------
1 | @color: red;
2 |
3 | .el-form-item {
4 | span {
5 | margin-left: 15px;
6 | }
7 | }
8 |
9 | .demo-table-expand {
10 | font-size: 0;
11 |
12 | label {
13 | width: 90px;
14 | color: #99a9bf;
15 | }
16 |
17 | .el-form-item {
18 | margin-right: 0;
19 | margin-bottom: 0;
20 | width: 50%;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/web/frpc/src/utils/status.js:
--------------------------------------------------------------------------------
1 | class ProxyStatus {
2 | constructor(status) {
3 | this.name = status.name
4 | this.type = status.type
5 | this.status = status.status
6 | this.err = status.err
7 | this.local_addr = status.local_addr
8 | this.plugin = status.plugin
9 | this.remote_addr = status.remote_addr
10 | }
11 | }
12 |
13 | export {ProxyStatus}
14 |
--------------------------------------------------------------------------------
/web/frps/src/utils/less/custom.less:
--------------------------------------------------------------------------------
1 | @color: red;
2 |
3 | .el-form-item {
4 | span {
5 | margin-left: 15px;
6 | }
7 | }
8 |
9 | .demo-table-expand {
10 | font-size: 0;
11 |
12 | label {
13 | width: 90px;
14 | color: #99a9bf;
15 | }
16 |
17 | .el-form-item {
18 | margin-right: 0;
19 | margin-bottom: 0;
20 | width: 50%;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/util/metric/counter_test.go:
--------------------------------------------------------------------------------
1 | package metric
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestCounter(t *testing.T) {
10 | assert := assert.New(t)
11 | c := NewCounter()
12 | c.Inc(10)
13 | assert.EqualValues(10, c.Count())
14 |
15 | c.Dec(5)
16 | assert.EqualValues(5, c.Count())
17 |
18 | cTmp := c.Snapshot()
19 | assert.EqualValues(5, cTmp.Count())
20 |
21 | c.Clear()
22 | assert.EqualValues(0, c.Count())
23 | }
24 |
--------------------------------------------------------------------------------
/web/frpc/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Overview from '../components/Overview.vue'
4 | import Configure from '../components/Configure.vue'
5 |
6 | Vue.use(Router)
7 |
8 | export default new Router({
9 | routes: [{
10 | path: '/',
11 | name: 'Overview',
12 | component: Overview
13 | },{
14 | path: '/configure',
15 | name: 'Configure',
16 | component: Configure,
17 | }]
18 | })
19 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | go-version-latest:
4 | docker:
5 | - image: cimg/go:1.17-node
6 | steps:
7 | - checkout
8 | - run: make
9 | - run: make alltest
10 | go-version-last:
11 | docker:
12 | - image: cimg/go:1.16-node
13 | steps:
14 | - checkout
15 | - run: make
16 | - run: make alltest
17 |
18 | workflows:
19 | version: 2
20 | build_and_test:
21 | jobs:
22 | - go-version-latest
23 | - go-version-last
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
26 | # Self
27 | bin/
28 | packages/
29 | release/
30 | test/bin/
31 | vendor/
32 | dist/
33 | .idea/
34 |
35 | # Cache
36 | *.swp
37 |
--------------------------------------------------------------------------------
/client/event/event.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/fatedier/frp/pkg/msg"
7 | )
8 |
9 | type Type int
10 |
11 | const (
12 | EvStartProxy Type = iota
13 | EvCloseProxy
14 | )
15 |
16 | var (
17 | ErrPayloadType = errors.New("error payload type")
18 | )
19 |
20 | type Handler func(evType Type, payload interface{}) error
21 |
22 | type StartProxyPayload struct {
23 | NewProxyMsg *msg.NewProxy
24 | }
25 |
26 | type CloseProxyPayload struct {
27 | CloseProxyMsg *msg.CloseProxy
28 | }
29 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | builds:
2 | - skip: true
3 | checksum:
4 | name_template: 'checksums.txt'
5 | release:
6 | # Same as for github
7 | # Note: it can only be one: either github, gitlab or gitea
8 | github:
9 | owner: fatedier
10 | name: frp
11 |
12 | draft: false
13 |
14 | # You can add extra pre-existing files to the release.
15 | # The filename on the release will be the last part of the path (base). If
16 | # another file with the same name exists, the latest one found will be used.
17 | # Defaults to empty.
18 | extra_files:
19 | - glob: ./release/packages/*
20 |
--------------------------------------------------------------------------------
/hack/run-e2e.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
4 |
5 | which ginkgo &> /dev/null
6 | if [ $? -ne 0 ]; then
7 | echo "ginkgo not found, try to install..."
8 | go install github.com/onsi/ginkgo/ginkgo@latest
9 | fi
10 |
11 | debug=false
12 | if [ x${DEBUG} == x"true" ]; then
13 | debug=true
14 | fi
15 | logLevel=debug
16 | if [ x${LOG_LEVEL} != x"" ]; then
17 | logLevel=${LOG_LEVEL}
18 | fi
19 |
20 | ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
21 |
--------------------------------------------------------------------------------
/pkg/util/metric/date_counter_test.go:
--------------------------------------------------------------------------------
1 | package metric
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestDateCounter(t *testing.T) {
10 | assert := assert.New(t)
11 |
12 | dc := NewDateCounter(3)
13 | dc.Inc(10)
14 | assert.EqualValues(10, dc.TodayCount())
15 |
16 | dc.Dec(5)
17 | assert.EqualValues(5, dc.TodayCount())
18 |
19 | counts := dc.GetLastDaysCount(3)
20 | assert.EqualValues(3, len(counts))
21 | assert.EqualValues(5, counts[0])
22 | assert.EqualValues(0, counts[1])
23 | assert.EqualValues(0, counts[2])
24 |
25 | dcTmp := dc.Snapshot()
26 | assert.EqualValues(5, dcTmp.TodayCount())
27 | }
28 |
--------------------------------------------------------------------------------
/test/e2e/suites.go:
--------------------------------------------------------------------------------
1 | package e2e
2 |
3 | // CleanupSuite is the boilerplate that can be used after tests on ginkgo were run, on the SynchronizedAfterSuite step.
4 | // Similar to SynchronizedBeforeSuite, we want to run some operations only once (such as collecting cluster logs).
5 | // Here, the order of functions is reversed; first, the function which runs everywhere,
6 | // and then the function that only runs on the first Ginkgo node.
7 | func CleanupSuite() {
8 | // Run on all Ginkgo nodes
9 | }
10 |
11 | // AfterSuiteActions are actions that are run on ginkgo's SynchronizedAfterSuite
12 | func AfterSuiteActions() {
13 | // Run only Ginkgo on node 1
14 | }
15 |
--------------------------------------------------------------------------------
/test/e2e/pkg/rpc/rpc.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "io"
8 | )
9 |
10 | func WriteBytes(w io.Writer, buf []byte) (int, error) {
11 | out := bytes.NewBuffer(nil)
12 | binary.Write(out, binary.BigEndian, int64(len(buf)))
13 | out.Write(buf)
14 | return w.Write(out.Bytes())
15 | }
16 |
17 | func ReadBytes(r io.Reader) ([]byte, error) {
18 | var length int64
19 | if err := binary.Read(r, binary.BigEndian, &length); err != nil {
20 | return nil, err
21 | }
22 | buffer := make([]byte, length)
23 | n, err := io.ReadFull(r, buffer)
24 | if err != nil {
25 | return nil, err
26 | }
27 | if int64(n) != length {
28 | return nil, errors.New("invalid length")
29 | }
30 | return buffer, nil
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/errors/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package errors
16 |
17 | import (
18 | "errors"
19 | )
20 |
21 | var (
22 | ErrMsgType = errors.New("message type error")
23 | ErrCtlClosed = errors.New("control is closed")
24 | )
25 |
--------------------------------------------------------------------------------
/test/e2e/examples.go:
--------------------------------------------------------------------------------
1 | package e2e
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/fatedier/frp/test/e2e/framework"
7 | "github.com/fatedier/frp/test/e2e/framework/consts"
8 |
9 | . "github.com/onsi/ginkgo"
10 | )
11 |
12 | var _ = Describe("[Feature: Example]", func() {
13 | f := framework.NewDefaultFramework()
14 |
15 | Describe("TCP", func() {
16 | It("Expose a TCP echo server", func() {
17 | serverConf := consts.DefaultServerConfig
18 | clientConf := consts.DefaultClientConfig
19 |
20 | remotePort := f.AllocPort()
21 | clientConf += fmt.Sprintf(`
22 | [tcp]
23 | type = tcp
24 | local_port = {{ .%s }}
25 | remote_port = %d
26 | `, framework.TCPEchoServerPort, remotePort)
27 |
28 | f.RunProcesses([]string{serverConf}, []string{clientConf})
29 |
30 | framework.NewRequestExpect(f).Port(remotePort).Ensure()
31 | })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/pkg/config/README.md:
--------------------------------------------------------------------------------
1 | So far, there is no mature Go project that does well in parsing `*.ini` files.
2 |
3 | By comparison, we have selected an open source project: `https://github.com/go-ini/ini`.
4 |
5 | This library helped us solve most of the key-value matching, but there are still some problems, such as not supporting parsing `map`.
6 |
7 | We add our own logic on the basis of this library. In the current situationwhich, we need to complete the entire `Unmarshal` in two steps:
8 |
9 | * Step#1, use `go-ini` to complete the basic parameter matching;
10 | * Step#2, parse our custom parameters to realize parsing special structure, like `map`, `array`.
11 |
12 | Some of the keywords in `tag`(like inline, extends, etc.) may be different from standard libraries such as `json` and `protobuf` in Go. For details, please refer to the library documentation: https://ini.unknwon.io/docs/intro.
--------------------------------------------------------------------------------
/pkg/util/vhost/https_test.go:
--------------------------------------------------------------------------------
1 | package vhost
2 |
3 | import (
4 | "crypto/tls"
5 | "net"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestGetHTTPSHostname(t *testing.T) {
13 | require := require.New(t)
14 |
15 | l, err := net.Listen("tcp", ":")
16 | require.NoError(err)
17 | defer l.Close()
18 |
19 | var conn net.Conn
20 | go func() {
21 | conn, _ = l.Accept()
22 | require.NotNil(conn)
23 | }()
24 |
25 | go func() {
26 | time.Sleep(100 * time.Millisecond)
27 | tls.Dial("tcp", l.Addr().String(), &tls.Config{
28 | InsecureSkipVerify: true,
29 | ServerName: "example.com",
30 | })
31 | }()
32 |
33 | time.Sleep(200 * time.Millisecond)
34 | _, infos, err := GetHTTPSHostname(conn)
35 | require.NoError(err)
36 | require.Equal("example.com", infos["Host"])
37 | require.Equal("https", infos["Scheme"])
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | goreleaser:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v2
12 | with:
13 | fetch-depth: 0
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v2
17 | with:
18 | go-version: 1.17
19 |
20 | - run: |
21 | # https://github.com/actions/setup-go/issues/107
22 | cp -f `which go` /usr/bin/go
23 |
24 | - name: Make All
25 | run: |
26 | ./package.sh
27 |
28 | - name: Run GoReleaser
29 | uses: goreleaser/goreleaser-action@v2
30 | with:
31 | version: latest
32 | args: release --rm-dist --release-notes=./Release.md
33 | env:
34 | GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
35 |
--------------------------------------------------------------------------------
/test/e2e/framework/consts/consts.go:
--------------------------------------------------------------------------------
1 | package consts
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/fatedier/frp/test/e2e/pkg/port"
8 | )
9 |
10 | const (
11 | TestString = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."
12 |
13 | DefaultTimeout = 2 * time.Second
14 | )
15 |
16 | var (
17 | PortServerName string
18 | PortClientAdmin string
19 |
20 | DefaultServerConfig = `
21 | [common]
22 | bind_port = {{ .%s }}
23 | log_level = trace
24 | `
25 |
26 | DefaultClientConfig = `
27 | [common]
28 | server_port = {{ .%s }}
29 | log_level = trace
30 | `
31 | )
32 |
33 | func init() {
34 | PortServerName = port.GenName("Server")
35 | PortClientAdmin = port.GenName("ClientAdmin")
36 | DefaultServerConfig = fmt.Sprintf(DefaultServerConfig, port.GenName("Server"))
37 | DefaultClientConfig = fmt.Sprintf(DefaultClientConfig, port.GenName("Server"))
38 | }
39 |
--------------------------------------------------------------------------------
/cmd/frpc/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "math/rand"
19 | "time"
20 |
21 | _ "github.com/fatedier/frp/assets/frpc"
22 | "github.com/fatedier/frp/cmd/frpc/sub"
23 |
24 | "github.com/fatedier/golib/crypto"
25 | )
26 |
27 | func main() {
28 | crypto.DefaultSalt = "frp"
29 | rand.Seed(time.Now().UnixNano())
30 |
31 | sub.Execute()
32 | }
33 |
--------------------------------------------------------------------------------
/cmd/frps/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "math/rand"
19 | "time"
20 |
21 | "github.com/fatedier/golib/crypto"
22 |
23 | _ "github.com/fatedier/frp/assets/frps"
24 | _ "github.com/fatedier/frp/pkg/metrics"
25 | )
26 |
27 | func main() {
28 | crypto.DefaultSalt = "frp"
29 | rand.Seed(time.Now().UnixNano())
30 |
31 | Execute()
32 | }
33 |
--------------------------------------------------------------------------------
/test/e2e/e2e_test.go:
--------------------------------------------------------------------------------
1 | package e2e
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "testing"
8 |
9 | "github.com/fatedier/frp/pkg/util/log"
10 | "github.com/fatedier/frp/test/e2e/framework"
11 |
12 | // test source
13 | _ "github.com/fatedier/frp/test/e2e/basic"
14 | _ "github.com/fatedier/frp/test/e2e/features"
15 | _ "github.com/fatedier/frp/test/e2e/plugin"
16 |
17 | _ "github.com/onsi/ginkgo"
18 | )
19 |
20 | // handleFlags sets up all flags and parses the command line.
21 | func handleFlags() {
22 | framework.RegisterCommonFlags(flag.CommandLine)
23 | flag.Parse()
24 | }
25 |
26 | func TestMain(m *testing.M) {
27 | // Register test flags, then parse flags.
28 | handleFlags()
29 |
30 | if err := framework.ValidateTestContext(&framework.TestContext); err != nil {
31 | fmt.Println(err)
32 | os.Exit(1)
33 | }
34 |
35 | log.InitLog("console", "", framework.TestContext.LogLevel, 0, true)
36 | os.Exit(m.Run())
37 | }
38 |
39 | func TestE2E(t *testing.T) {
40 | RunE2ETests(t)
41 | }
42 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | export PATH := $(GOPATH)/bin:$(PATH)
2 | export GO111MODULE=on
3 | LDFLAGS := -s -w
4 |
5 | all: fmt build
6 |
7 | build: frps frpc
8 |
9 | # compile assets into binary file
10 | file:
11 | rm -rf ./assets/frps/static/*
12 | rm -rf ./assets/frpc/static/*
13 | cp -rf ./web/frps/dist/* ./assets/frps/static
14 | cp -rf ./web/frpc/dist/* ./assets/frpc/static
15 |
16 | fmt:
17 | go fmt ./...
18 |
19 | frps:
20 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
21 |
22 | frpc:
23 | env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc
24 |
25 | test: gotest
26 |
27 | gotest:
28 | go test -v --cover ./assets/...
29 | go test -v --cover ./cmd/...
30 | go test -v --cover ./client/...
31 | go test -v --cover ./server/...
32 | go test -v --cover ./pkg/...
33 |
34 | e2e:
35 | ./hack/run-e2e.sh
36 |
37 | e2e-trace:
38 | DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
39 |
40 | alltest: gotest e2e
41 |
42 | clean:
43 | rm -f ./bin/frpc
44 | rm -f ./bin/frps
45 |
--------------------------------------------------------------------------------
/web/frps/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | //import ElementUI from 'element-ui'
3 | import {
4 | Button,
5 | Form,
6 | FormItem,
7 | Row,
8 | Col,
9 | Table,
10 | TableColumn,
11 | Popover,
12 | Menu,
13 | Submenu,
14 | MenuItem,
15 | Tag
16 | } from 'element-ui'
17 | import lang from 'element-ui/lib/locale/lang/en'
18 | import locale from 'element-ui/lib/locale'
19 | import 'element-ui/lib/theme-chalk/index.css'
20 | import './utils/less/custom.less'
21 |
22 | import App from './App.vue'
23 | import router from './router'
24 | import 'whatwg-fetch'
25 |
26 | locale.use(lang)
27 |
28 | Vue.use(Button)
29 | Vue.use(Form)
30 | Vue.use(FormItem)
31 | Vue.use(Row)
32 | Vue.use(Col)
33 | Vue.use(Table)
34 | Vue.use(TableColumn)
35 | Vue.use(Popover)
36 | Vue.use(Menu)
37 | Vue.use(Submenu)
38 | Vue.use(MenuItem)
39 | Vue.use(Tag)
40 |
41 | Vue.config.productionTip = false
42 |
43 | new Vue({
44 | el: '#app',
45 | router,
46 | template: '',
47 | components: { App }
48 | })
49 |
--------------------------------------------------------------------------------
/pkg/plugin/server/tracer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "context"
19 | )
20 |
21 | type key int
22 |
23 | const (
24 | reqidKey key = 0
25 | )
26 |
27 | func NewReqidContext(ctx context.Context, reqid string) context.Context {
28 | return context.WithValue(ctx, reqidKey, reqid)
29 | }
30 |
31 | func GetReqidFromContext(ctx context.Context) string {
32 | ret, _ := ctx.Value(reqidKey).(string)
33 | return ret
34 | }
35 |
--------------------------------------------------------------------------------
/server/group/group.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package group
16 |
17 | import (
18 | "errors"
19 | )
20 |
21 | var (
22 | ErrGroupAuthFailed = errors.New("group auth failed")
23 | ErrGroupParamsInvalid = errors.New("group params invalid")
24 | ErrListenerClosed = errors.New("group listener closed")
25 | ErrGroupDifferentPort = errors.New("group should have same remote port")
26 | ErrProxyRepeated = errors.New("group proxy repeated")
27 | )
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea to improve frp
3 | title: "[Feature Request] "
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | This is only used to request new product features.
10 | - type: textarea
11 | id: feature-request
12 | attributes:
13 | label: Describe the feature request
14 | description: Tell us what's you want and why it should be added in frp.
15 | validations:
16 | required: true
17 | - type: textarea
18 | id: alternatives
19 | attributes:
20 | label: Describe alternatives you've considered
21 | - type: checkboxes
22 | id: area
23 | attributes:
24 | label: Affected area
25 | options:
26 | - label: "Docs"
27 | - label: "Installation"
28 | - label: "Performance and Scalability"
29 | - label: "Security"
30 | - label: "User Experience"
31 | - label: "Test and Release"
32 | - label: "Developer Infrastructure"
33 | - label: "Client Plugin"
34 | - label: "Server Plugin"
35 | - label: "Extensions"
36 | - label: "Others"
37 |
--------------------------------------------------------------------------------
/web/frps/src/components/Traffic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
34 |
35 |
37 |
--------------------------------------------------------------------------------
/pkg/util/metric/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package metric
16 |
17 | // GaugeMetric represents a single numerical value that can arbitrarily go up
18 | // and down.
19 | type GaugeMetric interface {
20 | Inc()
21 | Dec()
22 | Set(float64)
23 | }
24 |
25 | // CounterMetric represents a single numerical value that only ever
26 | // goes up.
27 | type CounterMetric interface {
28 | Inc()
29 | }
30 |
31 | // HistogramMetric counts individual observations.
32 | type HistogramMetric interface {
33 | Observe(float64)
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/plugin/server/plugin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "context"
19 | )
20 |
21 | const (
22 | APIVersion = "0.1.0"
23 |
24 | OpLogin = "Login"
25 | OpNewProxy = "NewProxy"
26 | OpPing = "Ping"
27 | OpNewWorkConn = "NewWorkConn"
28 | OpNewUserConn = "NewUserConn"
29 | )
30 |
31 | type Plugin interface {
32 | Name() string
33 | IsSupport(op string) bool
34 | Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error)
35 | }
36 |
--------------------------------------------------------------------------------
/web/frpc/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | // import ElementUI from 'element-ui'
3 | import {
4 | Button,
5 | Form,
6 | FormItem,
7 | Row,
8 | Col,
9 | Table,
10 | TableColumn,
11 | Menu,
12 | MenuItem,
13 | MessageBox,
14 | Message,
15 | Input
16 | } from 'element-ui'
17 | import lang from 'element-ui/lib/locale/lang/en'
18 | import locale from 'element-ui/lib/locale'
19 | import 'element-ui/lib/theme-chalk/index.css'
20 | import './utils/less/custom.less'
21 |
22 | import App from './App.vue'
23 | import router from './router'
24 | import 'whatwg-fetch'
25 |
26 | locale.use(lang)
27 |
28 | Vue.use(Button)
29 | Vue.use(Form)
30 | Vue.use(FormItem)
31 | Vue.use(Row)
32 | Vue.use(Col)
33 | Vue.use(Table)
34 | Vue.use(TableColumn)
35 | Vue.use(Menu)
36 | Vue.use(MenuItem)
37 | Vue.use(Input)
38 |
39 | Vue.prototype.$msgbox = MessageBox;
40 | Vue.prototype.$confirm = MessageBox.confirm
41 | Vue.prototype.$message = Message
42 |
43 | //Vue.use(ElementUI)
44 |
45 | Vue.config.productionTip = false
46 |
47 | new Vue({
48 | el: '#app',
49 | router,
50 | template: '',
51 | components: { App }
52 | })
53 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: "Close stale issues"
2 | on:
3 | schedule:
4 | - cron: "20 0 * * *"
5 | workflow_dispatch:
6 | inputs:
7 | debug-only:
8 | description: 'In debug mod'
9 | required: false
10 | default: 'false'
11 | jobs:
12 | stale:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/stale@v3
16 | with:
17 | repo-token: ${{ secrets.GITHUB_TOKEN }}
18 | stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
19 | stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
20 | stale-issue-label: 'lifecycle/stale'
21 | exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
22 | stale-pr-label: 'lifecycle/stale'
23 | exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
24 | days-before-stale: 30
25 | days-before-close: 7
26 | debug-only: ${{ github.event.inputs.debug-only }}
27 |
--------------------------------------------------------------------------------
/pkg/util/util/util_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestRandId(t *testing.T) {
10 | assert := assert.New(t)
11 | id, err := RandID()
12 | assert.NoError(err)
13 | t.Log(id)
14 | assert.Equal(16, len(id))
15 | }
16 |
17 | func TestGetAuthKey(t *testing.T) {
18 | assert := assert.New(t)
19 | key := GetAuthKey("1234", 1488720000)
20 | t.Log(key)
21 | assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
22 | }
23 |
24 | func TestParseRangeNumbers(t *testing.T) {
25 | assert := assert.New(t)
26 | numbers, err := ParseRangeNumbers("2-5")
27 | if assert.NoError(err) {
28 | assert.Equal([]int64{2, 3, 4, 5}, numbers)
29 | }
30 |
31 | numbers, err = ParseRangeNumbers("1")
32 | if assert.NoError(err) {
33 | assert.Equal([]int64{1}, numbers)
34 | }
35 |
36 | numbers, err = ParseRangeNumbers("3-5,8")
37 | if assert.NoError(err) {
38 | assert.Equal([]int64{3, 4, 5, 8}, numbers)
39 | }
40 |
41 | numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
42 | if assert.NoError(err) {
43 | assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
44 | }
45 |
46 | _, err = ParseRangeNumbers("3-a")
47 | assert.Error(err)
48 | }
49 |
--------------------------------------------------------------------------------
/test/e2e/plugin/utils.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "crypto/tls"
5 | "encoding/json"
6 | "io"
7 | "net/http"
8 |
9 | plugin "github.com/fatedier/frp/pkg/plugin/server"
10 | "github.com/fatedier/frp/pkg/util/log"
11 | "github.com/fatedier/frp/test/e2e/mock/server/httpserver"
12 | )
13 |
14 | type PluginHandler func(req *plugin.Request) *plugin.Response
15 |
16 | type NewPluginRequest func() *plugin.Request
17 |
18 | func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler PluginHandler, tlsConfig *tls.Config) *httpserver.Server {
19 | return httpserver.New(
20 | httpserver.WithBindPort(port),
21 | httpserver.WithTlsConfig(tlsConfig),
22 | httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
23 | r := newFunc()
24 | buf, err := io.ReadAll(req.Body)
25 | if err != nil {
26 | w.WriteHeader(500)
27 | return
28 | }
29 | log.Trace("plugin request: %s", string(buf))
30 | err = json.Unmarshal(buf, &r)
31 | if err != nil {
32 | w.WriteHeader(500)
33 | return
34 | }
35 | resp := handler(r)
36 | buf, _ = json.Marshal(resp)
37 | log.Trace("plugin response: %s", string(buf))
38 | w.Write(buf)
39 | })),
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/util/xlog/ctx.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package xlog
16 |
17 | import (
18 | "context"
19 | )
20 |
21 | type key int
22 |
23 | const (
24 | xlogKey key = 0
25 | )
26 |
27 | func NewContext(ctx context.Context, xl *Logger) context.Context {
28 | return context.WithValue(ctx, xlogKey, xl)
29 | }
30 |
31 | func FromContext(ctx context.Context) (xl *Logger, ok bool) {
32 | xl, ok = ctx.Value(xlogKey).(*Logger)
33 | return
34 | }
35 |
36 | func FromContextSafe(ctx context.Context) *Logger {
37 | xl, ok := ctx.Value(xlogKey).(*Logger)
38 | if !ok {
39 | xl = New()
40 | }
41 | return xl
42 | }
43 |
--------------------------------------------------------------------------------
/Makefile.cross-compiles:
--------------------------------------------------------------------------------
1 | export PATH := $(GOPATH)/bin:$(PATH)
2 | export GO111MODULE=on
3 | LDFLAGS := -s -w
4 |
5 | os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat
6 |
7 | all: build
8 |
9 | build: app
10 |
11 | app:
12 | @$(foreach n, $(os-archs),\
13 | os=$(shell echo "$(n)" | cut -d : -f 1);\
14 | arch=$(shell echo "$(n)" | cut -d : -f 2);\
15 | gomips=$(shell echo "$(n)" | cut -d : -f 3);\
16 | target_suffix=$${os}_$${arch};\
17 | echo "Build $${os}-$${arch}...";\
18 | env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\
19 | env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\
20 | echo "Build $${os}-$${arch} done";\
21 | )
22 | @mv ./release/frpc_windows_386 ./release/frpc_windows_386.exe
23 | @mv ./release/frps_windows_386 ./release/frps_windows_386.exe
24 | @mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
25 | @mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
26 |
--------------------------------------------------------------------------------
/pkg/config/types_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | import (
18 | "encoding/json"
19 | "testing"
20 |
21 | "github.com/stretchr/testify/assert"
22 | )
23 |
24 | type Wrap struct {
25 | B BandwidthQuantity `json:"b"`
26 | Int int `json:"int"`
27 | }
28 |
29 | func TestBandwidthQuantity(t *testing.T) {
30 | assert := assert.New(t)
31 |
32 | var w Wrap
33 | err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
34 | assert.NoError(err)
35 | assert.EqualValues(1*KB, w.B.Bytes())
36 |
37 | buf, err := json.Marshal(&w)
38 | assert.NoError(err)
39 | assert.Equal(`{"b":"1KB","int":5}`, string(buf))
40 | }
41 |
--------------------------------------------------------------------------------
/cmd/frpc/sub/verify.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The frp Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package sub
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/fatedier/frp/pkg/config"
22 |
23 | "github.com/spf13/cobra"
24 | )
25 |
26 | func init() {
27 | rootCmd.AddCommand(verifyCmd)
28 | }
29 |
30 | var verifyCmd = &cobra.Command{
31 | Use: "verify",
32 | Short: "Verify that the configures is valid",
33 | RunE: func(cmd *cobra.Command, args []string) error {
34 | _, _, _, err := config.ParseClientConfig(cfgFile)
35 | if err != nil {
36 | fmt.Println(err)
37 | os.Exit(1)
38 | }
39 |
40 | fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile)
41 | return nil
42 | },
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/msg/ctl.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package msg
16 |
17 | import (
18 | "io"
19 |
20 | jsonMsg "github.com/fatedier/golib/msg/json"
21 | )
22 |
23 | type Message = jsonMsg.Message
24 |
25 | var (
26 | msgCtl *jsonMsg.MsgCtl
27 | )
28 |
29 | func init() {
30 | msgCtl = jsonMsg.NewMsgCtl()
31 | for typeByte, msg := range msgTypeMap {
32 | msgCtl.RegisterMsg(typeByte, msg)
33 | }
34 | }
35 |
36 | func ReadMsg(c io.Reader) (msg Message, err error) {
37 | return msgCtl.ReadMsg(c)
38 | }
39 |
40 | func ReadMsgInto(c io.Reader, msg Message) (err error) {
41 | return msgCtl.ReadMsgInto(c, msg)
42 | }
43 |
44 | func WriteMsg(c io.Writer, msg interface{}) (err error) {
45 | return msgCtl.WriteMsg(c, msg)
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/util/limit/reader.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package limit
16 |
17 | import (
18 | "context"
19 | "io"
20 |
21 | "golang.org/x/time/rate"
22 | )
23 |
24 | type Reader struct {
25 | r io.Reader
26 | limiter *rate.Limiter
27 | }
28 |
29 | func NewReader(r io.Reader, limiter *rate.Limiter) *Reader {
30 | return &Reader{
31 | r: r,
32 | limiter: limiter,
33 | }
34 | }
35 |
36 | func (r *Reader) Read(p []byte) (n int, err error) {
37 | b := r.limiter.Burst()
38 | if b < len(p) {
39 | p = p[:b]
40 | }
41 | n, err = r.r.Read(p)
42 | if err != nil {
43 | return
44 | }
45 |
46 | err = r.limiter.WaitN(context.Background(), n)
47 | if err != nil {
48 | return
49 | }
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/consts/consts.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package consts
16 |
17 | var (
18 | // proxy status
19 | Idle string = "idle"
20 | Working string = "working"
21 | Closed string = "closed"
22 | Online string = "online"
23 | Offline string = "offline"
24 |
25 | // proxy type
26 | TCPProxy string = "tcp"
27 | UDPProxy string = "udp"
28 | TCPMuxProxy string = "tcpmux"
29 | HTTPProxy string = "http"
30 | HTTPSProxy string = "https"
31 | STCPProxy string = "stcp"
32 | XTCPProxy string = "xtcp"
33 | SUDPProxy string = "sudp"
34 |
35 | // authentication method
36 | TokenAuthMethod string = "token"
37 | OidcAuthMethod string = "oidc"
38 |
39 | // TCP multiplexer
40 | HTTPConnectTCPMultiplexer string = "httpconnect"
41 | )
42 |
--------------------------------------------------------------------------------
/web/frps/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Overview from '../components/Overview.vue'
4 | import ProxiesTcp from '../components/ProxiesTcp.vue'
5 | import ProxiesUdp from '../components/ProxiesUdp.vue'
6 | import ProxiesHttp from '../components/ProxiesHttp.vue'
7 | import ProxiesHttps from '../components/ProxiesHttps.vue'
8 | import ProxiesStcp from '../components/ProxiesStcp.vue'
9 | import ProxiesSudp from '../components/ProxiesSudp.vue'
10 |
11 | Vue.use(Router)
12 |
13 | export default new Router({
14 | routes: [{
15 | path: '/',
16 | name: 'Overview',
17 | component: Overview
18 | }, {
19 | path: '/proxies/tcp',
20 | name: 'ProxiesTcp',
21 | component: ProxiesTcp
22 | }, {
23 | path: '/proxies/udp',
24 | name: 'ProxiesUdp',
25 | component: ProxiesUdp
26 | }, {
27 | path: '/proxies/http',
28 | name: 'ProxiesHttp',
29 | component: ProxiesHttp
30 | }, {
31 | path: '/proxies/https',
32 | name: 'ProxiesHttps',
33 | component: ProxiesHttps
34 | }, {
35 | path: '/proxies/stcp',
36 | name: 'ProxiesStcp',
37 | component: ProxiesStcp
38 | }, {
39 | path: '/proxies/sudp',
40 | name: 'ProxiesSudp',
41 | component: ProxiesSudp
42 | }]
43 | })
44 |
--------------------------------------------------------------------------------
/pkg/config/utils.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The frp Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | import (
18 | "strings"
19 | )
20 |
21 | func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string {
22 | m := make(map[string]string)
23 |
24 | for key, value := range set {
25 | if strings.HasPrefix(key, prefix) {
26 | m[strings.TrimPrefix(key, prefix)] = value
27 | }
28 | }
29 |
30 | if len(m) == 0 {
31 | return nil
32 | }
33 |
34 | return m
35 | }
36 |
37 | func GetMapByPrefix(set map[string]string, prefix string) map[string]string {
38 | m := make(map[string]string)
39 |
40 | for key, value := range set {
41 | if strings.HasPrefix(key, prefix) {
42 | m[key] = value
43 | }
44 | }
45 |
46 | if len(m) == 0 {
47 | return nil
48 | }
49 |
50 | return m
51 | }
52 |
--------------------------------------------------------------------------------
/assets/frpc/static/manifest.js:
--------------------------------------------------------------------------------
1 | !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l=6"
19 | },
20 | "devDependencies": {
21 | "autoprefixer": "^9.4.7",
22 | "babel-core": "^6.26.3",
23 | "babel-eslint": "^10.0.1",
24 | "babel-loader": "^7.1.5",
25 | "babel-plugin-component": "^1.1.1",
26 | "babel-preset-es2015": "^6.24.1",
27 | "css-loader": "^2.1.0",
28 | "eslint": "^5.12.1",
29 | "eslint-config-enough": "^0.3.4",
30 | "eslint-loader": "^2.1.1",
31 | "file-loader": "^3.0.1",
32 | "html-loader": "^0.5.5",
33 | "html-webpack-plugin": "^2.24.1",
34 | "less": "^3.9.0",
35 | "less-loader": "^4.1.0",
36 | "postcss-loader": "^3.0.0",
37 | "rimraf": "^2.6.3",
38 | "style-loader": "^0.23.1",
39 | "url-loader": "^1.1.2",
40 | "vue-loader": "^15.6.2",
41 | "vue-template-compiler": "^2.5.22",
42 | "webpack": "^2.7.0",
43 | "webpack-cli": "^3.2.1",
44 | "webpack-dev-server": "^3.1.14"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/server/proxy/stcp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proxy
16 |
17 | import (
18 | "github.com/fatedier/frp/pkg/config"
19 | )
20 |
21 | type STCPProxy struct {
22 | *BaseProxy
23 | cfg *config.STCPProxyConf
24 | }
25 |
26 | func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
27 | xl := pxy.xl
28 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
29 | if errRet != nil {
30 | err = errRet
31 | return
32 | }
33 | pxy.listeners = append(pxy.listeners, listener)
34 | xl.Info("stcp proxy custom listen success")
35 |
36 | pxy.startListenHandler(pxy, HandleUserTCPConnection)
37 | return
38 | }
39 |
40 | func (pxy *STCPProxy) GetConf() config.ProxyConf {
41 | return pxy.cfg
42 | }
43 |
44 | func (pxy *STCPProxy) Close() {
45 | pxy.BaseProxy.Close()
46 | pxy.rc.VisitorManager.CloseListener(pxy.GetName())
47 | }
48 |
--------------------------------------------------------------------------------
/server/proxy/sudp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proxy
16 |
17 | import (
18 | "github.com/fatedier/frp/pkg/config"
19 | )
20 |
21 | type SUDPProxy struct {
22 | *BaseProxy
23 | cfg *config.SUDPProxyConf
24 | }
25 |
26 | func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
27 | xl := pxy.xl
28 |
29 | listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
30 | if errRet != nil {
31 | err = errRet
32 | return
33 | }
34 | pxy.listeners = append(pxy.listeners, listener)
35 | xl.Info("sudp proxy custom listen success")
36 |
37 | pxy.startListenHandler(pxy, HandleUserTCPConnection)
38 | return
39 | }
40 |
41 | func (pxy *SUDPProxy) GetConf() config.ProxyConf {
42 | return pxy.cfg
43 | }
44 |
45 | func (pxy *SUDPProxy) Close() {
46 | pxy.BaseProxy.Close()
47 | pxy.rc.VisitorManager.CloseListener(pxy.GetName())
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/util/limit/writer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package limit
16 |
17 | import (
18 | "context"
19 | "io"
20 |
21 | "golang.org/x/time/rate"
22 | )
23 |
24 | type Writer struct {
25 | w io.Writer
26 | limiter *rate.Limiter
27 | }
28 |
29 | func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer {
30 | return &Writer{
31 | w: w,
32 | limiter: limiter,
33 | }
34 | }
35 |
36 | func (w *Writer) Write(p []byte) (n int, err error) {
37 | var nn int
38 | b := w.limiter.Burst()
39 | for {
40 | end := len(p)
41 | if end == 0 {
42 | break
43 | }
44 | if b < len(p) {
45 | end = b
46 | }
47 | err = w.limiter.WaitN(context.Background(), end)
48 | if err != nil {
49 | return
50 | }
51 |
52 | nn, err = w.w.Write(p[:end])
53 | n += nn
54 | if err != nil {
55 | return
56 | }
57 | p = p[end:]
58 | }
59 | return
60 | }
61 |
--------------------------------------------------------------------------------
/server/metrics/metrics.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type ServerMetrics interface {
8 | NewClient()
9 | CloseClient()
10 | NewProxy(name string, proxyType string)
11 | CloseProxy(name string, proxyType string)
12 | OpenConnection(name string, proxyType string)
13 | CloseConnection(name string, proxyType string)
14 | AddTrafficIn(name string, proxyType string, trafficBytes int64)
15 | AddTrafficOut(name string, proxyType string, trafficBytes int64)
16 | }
17 |
18 | var Server ServerMetrics = noopServerMetrics{}
19 |
20 | var registerMetrics sync.Once
21 |
22 | func Register(m ServerMetrics) {
23 | registerMetrics.Do(func() {
24 | Server = m
25 | })
26 | }
27 |
28 | type noopServerMetrics struct{}
29 |
30 | func (noopServerMetrics) NewClient() {}
31 | func (noopServerMetrics) CloseClient() {}
32 | func (noopServerMetrics) NewProxy(name string, proxyType string) {}
33 | func (noopServerMetrics) CloseProxy(name string, proxyType string) {}
34 | func (noopServerMetrics) OpenConnection(name string, proxyType string) {}
35 | func (noopServerMetrics) CloseConnection(name string, proxyType string) {}
36 | func (noopServerMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {}
37 | func (noopServerMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {}
38 |
--------------------------------------------------------------------------------
/web/frps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frps-dashboard",
3 | "description": "A dashboard for frp server.",
4 | "author": "fatedier",
5 | "private": true,
6 | "scripts": {
7 | "dev": "webpack-dev-server -d --inline --hot --env.dev",
8 | "build": "rimraf dist && webpack -p --progress --hide-modules"
9 | },
10 | "dependencies": {
11 | "bootstrap": "^3.3.7",
12 | "echarts": "^3.5.0",
13 | "element-ui": "^2.3.8",
14 | "humanize-plus": "^1.8.2",
15 | "vue": "^2.5.16",
16 | "vue-resource": "^1.2.1",
17 | "vue-router": "^2.3.0",
18 | "whatwg-fetch": "^2.0.3"
19 | },
20 | "engines": {
21 | "node": ">=6"
22 | },
23 | "devDependencies": {
24 | "autoprefixer": "^6.6.0",
25 | "babel-core": "^6.21.0",
26 | "babel-eslint": "^7.1.1",
27 | "babel-loader": "^6.4.0",
28 | "babel-plugin-component": "^1.1.1",
29 | "babel-preset-es2015": "^6.13.2",
30 | "css-loader": "^0.27.0",
31 | "eslint": "^3.12.2",
32 | "eslint-config-enough": "^0.2.2",
33 | "eslint-loader": "^1.6.3",
34 | "file-loader": "^0.10.1",
35 | "html-loader": "^0.4.5",
36 | "html-webpack-plugin": "^2.24.1",
37 | "less": "^3.0.4",
38 | "less-loader": "^4.1.0",
39 | "postcss-loader": "^1.3.3",
40 | "rimraf": "^2.5.4",
41 | "style-loader": "^0.13.2",
42 | "url-loader": "^1.0.1",
43 | "vue-loader": "^15.0.10",
44 | "vue-template-compiler": "^2.1.8",
45 | "webpack": "^2.2.0-rc.4",
46 | "webpack-dev-server": "^3.1.4"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/e2e/pkg/process/process.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "os/exec"
7 | )
8 |
9 | type Process struct {
10 | cmd *exec.Cmd
11 | cancel context.CancelFunc
12 | errorOutput *bytes.Buffer
13 | stdOutput *bytes.Buffer
14 |
15 | beforeStopHandler func()
16 | stopped bool
17 | }
18 |
19 | func New(path string, params []string) *Process {
20 | return NewWithEnvs(path, params, nil)
21 | }
22 |
23 | func NewWithEnvs(path string, params []string, envs []string) *Process {
24 | ctx, cancel := context.WithCancel(context.Background())
25 | cmd := exec.CommandContext(ctx, path, params...)
26 | cmd.Env = envs
27 | p := &Process{
28 | cmd: cmd,
29 | cancel: cancel,
30 | }
31 | p.errorOutput = bytes.NewBufferString("")
32 | p.stdOutput = bytes.NewBufferString("")
33 | cmd.Stderr = p.errorOutput
34 | cmd.Stdout = p.stdOutput
35 | return p
36 | }
37 |
38 | func (p *Process) Start() error {
39 | return p.cmd.Start()
40 | }
41 |
42 | func (p *Process) Stop() error {
43 | if p.stopped {
44 | return nil
45 | }
46 | defer func() {
47 | p.stopped = true
48 | }()
49 | if p.beforeStopHandler != nil {
50 | p.beforeStopHandler()
51 | }
52 | p.cancel()
53 | return p.cmd.Wait()
54 | }
55 |
56 | func (p *Process) ErrorOutput() string {
57 | return p.errorOutput.String()
58 | }
59 |
60 | func (p *Process) StdOutput() string {
61 | return p.stdOutput.String()
62 | }
63 |
64 | func (p *Process) SetBeforeStopHandler(fn func()) {
65 | p.beforeStopHandler = fn
66 | }
67 |
--------------------------------------------------------------------------------
/cmd/frps/verify.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The frp Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/fatedier/frp/pkg/config"
22 |
23 | "github.com/spf13/cobra"
24 | )
25 |
26 | func init() {
27 | rootCmd.AddCommand(verifyCmd)
28 | }
29 |
30 | var verifyCmd = &cobra.Command{
31 | Use: "verify",
32 | Short: "Verify that the configures is valid",
33 | RunE: func(cmd *cobra.Command, args []string) error {
34 | if cfgFile == "" {
35 | fmt.Println("no config file is specified")
36 | return nil
37 | }
38 | iniContent, err := config.GetRenderedConfFromFile(cfgFile)
39 | if err != nil {
40 | fmt.Println(err)
41 | os.Exit(1)
42 | }
43 |
44 | _, err = parseServerCommonCfg(CfgFileTypeIni, iniContent)
45 | if err != nil {
46 | fmt.Println(err)
47 | os.Exit(1)
48 | }
49 |
50 | fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
51 | return nil
52 | },
53 | }
54 |
--------------------------------------------------------------------------------
/test/e2e/features/bandwidth_limit.go:
--------------------------------------------------------------------------------
1 | package features
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | "github.com/fatedier/frp/test/e2e/framework"
9 | "github.com/fatedier/frp/test/e2e/framework/consts"
10 | "github.com/fatedier/frp/test/e2e/mock/server/streamserver"
11 | "github.com/fatedier/frp/test/e2e/pkg/request"
12 |
13 | . "github.com/onsi/ginkgo"
14 | )
15 |
16 | var _ = Describe("[Feature: Bandwidth Limit]", func() {
17 | f := framework.NewDefaultFramework()
18 |
19 | It("Proxy Bandwidth Limit", func() {
20 | serverConf := consts.DefaultServerConfig
21 | clientConf := consts.DefaultClientConfig
22 |
23 | localPort := f.AllocPort()
24 | localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))
25 | f.RunServer("", localServer)
26 |
27 | remotePort := f.AllocPort()
28 | clientConf += fmt.Sprintf(`
29 | [tcp]
30 | type = tcp
31 | local_port = %d
32 | remote_port = %d
33 | bandwidth_limit = 10KB
34 | `, localPort, remotePort)
35 |
36 | f.RunProcesses([]string{serverConf}, []string{clientConf})
37 |
38 | content := strings.Repeat("a", 50*1024) // 5KB
39 | start := time.Now()
40 | framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
41 | r.Body([]byte(content)).Timeout(30 * time.Second)
42 | }).ExpectResp([]byte(content)).Ensure()
43 | duration := time.Now().Sub(start)
44 |
45 | framework.ExpectTrue(duration.Seconds() > 7, "100Kb with 10KB limit, want > 7 seconds, but got %d seconds", duration.Seconds())
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/pkg/util/metric/counter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package metric
16 |
17 | import (
18 | "sync/atomic"
19 | )
20 |
21 | type Counter interface {
22 | Count() int32
23 | Inc(int32)
24 | Dec(int32)
25 | Snapshot() Counter
26 | Clear()
27 | }
28 |
29 | func NewCounter() Counter {
30 | return &StandardCounter{
31 | count: 0,
32 | }
33 | }
34 |
35 | type StandardCounter struct {
36 | count int32
37 | }
38 |
39 | func (c *StandardCounter) Count() int32 {
40 | return atomic.LoadInt32(&c.count)
41 | }
42 |
43 | func (c *StandardCounter) Inc(count int32) {
44 | atomic.AddInt32(&c.count, count)
45 | }
46 |
47 | func (c *StandardCounter) Dec(count int32) {
48 | atomic.AddInt32(&c.count, -count)
49 | }
50 |
51 | func (c *StandardCounter) Snapshot() Counter {
52 | tmp := &StandardCounter{
53 | count: atomic.LoadInt32(&c.count),
54 | }
55 | return tmp
56 | }
57 |
58 | func (c *StandardCounter) Clear() {
59 | atomic.StoreInt32(&c.count, 0)
60 | }
61 |
--------------------------------------------------------------------------------
/test/e2e/features/monitor.go:
--------------------------------------------------------------------------------
1 | package features
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | "github.com/fatedier/frp/pkg/util/log"
9 | "github.com/fatedier/frp/test/e2e/framework"
10 | "github.com/fatedier/frp/test/e2e/framework/consts"
11 | "github.com/fatedier/frp/test/e2e/pkg/request"
12 |
13 | . "github.com/onsi/ginkgo"
14 | )
15 |
16 | var _ = Describe("[Feature: Monitor]", func() {
17 | f := framework.NewDefaultFramework()
18 |
19 | It("Prometheus metrics", func() {
20 | dashboardPort := f.AllocPort()
21 | serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
22 | enable_prometheus = true
23 | dashboard_addr = 0.0.0.0
24 | dashboard_port = %d
25 | `, dashboardPort)
26 |
27 | clientConf := consts.DefaultClientConfig
28 | remotePort := f.AllocPort()
29 | clientConf += fmt.Sprintf(`
30 | [tcp]
31 | type = tcp
32 | local_port = {{ .%s }}
33 | remote_port = %d
34 | `, framework.TCPEchoServerPort, remotePort)
35 |
36 | f.RunProcesses([]string{serverConf}, []string{clientConf})
37 |
38 | framework.NewRequestExpect(f).Port(remotePort).Ensure()
39 | time.Sleep(500 * time.Millisecond)
40 |
41 | framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
42 | r.HTTP().Port(dashboardPort).HTTPPath("/metrics")
43 | }).Ensure(func(resp *request.Response) bool {
44 | log.Trace("prometheus metrics response: \n%s", resp.Content)
45 | if resp.Code != 200 {
46 | return false
47 | }
48 | if !strings.Contains(string(resp.Content), "traffic_in") {
49 | return false
50 | }
51 | return true
52 | })
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/pkg/util/net/listener.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package net
16 |
17 | import (
18 | "fmt"
19 | "net"
20 | "sync"
21 |
22 | "github.com/fatedier/golib/errors"
23 | )
24 |
25 | // Custom listener
26 | type CustomListener struct {
27 | acceptCh chan net.Conn
28 | closed bool
29 | mu sync.Mutex
30 | }
31 |
32 | func NewCustomListener() *CustomListener {
33 | return &CustomListener{
34 | acceptCh: make(chan net.Conn, 64),
35 | }
36 | }
37 |
38 | func (l *CustomListener) Accept() (net.Conn, error) {
39 | conn, ok := <-l.acceptCh
40 | if !ok {
41 | return nil, fmt.Errorf("listener closed")
42 | }
43 | return conn, nil
44 | }
45 |
46 | func (l *CustomListener) PutConn(conn net.Conn) error {
47 | err := errors.PanicToError(func() {
48 | select {
49 | case l.acceptCh <- conn:
50 | default:
51 | conn.Close()
52 | }
53 | })
54 | return err
55 | }
56 |
57 | func (l *CustomListener) Close() error {
58 | l.mu.Lock()
59 | defer l.mu.Unlock()
60 | if !l.closed {
61 | close(l.acceptCh)
62 | l.closed = true
63 | }
64 | return nil
65 | }
66 |
67 | func (l *CustomListener) Addr() net.Addr {
68 | return (*net.TCPAddr)(nil)
69 | }
70 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/fatedier/frp
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
7 | github.com/coreos/go-oidc v2.2.1+incompatible
8 | github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
9 | github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185
10 | github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
11 | github.com/go-playground/validator/v10 v10.6.1
12 | github.com/google/uuid v1.2.0
13 | github.com/gorilla/mux v1.8.0
14 | github.com/gorilla/websocket v1.4.2
15 | github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c
16 | github.com/klauspost/cpuid v1.2.0 // indirect
17 | github.com/klauspost/reedsolomon v1.9.1 // indirect
18 | github.com/leodido/go-urn v1.2.1 // indirect
19 | github.com/onsi/ginkgo v1.16.4
20 | github.com/onsi/gomega v1.13.0
21 | github.com/pires/go-proxyproto v0.5.0
22 | github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
23 | github.com/prometheus/client_golang v1.11.0
24 | github.com/rodaine/table v1.0.1
25 | github.com/spf13/cobra v1.1.3
26 | github.com/stretchr/testify v1.7.0
27 | github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
28 | github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
29 | github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
30 | github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
31 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
32 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
33 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
34 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
35 | gopkg.in/ini.v1 v1.62.0
36 | gopkg.in/square/go-jose.v2 v2.4.1 // indirect
37 | k8s.io/apimachinery v0.21.2
38 | k8s.io/client-go v0.21.2
39 | )
40 |
--------------------------------------------------------------------------------
/pkg/config/value.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The frp Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | import (
18 | "bytes"
19 | "os"
20 | "strings"
21 | "text/template"
22 | )
23 |
24 | var (
25 | glbEnvs map[string]string
26 | )
27 |
28 | func init() {
29 | glbEnvs = make(map[string]string)
30 | envs := os.Environ()
31 | for _, env := range envs {
32 | kv := strings.Split(env, "=")
33 | if len(kv) != 2 {
34 | continue
35 | }
36 | glbEnvs[kv[0]] = kv[1]
37 | }
38 | }
39 |
40 | type Values struct {
41 | Envs map[string]string // environment vars
42 | }
43 |
44 | func GetValues() *Values {
45 | return &Values{
46 | Envs: glbEnvs,
47 | }
48 | }
49 |
50 | func RenderContent(in []byte) (out []byte, err error) {
51 | tmpl, errRet := template.New("frp").Parse(string(in))
52 | if errRet != nil {
53 | err = errRet
54 | return
55 | }
56 |
57 | buffer := bytes.NewBufferString("")
58 | v := GetValues()
59 | err = tmpl.Execute(buffer, v)
60 | if err != nil {
61 | return
62 | }
63 | out = buffer.Bytes()
64 | return
65 | }
66 |
67 | func GetRenderedConfFromFile(path string) (out []byte, err error) {
68 | var b []byte
69 | b, err = os.ReadFile(path)
70 | if err != nil {
71 | return
72 | }
73 |
74 | out, err = RenderContent(b)
75 | return
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/util/version/version_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package version
16 |
17 | import (
18 | "fmt"
19 | "strconv"
20 | "strings"
21 | "testing"
22 |
23 | "github.com/stretchr/testify/assert"
24 | )
25 |
26 | func TestFull(t *testing.T) {
27 | assert := assert.New(t)
28 | version := Full()
29 | arr := strings.Split(version, ".")
30 | assert.Equal(3, len(arr))
31 |
32 | proto, err := strconv.ParseInt(arr[0], 10, 64)
33 | assert.NoError(err)
34 | assert.True(proto >= 0)
35 |
36 | major, err := strconv.ParseInt(arr[1], 10, 64)
37 | assert.NoError(err)
38 | assert.True(major >= 0)
39 |
40 | minor, err := strconv.ParseInt(arr[2], 10, 64)
41 | assert.NoError(err)
42 | assert.True(minor >= 0)
43 | }
44 |
45 | func TestVersion(t *testing.T) {
46 | assert := assert.New(t)
47 | proto := Proto(Full())
48 | major := Major(Full())
49 | minor := Minor(Full())
50 | parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
51 | version := Full()
52 | assert.Equal(parseVerion, version)
53 | }
54 |
55 | func TestCompact(t *testing.T) {
56 | assert := assert.New(t)
57 | ok, _ := Compat("0.9.0")
58 | assert.False(ok)
59 |
60 | ok, _ = Compat("10.0.0")
61 | assert.True(ok)
62 |
63 | ok, _ = Compat("0.10.0")
64 | assert.False(ok)
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/plugin/server/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "github.com/fatedier/frp/pkg/msg"
19 | )
20 |
21 | type Request struct {
22 | Version string `json:"version"`
23 | Op string `json:"op"`
24 | Content interface{} `json:"content"`
25 | }
26 |
27 | type Response struct {
28 | Reject bool `json:"reject"`
29 | RejectReason string `json:"reject_reason"`
30 | Unchange bool `json:"unchange"`
31 | Content interface{} `json:"content"`
32 | }
33 |
34 | type LoginContent struct {
35 | msg.Login
36 | }
37 |
38 | type UserInfo struct {
39 | User string `json:"user"`
40 | Metas map[string]string `json:"metas"`
41 | RunID string `json:"run_id"`
42 | }
43 |
44 | type NewProxyContent struct {
45 | User UserInfo `json:"user"`
46 | msg.NewProxy
47 | }
48 |
49 | type PingContent struct {
50 | User UserInfo `json:"user"`
51 | msg.Ping
52 | }
53 |
54 | type NewWorkConnContent struct {
55 | User UserInfo `json:"user"`
56 | msg.NewWorkConn
57 | }
58 |
59 | type NewUserConnContent struct {
60 | User UserInfo `json:"user"`
61 | ProxyName string `json:"proxy_name"`
62 | ProxyType string `json:"proxy_type"`
63 | RemoteAddr string `json:"remote_addr"`
64 | }
65 |
--------------------------------------------------------------------------------
/package.sh:
--------------------------------------------------------------------------------
1 | # compile for version
2 | make
3 | if [ $? -ne 0 ]; then
4 | echo "make error"
5 | exit 1
6 | fi
7 |
8 | frp_version=`./bin/frps --version`
9 | echo "build version: $frp_version"
10 |
11 | # cross_compiles
12 | make -f ./Makefile.cross-compiles
13 |
14 | rm -rf ./release/packages
15 | mkdir -p ./release/packages
16 |
17 | os_all='linux windows darwin freebsd'
18 | arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
19 |
20 | cd ./release
21 |
22 | for os in $os_all; do
23 | for arch in $arch_all; do
24 | frp_dir_name="frp_${frp_version}_${os}_${arch}"
25 | frp_path="./packages/frp_${frp_version}_${os}_${arch}"
26 |
27 | if [ "x${os}" = x"windows" ]; then
28 | if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
29 | continue
30 | fi
31 | if [ ! -f "./frps_${os}_${arch}.exe" ]; then
32 | continue
33 | fi
34 | mkdir ${frp_path}
35 | mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
36 | mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
37 | else
38 | if [ ! -f "./frpc_${os}_${arch}" ]; then
39 | continue
40 | fi
41 | if [ ! -f "./frps_${os}_${arch}" ]; then
42 | continue
43 | fi
44 | mkdir ${frp_path}
45 | mv ./frpc_${os}_${arch} ${frp_path}/frpc
46 | mv ./frps_${os}_${arch} ${frp_path}/frps
47 | fi
48 | cp ../LICENSE ${frp_path}
49 | cp -rf ../conf/* ${frp_path}
50 |
51 | # packages
52 | cd ./packages
53 | if [ "x${os}" = x"windows" ]; then
54 | zip -rq ${frp_dir_name}.zip ${frp_dir_name}
55 | else
56 | tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
57 | fi
58 | cd ..
59 | rm -rf ${frp_path}
60 | done
61 | done
62 |
63 | cd -
64 |
--------------------------------------------------------------------------------
/test/e2e/framework/test_context.go:
--------------------------------------------------------------------------------
1 | package framework
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/onsi/ginkgo/config"
9 | )
10 |
11 | type TestContextType struct {
12 | FRPClientPath string
13 | FRPServerPath string
14 | LogLevel string
15 | Debug bool
16 | }
17 |
18 | var TestContext TestContextType
19 |
20 | // RegisterCommonFlags registers flags common to all e2e test suites.
21 | // The flag set can be flag.CommandLine (if desired) or a custom
22 | // flag set that then gets passed to viperconfig.ViperizeFlags.
23 | //
24 | // The other Register*Flags methods below can be used to add more
25 | // test-specific flags. However, those settings then get added
26 | // regardless whether the test is actually in the test suite.
27 | //
28 | func RegisterCommonFlags(flags *flag.FlagSet) {
29 | // Turn on EmitSpecProgress to get spec progress (especially on interrupt)
30 | config.GinkgoConfig.EmitSpecProgress = true
31 |
32 | // Randomize specs as well as suites
33 | config.GinkgoConfig.RandomizeAllSpecs = true
34 |
35 | flags.StringVar(&TestContext.FRPClientPath, "frpc-path", "../../bin/frpc", "The frp client binary to use.")
36 | flags.StringVar(&TestContext.FRPServerPath, "frps-path", "../../bin/frps", "The frp server binary to use.")
37 | flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.")
38 | flags.BoolVar(&TestContext.Debug, "debug", false, "Enable debug mode to print detail info.")
39 | }
40 |
41 | func ValidateTestContext(t *TestContextType) error {
42 | if t.FRPClientPath == "" || t.FRPServerPath == "" {
43 | return fmt.Errorf("frpc and frps binary path can't be empty")
44 | }
45 | if _, err := os.Stat(t.FRPClientPath); err != nil {
46 | return fmt.Errorf("load frpc-path error: %v", err)
47 | }
48 | if _, err := os.Stat(t.FRPServerPath); err != nil {
49 | return fmt.Errorf("load frps-path error: %v", err)
50 | }
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/test/e2e/framework/cleanup.go:
--------------------------------------------------------------------------------
1 | package framework
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // CleanupActionHandle is an integer pointer type for handling cleanup action
8 | type CleanupActionHandle *int
9 | type cleanupFuncHandle struct {
10 | actionHandle CleanupActionHandle
11 | actionHook func()
12 | }
13 |
14 | var cleanupActionsLock sync.Mutex
15 | var cleanupHookList = []cleanupFuncHandle{}
16 |
17 | // AddCleanupAction installs a function that will be called in the event of the
18 | // whole test being terminated. This allows arbitrary pieces of the overall
19 | // test to hook into SynchronizedAfterSuite().
20 | // The hooks are called in last-in-first-out order.
21 | func AddCleanupAction(fn func()) CleanupActionHandle {
22 | p := CleanupActionHandle(new(int))
23 | cleanupActionsLock.Lock()
24 | defer cleanupActionsLock.Unlock()
25 | c := cleanupFuncHandle{actionHandle: p, actionHook: fn}
26 | cleanupHookList = append([]cleanupFuncHandle{c}, cleanupHookList...)
27 | return p
28 | }
29 |
30 | // RemoveCleanupAction removes a function that was installed by
31 | // AddCleanupAction.
32 | func RemoveCleanupAction(p CleanupActionHandle) {
33 | cleanupActionsLock.Lock()
34 | defer cleanupActionsLock.Unlock()
35 | for i, item := range cleanupHookList {
36 | if item.actionHandle == p {
37 | cleanupHookList = append(cleanupHookList[:i], cleanupHookList[i+1:]...)
38 | break
39 | }
40 | }
41 | }
42 |
43 | // RunCleanupActions runs all functions installed by AddCleanupAction. It does
44 | // not remove them (see RemoveCleanupAction) but it does run unlocked, so they
45 | // may remove themselves.
46 | func RunCleanupActions() {
47 | list := []func(){}
48 | func() {
49 | cleanupActionsLock.Lock()
50 | defer cleanupActionsLock.Unlock()
51 | for _, p := range cleanupHookList {
52 | list = append(list, p.actionHook)
53 | }
54 | }()
55 | // Run unlocked.
56 | for _, fn := range list {
57 | fn()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/e2e/pkg/port/util.go:
--------------------------------------------------------------------------------
1 | package port
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | const (
10 | NameDelimiter = "_"
11 | )
12 |
13 | type NameOption func(*nameBuilder) *nameBuilder
14 |
15 | type nameBuilder struct {
16 | name string
17 | rangePortFrom int
18 | rangePortTo int
19 | }
20 |
21 | func unmarshalFromName(name string) (*nameBuilder, error) {
22 | var builder nameBuilder
23 | arrs := strings.Split(name, NameDelimiter)
24 | switch len(arrs) {
25 | case 2:
26 | builder.name = arrs[1]
27 | case 4:
28 | builder.name = arrs[1]
29 | if fromPort, err := strconv.Atoi(arrs[2]); err != nil {
30 | return nil, fmt.Errorf("error range port from")
31 | } else {
32 | builder.rangePortFrom = fromPort
33 | }
34 | if toPort, err := strconv.Atoi(arrs[3]); err != nil {
35 | return nil, fmt.Errorf("error range port to")
36 | } else {
37 | builder.rangePortTo = toPort
38 | }
39 | default:
40 | return nil, fmt.Errorf("error port name format")
41 | }
42 | return &builder, nil
43 | }
44 |
45 | func (builder *nameBuilder) String() string {
46 | name := fmt.Sprintf("Port%s%s", NameDelimiter, builder.name)
47 | if builder.rangePortFrom > 0 && builder.rangePortTo > 0 && builder.rangePortTo > builder.rangePortFrom {
48 | name += fmt.Sprintf("%s%d%s%d", NameDelimiter, builder.rangePortFrom, NameDelimiter, builder.rangePortTo)
49 | }
50 | return name
51 | }
52 |
53 | func WithRangePorts(from, to int) NameOption {
54 | return func(builder *nameBuilder) *nameBuilder {
55 | builder.rangePortFrom = from
56 | builder.rangePortTo = to
57 | return builder
58 | }
59 | }
60 |
61 | func GenName(name string, options ...NameOption) string {
62 | name = strings.ReplaceAll(name, "-", "")
63 | name = strings.ReplaceAll(name, "_", "")
64 | builder := &nameBuilder{name: name}
65 | for _, option := range options {
66 | builder = option(builder)
67 | }
68 | return builder.String()
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/plugin/client/socks5.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "io"
19 | "log"
20 | "net"
21 |
22 | frpNet "github.com/fatedier/frp/pkg/util/net"
23 |
24 | gosocks5 "github.com/armon/go-socks5"
25 | )
26 |
27 | const PluginSocks5 = "socks5"
28 |
29 | func init() {
30 | Register(PluginSocks5, NewSocks5Plugin)
31 | }
32 |
33 | type Socks5Plugin struct {
34 | Server *gosocks5.Server
35 |
36 | user string
37 | passwd string
38 | }
39 |
40 | func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
41 | user := params["plugin_user"]
42 | passwd := params["plugin_passwd"]
43 |
44 | cfg := &gosocks5.Config{
45 | Logger: log.New(io.Discard, "", log.LstdFlags),
46 | }
47 | if user != "" || passwd != "" {
48 | cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
49 | }
50 | sp := &Socks5Plugin{}
51 | sp.Server, err = gosocks5.New(cfg)
52 | p = sp
53 | return
54 | }
55 |
56 | func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
57 | defer conn.Close()
58 | wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
59 | sp.Server.ServeConn(wrapConn)
60 | }
61 |
62 | func (sp *Socks5Plugin) Name() string {
63 | return PluginSocks5
64 | }
65 |
66 | func (sp *Socks5Plugin) Close() error {
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/util/net/tls.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package net
16 |
17 | import (
18 | "crypto/tls"
19 | "fmt"
20 | "net"
21 | "time"
22 |
23 | gnet "github.com/fatedier/golib/net"
24 | )
25 |
26 | var (
27 | FRPTLSHeadByte = 0x17
28 | )
29 |
30 | func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (out net.Conn) {
31 | if !disableCustomTLSHeadByte {
32 | c.Write([]byte{byte(FRPTLSHeadByte)})
33 | }
34 | out = tls.Client(c, tlsConfig)
35 | return
36 | }
37 |
38 | func CheckAndEnableTLSServerConnWithTimeout(
39 | c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
40 | ) (out net.Conn, isTLS bool, custom bool, err error) {
41 |
42 | sc, r := gnet.NewSharedConnSize(c, 2)
43 | buf := make([]byte, 1)
44 | var n int
45 | c.SetReadDeadline(time.Now().Add(timeout))
46 | n, err = r.Read(buf)
47 | c.SetReadDeadline(time.Time{})
48 | if err != nil {
49 | return
50 | }
51 |
52 | if n == 1 && int(buf[0]) == FRPTLSHeadByte {
53 | out = tls.Server(c, tlsConfig)
54 | isTLS = true
55 | custom = true
56 | } else if n == 1 && int(buf[0]) == 0x16 {
57 | out = tls.Server(sc, tlsConfig)
58 | isTLS = true
59 | } else {
60 | if tlsOnly {
61 | err = fmt.Errorf("non-TLS connection received on a TlsOnly server")
62 | return
63 | }
64 | out = sc
65 | }
66 | return
67 | }
68 |
--------------------------------------------------------------------------------
/test/e2e/features/chaos.go:
--------------------------------------------------------------------------------
1 | package features
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/fatedier/frp/test/e2e/framework"
8 |
9 | . "github.com/onsi/ginkgo"
10 | )
11 |
12 | var _ = Describe("[Feature: Chaos]", func() {
13 | f := framework.NewDefaultFramework()
14 |
15 | It("reconnect after frps restart", func() {
16 | serverPort := f.AllocPort()
17 | serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
18 | [common]
19 | bind_addr = 0.0.0.0
20 | bind_port = %d
21 | `, serverPort))
22 |
23 | remotePort := f.AllocPort()
24 | clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
25 | [common]
26 | server_port = %d
27 | log_level = trace
28 |
29 | [tcp]
30 | type = tcp
31 | local_port = %d
32 | remote_port = %d
33 | `, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort))
34 |
35 | // 1. start frps and frpc, expect request success
36 | ps, _, err := f.RunFrps("-c", serverConfigPath)
37 | framework.ExpectNoError(err)
38 |
39 | pc, _, err := f.RunFrpc("-c", clientConfigPath)
40 | framework.ExpectNoError(err)
41 | framework.NewRequestExpect(f).Port(remotePort).Ensure()
42 |
43 | // 2. stop frps, expect request failed
44 | ps.Stop()
45 | time.Sleep(200 * time.Millisecond)
46 | framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
47 |
48 | // 3. restart frps, expect request success
49 | _, _, err = f.RunFrps("-c", serverConfigPath)
50 | framework.ExpectNoError(err)
51 | time.Sleep(2 * time.Second)
52 | framework.NewRequestExpect(f).Port(remotePort).Ensure()
53 |
54 | // 4. stop frpc, expect request failed
55 | pc.Stop()
56 | time.Sleep(200 * time.Millisecond)
57 | framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
58 |
59 | // 5. restart frpc, expect request success
60 | _, _, err = f.RunFrpc("-c", clientConfigPath)
61 | framework.ExpectNoError(err)
62 | time.Sleep(time.Second)
63 | framework.NewRequestExpect(f).Port(remotePort).Ensure()
64 | })
65 | })
66 |
--------------------------------------------------------------------------------
/test/e2e/pkg/cert/generator.go:
--------------------------------------------------------------------------------
1 | package cert
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "encoding/pem"
7 | "time"
8 | )
9 |
10 | // Artifacts hosts a private key, its corresponding serving certificate and
11 | // the CA certificate that signs the serving certificate.
12 | type Artifacts struct {
13 | // PEM encoded private key
14 | Key []byte
15 | // PEM encoded serving certificate
16 | Cert []byte
17 | // PEM encoded CA private key
18 | CAKey []byte
19 | // PEM encoded CA certificate
20 | CACert []byte
21 | // Resource version of the certs
22 | ResourceVersion string
23 | }
24 |
25 | // CertGenerator is an interface to provision the serving certificate.
26 | type CertGenerator interface {
27 | // Generate returns a Artifacts struct.
28 | Generate(CommonName string) (*Artifacts, error)
29 | // SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
30 | SetCA(caKey, caCert []byte)
31 | }
32 |
33 | // ValidCACert think cert and key are valid if they meet the following requirements:
34 | // - key and cert are valid pair
35 | // - caCert is the root ca of cert
36 | // - cert is for dnsName
37 | // - cert won't expire before time
38 | func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool {
39 | if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 {
40 | return false
41 | }
42 | // Verify key and cert are valid pair
43 | _, err := tls.X509KeyPair(cert, key)
44 | if err != nil {
45 | return false
46 | }
47 |
48 | // Verify cert is valid for at least 1 year.
49 | pool := x509.NewCertPool()
50 | if !pool.AppendCertsFromPEM(caCert) {
51 | return false
52 | }
53 | block, _ := pem.Decode(cert)
54 | if block == nil {
55 | return false
56 | }
57 | c, err := x509.ParseCertificate(block.Bytes)
58 | if err != nil {
59 | return false
60 | }
61 | ops := x509.VerifyOptions{
62 | DNSName: dnsName,
63 | Roots: pool,
64 | CurrentTime: time,
65 | }
66 | _, err = c.Verify(ops)
67 | return err == nil
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/plugin/client/unix_domain_socket.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "fmt"
19 | "io"
20 | "net"
21 |
22 | frpIo "github.com/fatedier/golib/io"
23 | )
24 |
25 | const PluginUnixDomainSocket = "unix_domain_socket"
26 |
27 | func init() {
28 | Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
29 | }
30 |
31 | type UnixDomainSocketPlugin struct {
32 | UnixAddr *net.UnixAddr
33 | }
34 |
35 | func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
36 | unixPath, ok := params["plugin_unix_path"]
37 | if !ok {
38 | err = fmt.Errorf("plugin_unix_path not found")
39 | return
40 | }
41 |
42 | unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath)
43 | if errRet != nil {
44 | err = errRet
45 | return
46 | }
47 |
48 | p = &UnixDomainSocketPlugin{
49 | UnixAddr: unixAddr,
50 | }
51 | return
52 | }
53 |
54 | func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
55 | localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
56 | if err != nil {
57 | return
58 | }
59 | if len(extraBufToLocal) > 0 {
60 | localConn.Write(extraBufToLocal)
61 | }
62 |
63 | frpIo.Join(localConn, conn)
64 | }
65 |
66 | func (uds *UnixDomainSocketPlugin) Name() string {
67 | return PluginUnixDomainSocket
68 | }
69 |
70 | func (uds *UnixDomainSocketPlugin) Close() error {
71 | return nil
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/util/tcpmux/httpconnect.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 guylewin, guy@lewin.co.il
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package tcpmux
16 |
17 | import (
18 | "bufio"
19 | "fmt"
20 | "io"
21 | "net"
22 | "net/http"
23 | "time"
24 |
25 | "github.com/fatedier/frp/pkg/util/util"
26 | "github.com/fatedier/frp/pkg/util/vhost"
27 | )
28 |
29 | type HTTPConnectTCPMuxer struct {
30 | *vhost.Muxer
31 | }
32 |
33 | func NewHTTPConnectTCPMuxer(listener net.Listener, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
34 | mux, err := vhost.NewMuxer(listener, getHostFromHTTPConnect, nil, sendHTTPOk, nil, timeout)
35 | return &HTTPConnectTCPMuxer{mux}, err
36 | }
37 |
38 | func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
39 | bufioReader := bufio.NewReader(rd)
40 |
41 | req, err := http.ReadRequest(bufioReader)
42 | if err != nil {
43 | return
44 | }
45 |
46 | if req.Method != "CONNECT" {
47 | err = fmt.Errorf("connections to tcp vhost must be of method CONNECT")
48 | return
49 | }
50 |
51 | host = util.GetHostFromAddr(req.Host)
52 | return
53 | }
54 |
55 | func sendHTTPOk(c net.Conn) error {
56 | return util.OkResponse().Write(c)
57 | }
58 |
59 | func getHostFromHTTPConnect(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
60 | reqInfoMap := make(map[string]string, 0)
61 | host, err := readHTTPConnectRequest(c)
62 | if err != nil {
63 | return nil, reqInfoMap, err
64 | }
65 | reqInfoMap["Host"] = host
66 | reqInfoMap["Scheme"] = "tcp"
67 | return c, reqInfoMap, nil
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/util/xlog/xlog.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package xlog
16 |
17 | import (
18 | "github.com/fatedier/frp/pkg/util/log"
19 | )
20 |
21 | // Logger is not thread safety for operations on prefix
22 | type Logger struct {
23 | prefixes []string
24 |
25 | prefixString string
26 | }
27 |
28 | func New() *Logger {
29 | return &Logger{
30 | prefixes: make([]string, 0),
31 | }
32 | }
33 |
34 | func (l *Logger) ResetPrefixes() (old []string) {
35 | old = l.prefixes
36 | l.prefixes = make([]string, 0)
37 | l.prefixString = ""
38 | return
39 | }
40 |
41 | func (l *Logger) AppendPrefix(prefix string) *Logger {
42 | l.prefixes = append(l.prefixes, prefix)
43 | l.prefixString += "[" + prefix + "] "
44 | return l
45 | }
46 |
47 | func (l *Logger) Spawn() *Logger {
48 | nl := New()
49 | for _, v := range l.prefixes {
50 | nl.AppendPrefix(v)
51 | }
52 | return nl
53 | }
54 |
55 | func (l *Logger) Error(format string, v ...interface{}) {
56 | log.Log.Error(l.prefixString+format, v...)
57 | }
58 |
59 | func (l *Logger) Warn(format string, v ...interface{}) {
60 | log.Log.Warn(l.prefixString+format, v...)
61 | }
62 |
63 | func (l *Logger) Info(format string, v ...interface{}) {
64 | log.Log.Info(l.prefixString+format, v...)
65 | }
66 |
67 | func (l *Logger) Debug(format string, v ...interface{}) {
68 | log.Log.Debug(l.prefixString+format, v...)
69 | }
70 |
71 | func (l *Logger) Trace(format string, v ...interface{}) {
72 | log.Log.Trace(l.prefixString+format, v...)
73 | }
74 |
--------------------------------------------------------------------------------
/server/controller/resource.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package controller
16 |
17 | import (
18 | "github.com/fatedier/frp/pkg/nathole"
19 | plugin "github.com/fatedier/frp/pkg/plugin/server"
20 | "github.com/fatedier/frp/pkg/util/tcpmux"
21 | "github.com/fatedier/frp/pkg/util/vhost"
22 | "github.com/fatedier/frp/server/group"
23 | "github.com/fatedier/frp/server/ports"
24 | "github.com/fatedier/frp/server/visitor"
25 | )
26 |
27 | // All resource managers and controllers
28 | type ResourceController struct {
29 | // Manage all visitor listeners
30 | VisitorManager *visitor.Manager
31 |
32 | // TCP Group Controller
33 | TCPGroupCtl *group.TCPGroupCtl
34 |
35 | // HTTP Group Controller
36 | HTTPGroupCtl *group.HTTPGroupController
37 |
38 | // TCP Mux Group Controller
39 | TCPMuxGroupCtl *group.TCPMuxGroupCtl
40 |
41 | // Manage all TCP ports
42 | TCPPortManager *ports.Manager
43 |
44 | // Manage all UDP ports
45 | UDPPortManager *ports.Manager
46 |
47 | // For HTTP proxies, forwarding HTTP requests
48 | HTTPReverseProxy *vhost.HTTPReverseProxy
49 |
50 | // For HTTPS proxies, route requests to different clients by hostname and other information
51 | VhostHTTPSMuxer *vhost.HTTPSMuxer
52 |
53 | // Controller for nat hole connections
54 | NatHoleController *nathole.Controller
55 |
56 | // TCPMux HTTP CONNECT multiplexer
57 | TCPMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxer
58 |
59 | // All server manager plugin
60 | PluginManager *plugin.Manager
61 | }
62 |
--------------------------------------------------------------------------------
/web/frpc/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 | Overview
13 | Configure
14 | Help
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
40 |
41 |
74 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | # frp
2 |
3 | [](https://travis-ci.org/fatedier/frp)
4 | [](https://github.com/fatedier/frp/releases)
5 |
6 | [README](README.md) | [中文文档](README_zh.md)
7 |
8 | frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
9 |
10 | ## 为什么使用 frp ?
11 |
12 | 通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
13 |
14 | * 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。
15 | * 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。
16 | * 代理组间的负载均衡。
17 | * 端口复用,多个服务通过同一个服务端端口暴露。
18 | * 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
19 | * 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。
20 | * 服务端和客户端 UI 页面。
21 |
22 | ## 开发状态
23 |
24 | frp 目前已被很多公司广泛用于测试、生产环境。
25 |
26 | master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
27 |
28 | 我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
29 |
30 | 现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
31 |
32 | ## 文档
33 |
34 | 完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
35 |
36 | ## 为 frp 做贡献
37 |
38 | frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。
39 |
40 | * 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。
41 | * Bug 的修复可以直接提交 Pull Request 到 dev 分支。
42 | * 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
43 | * 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。
44 | * 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
45 | * 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。
46 |
47 | **提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
48 |
49 | ## 捐助
50 |
51 | 如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
52 |
53 | ### 知识星球
54 |
55 | 如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
56 |
57 | 
58 |
59 | ### 支付宝扫码捐赠
60 |
61 | 
62 |
63 | ### 微信支付捐赠
64 |
65 | 
66 |
67 | ### Paypal 捐赠
68 |
69 | 海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
70 |
--------------------------------------------------------------------------------
/pkg/util/version/version.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package version
16 |
17 | import (
18 | "strconv"
19 | "strings"
20 | )
21 |
22 | var version string = "0.38.1"
23 |
24 | func Full() string {
25 | return version
26 | }
27 |
28 | func getSubVersion(v string, position int) int64 {
29 | arr := strings.Split(v, ".")
30 | if len(arr) < 3 {
31 | return 0
32 | }
33 | res, _ := strconv.ParseInt(arr[position], 10, 64)
34 | return res
35 | }
36 |
37 | func Proto(v string) int64 {
38 | return getSubVersion(v, 0)
39 | }
40 |
41 | func Major(v string) int64 {
42 | return getSubVersion(v, 1)
43 | }
44 |
45 | func Minor(v string) int64 {
46 | return getSubVersion(v, 2)
47 | }
48 |
49 | // add every case there if server will not accept client's protocol and return false
50 | func Compat(client string) (ok bool, msg string) {
51 | if LessThan(client, "0.18.0") {
52 | return false, "Please upgrade your frpc version to at least 0.18.0"
53 | }
54 | return true, ""
55 | }
56 |
57 | func LessThan(client string, server string) bool {
58 | vc := Proto(client)
59 | vs := Proto(server)
60 | if vc > vs {
61 | return false
62 | } else if vc < vs {
63 | return true
64 | }
65 |
66 | vc = Major(client)
67 | vs = Major(server)
68 | if vc > vs {
69 | return false
70 | } else if vc < vs {
71 | return true
72 | }
73 |
74 | vc = Minor(client)
75 | vs = Minor(server)
76 | if vc > vs {
77 | return false
78 | } else if vc < vs {
79 | return true
80 | }
81 | return false
82 | }
83 |
--------------------------------------------------------------------------------
/pkg/util/util/http.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 guylewin, guy@lewin.co.il
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package util
16 |
17 | import (
18 | "net"
19 | "net/http"
20 | "strings"
21 | )
22 |
23 | func OkResponse() *http.Response {
24 | header := make(http.Header)
25 |
26 | res := &http.Response{
27 | Status: "OK",
28 | StatusCode: 200,
29 | Proto: "HTTP/1.1",
30 | ProtoMajor: 1,
31 | ProtoMinor: 1,
32 | Header: header,
33 | }
34 | return res
35 | }
36 |
37 | // TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
38 | func GetHostFromAddr(addr string) (host string) {
39 | strs := strings.Split(addr, ":")
40 | if len(strs) > 1 {
41 | host = strs[0]
42 | } else {
43 | host = addr
44 | }
45 | return
46 | }
47 |
48 | // canonicalHost strips port from host if present and returns the canonicalized
49 | // host name.
50 | func CanonicalHost(host string) (string, error) {
51 | var err error
52 | host = strings.ToLower(host)
53 | if hasPort(host) {
54 | host, _, err = net.SplitHostPort(host)
55 | if err != nil {
56 | return "", err
57 | }
58 | }
59 | if strings.HasSuffix(host, ".") {
60 | // Strip trailing dot from fully qualified domain names.
61 | host = host[:len(host)-1]
62 | }
63 | return host, nil
64 | }
65 |
66 | // hasPort reports whether host contains a port number. host may be a host
67 | // name, an IPv4 or an IPv6 address.
68 | func hasPort(host string) bool {
69 | colons := strings.Count(host, ":")
70 | if colons == 0 {
71 | return false
72 | }
73 | if colons == 1 {
74 | return true
75 | }
76 | return host[0] == '[' && strings.Contains(host, "]:")
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/metrics/mem/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package mem
16 |
17 | import (
18 | "time"
19 |
20 | "github.com/fatedier/frp/pkg/util/metric"
21 | )
22 |
23 | const (
24 | ReserveDays = 7
25 | )
26 |
27 | type ServerStats struct {
28 | TotalTrafficIn int64
29 | TotalTrafficOut int64
30 | CurConns int64
31 | ClientCounts int64
32 | ProxyTypeCounts map[string]int64
33 | }
34 |
35 | type ProxyStats struct {
36 | Name string
37 | Type string
38 | TodayTrafficIn int64
39 | TodayTrafficOut int64
40 | LastStartTime string
41 | LastCloseTime string
42 | CurConns int64
43 | }
44 |
45 | type ProxyTrafficInfo struct {
46 | Name string
47 | TrafficIn []int64
48 | TrafficOut []int64
49 | }
50 |
51 | type ProxyStatistics struct {
52 | Name string
53 | ProxyType string
54 | TrafficIn metric.DateCounter
55 | TrafficOut metric.DateCounter
56 | CurConns metric.Counter
57 | LastStartTime time.Time
58 | LastCloseTime time.Time
59 | }
60 |
61 | type ServerStatistics struct {
62 | TotalTrafficIn metric.DateCounter
63 | TotalTrafficOut metric.DateCounter
64 | CurConns metric.Counter
65 |
66 | // counter for clients
67 | ClientCounts metric.Counter
68 |
69 | // counter for proxy types
70 | ProxyTypeCounts map[string]metric.Counter
71 |
72 | // statistics for different proxies
73 | // key is proxy name
74 | ProxyStatistics map[string]*ProxyStatistics
75 | }
76 |
77 | type Collector interface {
78 | GetServer() *ServerStats
79 | GetProxiesByType(proxyType string) []*ProxyStats
80 | GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
81 | GetProxyTraffic(name string) *ProxyTrafficInfo
82 | }
83 |
--------------------------------------------------------------------------------
/cmd/frpc/sub/reload.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package sub
16 |
17 | import (
18 | "encoding/base64"
19 | "fmt"
20 | "io"
21 | "net/http"
22 | "os"
23 | "strings"
24 |
25 | "github.com/fatedier/frp/pkg/config"
26 |
27 | "github.com/spf13/cobra"
28 | )
29 |
30 | func init() {
31 | rootCmd.AddCommand(reloadCmd)
32 | }
33 |
34 | var reloadCmd = &cobra.Command{
35 | Use: "reload",
36 | Short: "Hot-Reload frpc configuration",
37 | RunE: func(cmd *cobra.Command, args []string) error {
38 | cfg, _, _, err := config.ParseClientConfig(cfgFile)
39 | if err != nil {
40 | fmt.Println(err)
41 | os.Exit(1)
42 | }
43 |
44 | err = reload(cfg)
45 | if err != nil {
46 | fmt.Printf("frpc reload error: %v\n", err)
47 | os.Exit(1)
48 | }
49 | fmt.Printf("reload success\n")
50 | return nil
51 | },
52 | }
53 |
54 | func reload(clientCfg config.ClientCommonConf) error {
55 | if clientCfg.AdminPort == 0 {
56 | return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
57 | }
58 |
59 | req, err := http.NewRequest("GET", "http://"+
60 | clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
61 | if err != nil {
62 | return err
63 | }
64 |
65 | authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
66 | clientCfg.AdminPwd))
67 |
68 | req.Header.Add("Authorization", authStr)
69 | resp, err := http.DefaultClient.Do(req)
70 | if err != nil {
71 | return err
72 | }
73 | defer resp.Body.Close()
74 |
75 | if resp.StatusCode == 200 {
76 | return nil
77 | }
78 |
79 | body, err := io.ReadAll(resp.Body)
80 | if err != nil {
81 | return err
82 | }
83 | return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
84 | }
85 |
--------------------------------------------------------------------------------
/test/e2e/mock/server/httpserver/server.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "net"
7 | "net/http"
8 | "strconv"
9 | )
10 |
11 | type Server struct {
12 | bindAddr string
13 | bindPort int
14 | handler http.Handler
15 |
16 | l net.Listener
17 | tlsConfig *tls.Config
18 | hs *http.Server
19 | }
20 |
21 | type Option func(*Server) *Server
22 |
23 | func New(options ...Option) *Server {
24 | s := &Server{
25 | bindAddr: "127.0.0.1",
26 | }
27 |
28 | for _, option := range options {
29 | s = option(s)
30 | }
31 | return s
32 | }
33 |
34 | func WithBindAddr(addr string) Option {
35 | return func(s *Server) *Server {
36 | s.bindAddr = addr
37 | return s
38 | }
39 | }
40 |
41 | func WithBindPort(port int) Option {
42 | return func(s *Server) *Server {
43 | s.bindPort = port
44 | return s
45 | }
46 | }
47 |
48 | func WithTlsConfig(tlsConfig *tls.Config) Option {
49 | return func(s *Server) *Server {
50 | s.tlsConfig = tlsConfig
51 | return s
52 | }
53 | }
54 |
55 | func WithHandler(h http.Handler) Option {
56 | return func(s *Server) *Server {
57 | s.handler = h
58 | return s
59 | }
60 | }
61 |
62 | func WithResponse(resp []byte) Option {
63 | return func(s *Server) *Server {
64 | s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65 | w.Write(resp)
66 | })
67 | return s
68 | }
69 | }
70 |
71 | func (s *Server) Run() error {
72 | if err := s.initListener(); err != nil {
73 | return err
74 | }
75 |
76 | addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))
77 | hs := &http.Server{
78 | Addr: addr,
79 | Handler: s.handler,
80 | TLSConfig: s.tlsConfig,
81 | }
82 |
83 | s.hs = hs
84 | if s.tlsConfig == nil {
85 | go hs.Serve(s.l)
86 | } else {
87 | go hs.ServeTLS(s.l, "", "")
88 | }
89 | return nil
90 | }
91 |
92 | func (s *Server) Close() error {
93 | if s.hs != nil {
94 | return s.hs.Close()
95 | }
96 | return nil
97 | }
98 |
99 | func (s *Server) initListener() (err error) {
100 | s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
101 | return
102 | }
103 |
104 | func (s *Server) BindAddr() string {
105 | return s.bindAddr
106 | }
107 |
108 | func (s *Server) BindPort() int {
109 | return s.bindPort
110 | }
111 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Report a bug to help us improve frp
3 |
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for taking the time to fill out this bug report!
9 | - type: textarea
10 | id: bug-description
11 | attributes:
12 | label: Bug Description
13 | description: Tell us what issues you ran into
14 | placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
15 | validations:
16 | required: true
17 | - type: input
18 | id: frpc-version
19 | attributes:
20 | label: frpc Version
21 | description: Include the output of `frpc -v`
22 | validations:
23 | required: true
24 | - type: input
25 | id: frps-version
26 | attributes:
27 | label: frps Version
28 | description: Include the output of `frps -v`
29 | validations:
30 | required: true
31 | - type: input
32 | id: system-architecture
33 | attributes:
34 | label: System Architecture
35 | description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
36 | validations:
37 | required: true
38 | - type: textarea
39 | id: config
40 | attributes:
41 | label: Configurations
42 | description: Include what configurrations you used and ran into this problem
43 | placeholder: Pay attention to hiding the token and password in your output
44 | validations:
45 | required: true
46 | - type: textarea
47 | id: log
48 | attributes:
49 | label: Logs
50 | description: Prefer you providing releated error logs here
51 | placeholder: Pay attention to hiding your personal informations
52 | - type: textarea
53 | id: steps-to-reproduce
54 | attributes:
55 | label: Steps to reproduce
56 | description: How to reproduce it? It's important for us to find the bug
57 | value: |
58 | 1.
59 | 2.
60 | 3.
61 | ...
62 | - type: checkboxes
63 | id: area
64 | attributes:
65 | label: Affected area
66 | options:
67 | - label: "Docs"
68 | - label: "Installation"
69 | - label: "Performance and Scalability"
70 | - label: "Security"
71 | - label: "User Experience"
72 | - label: "Test and Release"
73 | - label: "Developer Infrastructure"
74 | - label: "Client Plugin"
75 | - label: "Server Plugin"
76 | - label: "Extensions"
77 | - label: "Others"
78 |
--------------------------------------------------------------------------------
/test/e2e/framework/ginkgowrapper/wrapper.go:
--------------------------------------------------------------------------------
1 | // Package ginkgowrapper wraps Ginkgo Fail and Skip functions to panic
2 | // with structured data instead of a constant string.
3 | package ginkgowrapper
4 |
5 | import (
6 | "bufio"
7 | "bytes"
8 | "regexp"
9 | "runtime"
10 | "runtime/debug"
11 | "strings"
12 |
13 | "github.com/onsi/ginkgo"
14 | )
15 |
16 | // FailurePanic is the value that will be panicked from Fail.
17 | type FailurePanic struct {
18 | Message string // The failure message passed to Fail
19 | Filename string // The filename that is the source of the failure
20 | Line int // The line number of the filename that is the source of the failure
21 | FullStackTrace string // A full stack trace starting at the source of the failure
22 | }
23 |
24 | // String makes FailurePanic look like the old Ginkgo panic when printed.
25 | func (FailurePanic) String() string { return ginkgo.GINKGO_PANIC }
26 |
27 | // Fail wraps ginkgo.Fail so that it panics with more useful
28 | // information about the failure. This function will panic with a
29 | // FailurePanic.
30 | func Fail(message string, callerSkip ...int) {
31 | skip := 1
32 | if len(callerSkip) > 0 {
33 | skip += callerSkip[0]
34 | }
35 |
36 | _, file, line, _ := runtime.Caller(skip)
37 | fp := FailurePanic{
38 | Message: message,
39 | Filename: file,
40 | Line: line,
41 | FullStackTrace: pruneStack(skip),
42 | }
43 |
44 | defer func() {
45 | e := recover()
46 | if e != nil {
47 | panic(fp)
48 | }
49 | }()
50 |
51 | ginkgo.Fail(message, skip)
52 | }
53 |
54 | // ginkgo adds a lot of test running infrastructure to the stack, so
55 | // we filter those out
56 | var stackSkipPattern = regexp.MustCompile(`onsi/ginkgo`)
57 |
58 | func pruneStack(skip int) string {
59 | skip += 2 // one for pruneStack and one for debug.Stack
60 | stack := debug.Stack()
61 | scanner := bufio.NewScanner(bytes.NewBuffer(stack))
62 | var prunedStack []string
63 |
64 | // skip the top of the stack
65 | for i := 0; i < 2*skip+1; i++ {
66 | scanner.Scan()
67 | }
68 |
69 | for scanner.Scan() {
70 | if stackSkipPattern.Match(scanner.Bytes()) {
71 | scanner.Scan() // these come in pairs
72 | } else {
73 | prunedStack = append(prunedStack, scanner.Text())
74 | scanner.Scan() // these come in pairs
75 | prunedStack = append(prunedStack, scanner.Text())
76 | }
77 | }
78 |
79 | return strings.Join(prunedStack, "\n")
80 | }
81 |
--------------------------------------------------------------------------------
/test/e2e/basic/config.go:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/fatedier/frp/test/e2e/framework"
7 | "github.com/fatedier/frp/test/e2e/framework/consts"
8 | "github.com/fatedier/frp/test/e2e/pkg/port"
9 |
10 | . "github.com/onsi/ginkgo"
11 | )
12 |
13 | var _ = Describe("[Feature: Config]", func() {
14 | f := framework.NewDefaultFramework()
15 |
16 | Describe("Template", func() {
17 | It("render by env", func() {
18 | serverConf := consts.DefaultServerConfig
19 | clientConf := consts.DefaultClientConfig
20 |
21 | portName := port.GenName("TCP")
22 | serverConf += fmt.Sprintf(`
23 | token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
24 | `, "`", "`")
25 |
26 | clientConf += fmt.Sprintf(`
27 | token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
28 |
29 | [tcp]
30 | type = tcp
31 | local_port = {{ .%s }}
32 | remote_port = {{ .%s }}
33 | `, "`", "`", framework.TCPEchoServerPort, portName)
34 |
35 | f.SetEnvs([]string{"FRP_TOKEN=123"})
36 | f.RunProcesses([]string{serverConf}, []string{clientConf})
37 |
38 | framework.NewRequestExpect(f).PortName(portName).Ensure()
39 | })
40 | })
41 |
42 | Describe("Includes", func() {
43 | It("split tcp proxies into different files", func() {
44 | serverPort := f.AllocPort()
45 | serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
46 | [common]
47 | bind_addr = 0.0.0.0
48 | bind_port = %d
49 | `, serverPort))
50 |
51 | remotePort := f.AllocPort()
52 | proxyConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
53 | [tcp]
54 | type = tcp
55 | local_port = %d
56 | remote_port = %d
57 | `, f.PortByName(framework.TCPEchoServerPort), remotePort))
58 |
59 | remotePort2 := f.AllocPort()
60 | proxyConfigPath2 := f.GenerateConfigFile(fmt.Sprintf(`
61 | [tcp2]
62 | type = tcp
63 | local_port = %d
64 | remote_port = %d
65 | `, f.PortByName(framework.TCPEchoServerPort), remotePort2))
66 |
67 | clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
68 | [common]
69 | server_port = %d
70 | includes = %s,%s
71 | `, serverPort, proxyConfigPath, proxyConfigPath2))
72 |
73 | _, _, err := f.RunFrps("-c", serverConfigPath)
74 | framework.ExpectNoError(err)
75 |
76 | _, _, err = f.RunFrpc("-c", clientConfigPath)
77 | framework.ExpectNoError(err)
78 |
79 | framework.NewRequestExpect(f).Port(remotePort).Ensure()
80 | framework.NewRequestExpect(f).Port(remotePort2).Ensure()
81 | })
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/server/proxy/tcp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proxy
16 |
17 | import (
18 | "fmt"
19 | "net"
20 |
21 | "github.com/fatedier/frp/pkg/config"
22 | )
23 |
24 | type TCPProxy struct {
25 | *BaseProxy
26 | cfg *config.TCPProxyConf
27 |
28 | realPort int
29 | }
30 |
31 | func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
32 | xl := pxy.xl
33 | if pxy.cfg.Group != "" {
34 | l, realPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
35 | if errRet != nil {
36 | err = errRet
37 | return
38 | }
39 | defer func() {
40 | if err != nil {
41 | l.Close()
42 | }
43 | }()
44 | pxy.realPort = realPort
45 | pxy.listeners = append(pxy.listeners, l)
46 | xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
47 | } else {
48 | pxy.realPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
49 | if err != nil {
50 | return
51 | }
52 | defer func() {
53 | if err != nil {
54 | pxy.rc.TCPPortManager.Release(pxy.realPort)
55 | }
56 | }()
57 | listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
58 | if errRet != nil {
59 | err = errRet
60 | return
61 | }
62 | pxy.listeners = append(pxy.listeners, listener)
63 | xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
64 | }
65 |
66 | pxy.cfg.RemotePort = pxy.realPort
67 | remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
68 | pxy.startListenHandler(pxy, HandleUserTCPConnection)
69 | return
70 | }
71 |
72 | func (pxy *TCPProxy) GetConf() config.ProxyConf {
73 | return pxy.cfg
74 | }
75 |
76 | func (pxy *TCPProxy) Close() {
77 | pxy.BaseProxy.Close()
78 | if pxy.cfg.Group == "" {
79 | pxy.rc.TCPPortManager.Release(pxy.realPort)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/pkg/plugin/client/plugin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "fmt"
19 | "io"
20 | "net"
21 | "sync"
22 |
23 | "github.com/fatedier/golib/errors"
24 | )
25 |
26 | // Creators is used for create plugins to handle connections.
27 | var creators = make(map[string]CreatorFn)
28 |
29 | // params has prefix "plugin_"
30 | type CreatorFn func(params map[string]string) (Plugin, error)
31 |
32 | func Register(name string, fn CreatorFn) {
33 | creators[name] = fn
34 | }
35 |
36 | func Create(name string, params map[string]string) (p Plugin, err error) {
37 | if fn, ok := creators[name]; ok {
38 | p, err = fn(params)
39 | } else {
40 | err = fmt.Errorf("plugin [%s] is not registered", name)
41 | }
42 | return
43 | }
44 |
45 | type Plugin interface {
46 | Name() string
47 |
48 | // extraBufToLocal will send to local connection first, then join conn with local connection
49 | Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte)
50 | Close() error
51 | }
52 |
53 | type Listener struct {
54 | conns chan net.Conn
55 | closed bool
56 | mu sync.Mutex
57 | }
58 |
59 | func NewProxyListener() *Listener {
60 | return &Listener{
61 | conns: make(chan net.Conn, 64),
62 | }
63 | }
64 |
65 | func (l *Listener) Accept() (net.Conn, error) {
66 | conn, ok := <-l.conns
67 | if !ok {
68 | return nil, fmt.Errorf("listener closed")
69 | }
70 | return conn, nil
71 | }
72 |
73 | func (l *Listener) PutConn(conn net.Conn) error {
74 | err := errors.PanicToError(func() {
75 | l.conns <- conn
76 | })
77 | return err
78 | }
79 |
80 | func (l *Listener) Close() error {
81 | l.mu.Lock()
82 | defer l.mu.Unlock()
83 | if !l.closed {
84 | close(l.conns)
85 | l.closed = true
86 | }
87 | return nil
88 | }
89 |
90 | func (l *Listener) Addr() net.Addr {
91 | return (*net.TCPAddr)(nil)
92 | }
93 |
--------------------------------------------------------------------------------
/client/admin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package client
16 |
17 | import (
18 | "net"
19 | "net/http"
20 | "time"
21 |
22 | "github.com/fatedier/frp/assets"
23 | frpNet "github.com/fatedier/frp/pkg/util/net"
24 |
25 | "github.com/gorilla/mux"
26 | )
27 |
28 | var (
29 | httpServerReadTimeout = 10 * time.Second
30 | httpServerWriteTimeout = 10 * time.Second
31 | )
32 |
33 | func (svr *Service) RunAdminServer(address string) (err error) {
34 | // url router
35 | router := mux.NewRouter()
36 |
37 | router.HandleFunc("/healthz", svr.healthz)
38 |
39 | subRouter := router.NewRoute().Subrouter()
40 | user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
41 | subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
42 |
43 | // api, see admin_api.go
44 | subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
45 | subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
46 | subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
47 | subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
48 |
49 | // view
50 | subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
51 | subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
52 | subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
53 | http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
54 | })
55 |
56 | server := &http.Server{
57 | Addr: address,
58 | Handler: router,
59 | ReadTimeout: httpServerReadTimeout,
60 | WriteTimeout: httpServerWriteTimeout,
61 | }
62 | if address == "" {
63 | address = ":http"
64 | }
65 | ln, err := net.Listen("tcp", address)
66 | if err != nil {
67 | return err
68 | }
69 |
70 | go server.Serve(ln)
71 | return
72 | }
73 |
--------------------------------------------------------------------------------
/server/proxy/https.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proxy
16 |
17 | import (
18 | "strings"
19 |
20 | "github.com/fatedier/frp/pkg/config"
21 | "github.com/fatedier/frp/pkg/util/util"
22 | "github.com/fatedier/frp/pkg/util/vhost"
23 | )
24 |
25 | type HTTPSProxy struct {
26 | *BaseProxy
27 | cfg *config.HTTPSProxyConf
28 | }
29 |
30 | func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
31 | xl := pxy.xl
32 | routeConfig := &vhost.RouteConfig{}
33 |
34 | defer func() {
35 | if err != nil {
36 | pxy.Close()
37 | }
38 | }()
39 | addrs := make([]string, 0)
40 | for _, domain := range pxy.cfg.CustomDomains {
41 | if domain == "" {
42 | continue
43 | }
44 |
45 | routeConfig.Domain = domain
46 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig)
47 | if errRet != nil {
48 | err = errRet
49 | return
50 | }
51 | xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
52 | pxy.listeners = append(pxy.listeners, l)
53 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort))
54 | }
55 |
56 | if pxy.cfg.SubDomain != "" {
57 | routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
58 | l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig)
59 | if errRet != nil {
60 | err = errRet
61 | return
62 | }
63 | xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
64 | pxy.listeners = append(pxy.listeners, l)
65 | addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHTTPSPort)))
66 | }
67 |
68 | pxy.startListenHandler(pxy, HandleUserTCPConnection)
69 | remoteAddr = strings.Join(addrs, ",")
70 | return
71 | }
72 |
73 | func (pxy *HTTPSProxy) GetConf() config.ProxyConf {
74 | return pxy.cfg
75 | }
76 |
77 | func (pxy *HTTPSProxy) Close() {
78 | pxy.BaseProxy.Close()
79 | }
80 |
--------------------------------------------------------------------------------
/cmd/frpc/sub/tcp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package sub
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/spf13/cobra"
22 |
23 | "github.com/fatedier/frp/pkg/config"
24 | "github.com/fatedier/frp/pkg/consts"
25 | )
26 |
27 | func init() {
28 | RegisterCommonFlags(tcpCmd)
29 |
30 | tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
31 | tcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
32 | tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
33 | tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
34 | tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
35 | tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
36 |
37 | rootCmd.AddCommand(tcpCmd)
38 | }
39 |
40 | var tcpCmd = &cobra.Command{
41 | Use: "tcp",
42 | Short: "Run frpc with a single tcp proxy",
43 | RunE: func(cmd *cobra.Command, args []string) error {
44 | clientCfg, err := parseClientCommonCfgFromCmd()
45 | if err != nil {
46 | fmt.Println(err)
47 | os.Exit(1)
48 | }
49 |
50 | cfg := &config.TCPProxyConf{}
51 | var prefix string
52 | if user != "" {
53 | prefix = user + "."
54 | }
55 | cfg.ProxyName = prefix + proxyName
56 | cfg.ProxyType = consts.TCPProxy
57 | cfg.LocalIP = localIP
58 | cfg.LocalPort = localPort
59 | cfg.RemotePort = remotePort
60 | cfg.UseEncryption = useEncryption
61 | cfg.UseCompression = useCompression
62 |
63 | err = cfg.CheckForCli()
64 | if err != nil {
65 | fmt.Println(err)
66 | os.Exit(1)
67 | }
68 |
69 | proxyConfs := map[string]config.ProxyConf{
70 | cfg.ProxyName: cfg,
71 | }
72 | err = startService(clientCfg, proxyConfs, nil, "")
73 | if err != nil {
74 | fmt.Println(err)
75 | os.Exit(1)
76 | }
77 | return nil
78 | },
79 | }
80 |
--------------------------------------------------------------------------------
/cmd/frpc/sub/udp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package sub
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/fatedier/frp/pkg/config"
22 | "github.com/fatedier/frp/pkg/consts"
23 |
24 | "github.com/spf13/cobra"
25 | )
26 |
27 | func init() {
28 | RegisterCommonFlags(udpCmd)
29 |
30 | udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
31 | udpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
32 | udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
33 | udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
34 | udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
35 | udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
36 |
37 | rootCmd.AddCommand(udpCmd)
38 | }
39 |
40 | var udpCmd = &cobra.Command{
41 | Use: "udp",
42 | Short: "Run frpc with a single udp proxy",
43 | RunE: func(cmd *cobra.Command, args []string) error {
44 | clientCfg, err := parseClientCommonCfgFromCmd()
45 | if err != nil {
46 | fmt.Println(err)
47 | os.Exit(1)
48 | }
49 |
50 | cfg := &config.UDPProxyConf{}
51 | var prefix string
52 | if user != "" {
53 | prefix = user + "."
54 | }
55 | cfg.ProxyName = prefix + proxyName
56 | cfg.ProxyType = consts.UDPProxy
57 | cfg.LocalIP = localIP
58 | cfg.LocalPort = localPort
59 | cfg.RemotePort = remotePort
60 | cfg.UseEncryption = useEncryption
61 | cfg.UseCompression = useCompression
62 |
63 | err = cfg.CheckForCli()
64 | if err != nil {
65 | fmt.Println(err)
66 | os.Exit(1)
67 | }
68 |
69 | proxyConfs := map[string]config.ProxyConf{
70 | cfg.ProxyName: cfg,
71 | }
72 | err = startService(clientCfg, proxyConfs, nil, "")
73 | if err != nil {
74 | fmt.Println(err)
75 | os.Exit(1)
76 | }
77 | return nil
78 | },
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/util/net/websocket.go:
--------------------------------------------------------------------------------
1 | package net
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net"
7 | "net/http"
8 | "net/url"
9 | "time"
10 |
11 | "golang.org/x/net/websocket"
12 | )
13 |
14 | var (
15 | ErrWebsocketListenerClosed = errors.New("websocket listener closed")
16 | )
17 |
18 | const (
19 | FrpWebsocketPath = "/~!frp"
20 | )
21 |
22 | type WebsocketListener struct {
23 | ln net.Listener
24 | acceptCh chan net.Conn
25 |
26 | server *http.Server
27 | httpMutex *http.ServeMux
28 | }
29 |
30 | // NewWebsocketListener to handle websocket connections
31 | // ln: tcp listener for websocket connections
32 | func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
33 | wl = &WebsocketListener{
34 | acceptCh: make(chan net.Conn),
35 | }
36 |
37 | muxer := http.NewServeMux()
38 | muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) {
39 | notifyCh := make(chan struct{})
40 | conn := WrapCloseNotifyConn(c, func() {
41 | close(notifyCh)
42 | })
43 | wl.acceptCh <- conn
44 | <-notifyCh
45 | }))
46 |
47 | wl.server = &http.Server{
48 | Addr: ln.Addr().String(),
49 | Handler: muxer,
50 | }
51 |
52 | go wl.server.Serve(ln)
53 | return
54 | }
55 |
56 | func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
57 | tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
58 | if err != nil {
59 | return nil, err
60 | }
61 | l := NewWebsocketListener(tcpLn)
62 | return l, nil
63 | }
64 |
65 | func (p *WebsocketListener) Accept() (net.Conn, error) {
66 | c, ok := <-p.acceptCh
67 | if !ok {
68 | return nil, ErrWebsocketListenerClosed
69 | }
70 | return c, nil
71 | }
72 |
73 | func (p *WebsocketListener) Close() error {
74 | return p.server.Close()
75 | }
76 |
77 | func (p *WebsocketListener) Addr() net.Addr {
78 | return p.ln.Addr()
79 | }
80 |
81 | // addr: domain:port
82 | func ConnectWebsocketServer(addr string) (net.Conn, error) {
83 | addr = "ws://" + addr + FrpWebsocketPath
84 | uri, err := url.Parse(addr)
85 | if err != nil {
86 | return nil, err
87 | }
88 |
89 | origin := "http://" + uri.Host
90 | cfg, err := websocket.NewConfig(addr, origin)
91 | if err != nil {
92 | return nil, err
93 | }
94 | cfg.Dialer = &net.Dialer{
95 | Timeout: 10 * time.Second,
96 | }
97 |
98 | conn, err := websocket.DialConfig(cfg)
99 | if err != nil {
100 | return nil, err
101 | }
102 | return conn, nil
103 | }
104 |
--------------------------------------------------------------------------------
/test/e2e/e2e.go:
--------------------------------------------------------------------------------
1 | package e2e
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/fatedier/frp/pkg/util/log"
7 | "github.com/fatedier/frp/test/e2e/framework"
8 |
9 | "github.com/onsi/ginkgo"
10 | "github.com/onsi/ginkgo/config"
11 | "github.com/onsi/gomega"
12 | )
13 |
14 | var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
15 | setupSuite()
16 | return nil
17 | }, func(data []byte) {
18 | // Run on all Ginkgo nodes
19 | setupSuitePerGinkgoNode()
20 | })
21 |
22 | var _ = ginkgo.SynchronizedAfterSuite(func() {
23 | CleanupSuite()
24 | }, func() {
25 | AfterSuiteActions()
26 | })
27 |
28 | // RunE2ETests checks configuration parameters (specified through flags) and then runs
29 | // E2E tests using the Ginkgo runner.
30 | // If a "report directory" is specified, one or more JUnit test reports will be
31 | // generated in this directory, and cluster logs will also be saved.
32 | // This function is called on each Ginkgo node in parallel mode.
33 | func RunE2ETests(t *testing.T) {
34 | gomega.RegisterFailHandler(framework.Fail)
35 |
36 | log.Info("Starting e2e run %q on Ginkgo node %d of total %d",
37 | framework.RunID, config.GinkgoConfig.ParallelNode, config.GinkgoConfig.ParallelTotal)
38 | ginkgo.RunSpecs(t, "frp e2e suite")
39 | }
40 |
41 | // setupSuite is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.
42 | // There are certain operations we only want to run once per overall test invocation
43 | // (such as deleting old namespaces, or verifying that all system pods are running.
44 | // Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite
45 | // to ensure that these operations only run on the first parallel Ginkgo node.
46 | //
47 | // This function takes two parameters: one function which runs on only the first Ginkgo node,
48 | // returning an opaque byte array, and then a second function which runs on all Ginkgo nodes,
49 | // accepting the byte array.
50 | func setupSuite() {
51 | // Run only on Ginkgo node 1
52 | }
53 |
54 | // setupSuitePerGinkgoNode is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.
55 | // There are certain operations we only want to run once per overall test invocation on each Ginkgo node
56 | // such as making some global variables accessible to all parallel executions
57 | // Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite
58 | // Ref: https://onsi.github.io/ginkgo/#parallel-specs
59 | func setupSuitePerGinkgoNode() {
60 | // config.GinkgoConfig.ParallelNode
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/plugin/client/static_file.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "io"
19 | "net"
20 | "net/http"
21 |
22 | frpNet "github.com/fatedier/frp/pkg/util/net"
23 |
24 | "github.com/gorilla/mux"
25 | )
26 |
27 | const PluginStaticFile = "static_file"
28 |
29 | func init() {
30 | Register(PluginStaticFile, NewStaticFilePlugin)
31 | }
32 |
33 | type StaticFilePlugin struct {
34 | localPath string
35 | stripPrefix string
36 | httpUser string
37 | httpPasswd string
38 |
39 | l *Listener
40 | s *http.Server
41 | }
42 |
43 | func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
44 | localPath := params["plugin_local_path"]
45 | stripPrefix := params["plugin_strip_prefix"]
46 | httpUser := params["plugin_http_user"]
47 | httpPasswd := params["plugin_http_passwd"]
48 |
49 | listener := NewProxyListener()
50 |
51 | sp := &StaticFilePlugin{
52 | localPath: localPath,
53 | stripPrefix: stripPrefix,
54 | httpUser: httpUser,
55 | httpPasswd: httpPasswd,
56 |
57 | l: listener,
58 | }
59 | var prefix string
60 | if stripPrefix != "" {
61 | prefix = "/" + stripPrefix + "/"
62 | } else {
63 | prefix = "/"
64 | }
65 |
66 | router := mux.NewRouter()
67 | router.Use(frpNet.NewHTTPAuthMiddleware(httpUser, httpPasswd).Middleware)
68 | router.PathPrefix(prefix).Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
69 | sp.s = &http.Server{
70 | Handler: router,
71 | }
72 | go sp.s.Serve(listener)
73 | return sp, nil
74 | }
75 |
76 | func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
77 | wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
78 | sp.l.PutConn(wrapConn)
79 | }
80 |
81 | func (sp *StaticFilePlugin) Name() string {
82 | return PluginStaticFile
83 | }
84 |
85 | func (sp *StaticFilePlugin) Close() error {
86 | sp.s.Close()
87 | sp.l.Close()
88 | return nil
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/util/log/log.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package log
16 |
17 | import (
18 | "fmt"
19 |
20 | "github.com/fatedier/beego/logs"
21 | )
22 |
23 | // Log is the under log object
24 | var Log *logs.BeeLogger
25 |
26 | func init() {
27 | Log = logs.NewLogger(200)
28 | Log.EnableFuncCallDepth(true)
29 | Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
30 | }
31 |
32 | func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
33 | SetLogFile(logWay, logFile, maxdays, disableLogColor)
34 | SetLogLevel(logLevel)
35 | }
36 |
37 | // SetLogFile to configure log params
38 | // logWay: file or console
39 | func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
40 | if logWay == "console" {
41 | params := ""
42 | if disableLogColor {
43 | params = fmt.Sprintf(`{"color": false}`)
44 | }
45 | Log.SetLogger("console", params)
46 | } else {
47 | params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
48 | Log.SetLogger("file", params)
49 | }
50 | }
51 |
52 | // SetLogLevel set log level, default is warning
53 | // value: error, warning, info, debug, trace
54 | func SetLogLevel(logLevel string) {
55 | level := 4 // warning
56 | switch logLevel {
57 | case "error":
58 | level = 3
59 | case "warn":
60 | level = 4
61 | case "info":
62 | level = 6
63 | case "debug":
64 | level = 7
65 | case "trace":
66 | level = 8
67 | default:
68 | level = 4
69 | }
70 | Log.SetLevel(level)
71 | }
72 |
73 | // wrap log
74 |
75 | func Error(format string, v ...interface{}) {
76 | Log.Error(format, v...)
77 | }
78 |
79 | func Warn(format string, v ...interface{}) {
80 | Log.Warn(format, v...)
81 | }
82 |
83 | func Info(format string, v ...interface{}) {
84 | Log.Info(format, v...)
85 | }
86 |
87 | func Debug(format string, v ...interface{}) {
88 | Log.Debug(format, v...)
89 | }
90 |
91 | func Trace(format string, v ...interface{}) {
92 | Log.Trace(format, v...)
93 | }
94 |
--------------------------------------------------------------------------------
/test/e2e/pkg/port/port.go:
--------------------------------------------------------------------------------
1 | package port
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "sync"
7 |
8 | "k8s.io/apimachinery/pkg/util/sets"
9 | )
10 |
11 | type Allocator struct {
12 | reserved sets.Int
13 | used sets.Int
14 | mu sync.Mutex
15 | }
16 |
17 | // NewAllocator return a port allocator for testing.
18 | // Example: from: 10, to: 20, mod 4, index 1
19 | // Reserved ports: 13, 17
20 | func NewAllocator(from int, to int, mod int, index int) *Allocator {
21 | pa := &Allocator{
22 | reserved: sets.NewInt(),
23 | used: sets.NewInt(),
24 | }
25 |
26 | for i := from; i <= to; i++ {
27 | if i%mod == index {
28 | pa.reserved.Insert(i)
29 | }
30 | }
31 | return pa
32 | }
33 |
34 | func (pa *Allocator) Get() int {
35 | return pa.GetByName("")
36 | }
37 |
38 | func (pa *Allocator) GetByName(portName string) int {
39 | var builder *nameBuilder
40 | if portName == "" {
41 | builder = &nameBuilder{}
42 | } else {
43 | var err error
44 | builder, err = unmarshalFromName(portName)
45 | if err != nil {
46 | fmt.Println(err, portName)
47 | return 0
48 | }
49 | }
50 |
51 | pa.mu.Lock()
52 | defer pa.mu.Unlock()
53 |
54 | for i := 0; i < 20; i++ {
55 | port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo)
56 | if port == 0 {
57 | return 0
58 | }
59 |
60 | l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
61 | if err != nil {
62 | // Maybe not controlled by us, mark it used.
63 | pa.used.Insert(port)
64 | continue
65 | }
66 | l.Close()
67 |
68 | udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port))
69 | if err != nil {
70 | continue
71 | }
72 | udpConn, err := net.ListenUDP("udp", udpAddr)
73 | if err != nil {
74 | // Maybe not controlled by us, mark it used.
75 | pa.used.Insert(port)
76 | continue
77 | }
78 | udpConn.Close()
79 |
80 | pa.used.Insert(port)
81 | return port
82 | }
83 | return 0
84 | }
85 |
86 | func (pa *Allocator) getByRange(from, to int) int {
87 | if from <= 0 {
88 | port, _ := pa.reserved.PopAny()
89 | return port
90 | }
91 |
92 | // choose a random port between from - to
93 | ports := pa.reserved.UnsortedList()
94 | for _, port := range ports {
95 | if port >= from && port <= to {
96 | return port
97 | }
98 | }
99 | return 0
100 | }
101 |
102 | func (pa *Allocator) Release(port int) {
103 | if port <= 0 {
104 | return
105 | }
106 |
107 | pa.mu.Lock()
108 | defer pa.mu.Unlock()
109 |
110 | if pa.used.Has(port) {
111 | pa.used.Delete(port)
112 | pa.reserved.Insert(port)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/pkg/util/vhost/router.go:
--------------------------------------------------------------------------------
1 | package vhost
2 |
3 | import (
4 | "errors"
5 | "sort"
6 | "strings"
7 | "sync"
8 | )
9 |
10 | var (
11 | ErrRouterConfigConflict = errors.New("router config conflict")
12 | )
13 |
14 | type Routers struct {
15 | RouterByDomain map[string][]*Router
16 | mutex sync.RWMutex
17 | }
18 |
19 | type Router struct {
20 | domain string
21 | location string
22 |
23 | payload interface{}
24 | }
25 |
26 | func NewRouters() *Routers {
27 | return &Routers{
28 | RouterByDomain: make(map[string][]*Router),
29 | }
30 | }
31 |
32 | func (r *Routers) Add(domain, location string, payload interface{}) error {
33 | r.mutex.Lock()
34 | defer r.mutex.Unlock()
35 |
36 | if _, exist := r.exist(domain, location); exist {
37 | return ErrRouterConfigConflict
38 | }
39 |
40 | vrs, found := r.RouterByDomain[domain]
41 | if !found {
42 | vrs = make([]*Router, 0, 1)
43 | }
44 |
45 | vr := &Router{
46 | domain: domain,
47 | location: location,
48 | payload: payload,
49 | }
50 | vrs = append(vrs, vr)
51 |
52 | sort.Sort(sort.Reverse(ByLocation(vrs)))
53 | r.RouterByDomain[domain] = vrs
54 | return nil
55 | }
56 |
57 | func (r *Routers) Del(domain, location string) {
58 | r.mutex.Lock()
59 | defer r.mutex.Unlock()
60 |
61 | vrs, found := r.RouterByDomain[domain]
62 | if !found {
63 | return
64 | }
65 | newVrs := make([]*Router, 0)
66 | for _, vr := range vrs {
67 | if vr.location != location {
68 | newVrs = append(newVrs, vr)
69 | }
70 | }
71 | r.RouterByDomain[domain] = newVrs
72 | }
73 |
74 | func (r *Routers) Get(host, path string) (vr *Router, exist bool) {
75 | r.mutex.RLock()
76 | defer r.mutex.RUnlock()
77 |
78 | vrs, found := r.RouterByDomain[host]
79 | if !found {
80 | return
81 | }
82 |
83 | // can't support load balance, will to do
84 | for _, vr = range vrs {
85 | if strings.HasPrefix(path, vr.location) {
86 | return vr, true
87 | }
88 | }
89 |
90 | return
91 | }
92 |
93 | func (r *Routers) exist(host, path string) (vr *Router, exist bool) {
94 | vrs, found := r.RouterByDomain[host]
95 | if !found {
96 | return
97 | }
98 |
99 | for _, vr = range vrs {
100 | if path == vr.location {
101 | return vr, true
102 | }
103 | }
104 |
105 | return
106 | }
107 |
108 | // sort by location
109 | type ByLocation []*Router
110 |
111 | func (a ByLocation) Len() int {
112 | return len(a)
113 | }
114 | func (a ByLocation) Swap(i, j int) {
115 | a[i], a[j] = a[j], a[i]
116 | }
117 | func (a ByLocation) Less(i, j int) bool {
118 | return strings.Compare(a[i].location, a[j].location) < 0
119 | }
120 |
--------------------------------------------------------------------------------
/server/proxy/xtcp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proxy
16 |
17 | import (
18 | "fmt"
19 |
20 | "github.com/fatedier/frp/pkg/config"
21 | "github.com/fatedier/frp/pkg/msg"
22 |
23 | "github.com/fatedier/golib/errors"
24 | )
25 |
26 | type XTCPProxy struct {
27 | *BaseProxy
28 | cfg *config.XTCPProxyConf
29 |
30 | closeCh chan struct{}
31 | }
32 |
33 | func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
34 | xl := pxy.xl
35 |
36 | if pxy.rc.NatHoleController == nil {
37 | xl.Error("udp port for xtcp is not specified.")
38 | err = fmt.Errorf("xtcp is not supported in frps")
39 | return
40 | }
41 | sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
42 | go func() {
43 | for {
44 | select {
45 | case <-pxy.closeCh:
46 | break
47 | case sidRequest := <-sidCh:
48 | sr := sidRequest
49 | workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
50 | if errRet != nil {
51 | continue
52 | }
53 | m := &msg.NatHoleSid{
54 | Sid: sr.Sid,
55 | }
56 | errRet = msg.WriteMsg(workConn, m)
57 | if errRet != nil {
58 | xl.Warn("write nat hole sid package error, %v", errRet)
59 | workConn.Close()
60 | break
61 | }
62 |
63 | go func() {
64 | raw, errRet := msg.ReadMsg(workConn)
65 | if errRet != nil {
66 | xl.Warn("read nat hole client ok package error: %v", errRet)
67 | workConn.Close()
68 | return
69 | }
70 | if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
71 | xl.Warn("read nat hole client ok package format error")
72 | workConn.Close()
73 | return
74 | }
75 |
76 | select {
77 | case sr.NotifyCh <- struct{}{}:
78 | default:
79 | }
80 | }()
81 | }
82 | }
83 | }()
84 | return
85 | }
86 |
87 | func (pxy *XTCPProxy) GetConf() config.ProxyConf {
88 | return pxy.cfg
89 | }
90 |
91 | func (pxy *XTCPProxy) Close() {
92 | pxy.BaseProxy.Close()
93 | pxy.rc.NatHoleController.CloseClient(pxy.GetName())
94 | errors.PanicToError(func() {
95 | close(pxy.closeCh)
96 | })
97 | }
98 |
--------------------------------------------------------------------------------
/pkg/metrics/aggregate/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package aggregate
16 |
17 | import (
18 | "github.com/fatedier/frp/pkg/metrics/mem"
19 | "github.com/fatedier/frp/pkg/metrics/prometheus"
20 | "github.com/fatedier/frp/server/metrics"
21 | )
22 |
23 | // EnableMem start to mark metrics to memory monitor system.
24 | func EnableMem() {
25 | sm.Add(mem.ServerMetrics)
26 | }
27 |
28 | // EnablePrometheus start to mark metrics to prometheus.
29 | func EnablePrometheus() {
30 | sm.Add(prometheus.ServerMetrics)
31 | }
32 |
33 | var sm *serverMetrics = &serverMetrics{}
34 |
35 | func init() {
36 | metrics.Register(sm)
37 | }
38 |
39 | type serverMetrics struct {
40 | ms []metrics.ServerMetrics
41 | }
42 |
43 | func (m *serverMetrics) Add(sm metrics.ServerMetrics) {
44 | m.ms = append(m.ms, sm)
45 | }
46 |
47 | func (m *serverMetrics) NewClient() {
48 | for _, v := range m.ms {
49 | v.NewClient()
50 | }
51 | }
52 |
53 | func (m *serverMetrics) CloseClient() {
54 | for _, v := range m.ms {
55 | v.CloseClient()
56 | }
57 | }
58 |
59 | func (m *serverMetrics) NewProxy(name string, proxyType string) {
60 | for _, v := range m.ms {
61 | v.NewProxy(name, proxyType)
62 | }
63 | }
64 |
65 | func (m *serverMetrics) CloseProxy(name string, proxyType string) {
66 | for _, v := range m.ms {
67 | v.CloseProxy(name, proxyType)
68 | }
69 | }
70 |
71 | func (m *serverMetrics) OpenConnection(name string, proxyType string) {
72 | for _, v := range m.ms {
73 | v.OpenConnection(name, proxyType)
74 | }
75 | }
76 |
77 | func (m *serverMetrics) CloseConnection(name string, proxyType string) {
78 | for _, v := range m.ms {
79 | v.CloseConnection(name, proxyType)
80 | }
81 | }
82 |
83 | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
84 | for _, v := range m.ms {
85 | v.AddTrafficIn(name, proxyType, trafficBytes)
86 | }
87 | }
88 |
89 | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
90 | for _, v := range m.ms {
91 | v.AddTrafficOut(name, proxyType, trafficBytes)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/web/frps/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 | Overview
13 |
14 | Proxies
15 | TCP
16 | UDP
17 | HTTP
18 | HTTPS
19 | STCP
20 | SUDP
21 |
22 | Help
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
48 |
49 |
82 |
--------------------------------------------------------------------------------
/cmd/frpc/sub/https.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package sub
16 |
17 | import (
18 | "fmt"
19 | "os"
20 | "strings"
21 |
22 | "github.com/spf13/cobra"
23 |
24 | "github.com/fatedier/frp/pkg/config"
25 | "github.com/fatedier/frp/pkg/consts"
26 | )
27 |
28 | func init() {
29 | RegisterCommonFlags(httpsCmd)
30 |
31 | httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
32 | httpsCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
33 | httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
34 | httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
35 | httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
36 | httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
37 | httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
38 |
39 | rootCmd.AddCommand(httpsCmd)
40 | }
41 |
42 | var httpsCmd = &cobra.Command{
43 | Use: "https",
44 | Short: "Run frpc with a single https proxy",
45 | RunE: func(cmd *cobra.Command, args []string) error {
46 | clientCfg, err := parseClientCommonCfgFromCmd()
47 | if err != nil {
48 | fmt.Println(err)
49 | os.Exit(1)
50 | }
51 |
52 | cfg := &config.HTTPSProxyConf{}
53 | var prefix string
54 | if user != "" {
55 | prefix = user + "."
56 | }
57 | cfg.ProxyName = prefix + proxyName
58 | cfg.ProxyType = consts.HTTPSProxy
59 | cfg.LocalIP = localIP
60 | cfg.LocalPort = localPort
61 | cfg.CustomDomains = strings.Split(customDomains, ",")
62 | cfg.SubDomain = subDomain
63 | cfg.UseEncryption = useEncryption
64 | cfg.UseCompression = useCompression
65 |
66 | err = cfg.CheckForCli()
67 | if err != nil {
68 | fmt.Println(err)
69 | os.Exit(1)
70 | }
71 |
72 | proxyConfs := map[string]config.ProxyConf{
73 | cfg.ProxyName: cfg,
74 | }
75 | err = startService(clientCfg, proxyConfs, nil, "")
76 | if err != nil {
77 | fmt.Println(err)
78 | os.Exit(1)
79 | }
80 | return nil
81 | },
82 | }
83 |
--------------------------------------------------------------------------------
/server/dashboard.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "net"
19 | "net/http"
20 | "time"
21 |
22 | "github.com/fatedier/frp/assets"
23 | frpNet "github.com/fatedier/frp/pkg/util/net"
24 |
25 | "github.com/gorilla/mux"
26 | "github.com/prometheus/client_golang/prometheus/promhttp"
27 | )
28 |
29 | var (
30 | httpServerReadTimeout = 10 * time.Second
31 | httpServerWriteTimeout = 10 * time.Second
32 | )
33 |
34 | func (svr *Service) RunDashboardServer(address string) (err error) {
35 | // url router
36 | router := mux.NewRouter()
37 | router.HandleFunc("/healthz", svr.Healthz)
38 |
39 | subRouter := router.NewRoute().Subrouter()
40 |
41 | user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
42 | subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
43 |
44 | // metrics
45 | if svr.cfg.EnablePrometheus {
46 | subRouter.Handle("/metrics", promhttp.Handler())
47 | }
48 |
49 | // api, see dashboard_api.go
50 | subRouter.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET")
51 | subRouter.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
52 | subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
53 | subRouter.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
54 |
55 | // view
56 | subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
57 | subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
58 |
59 | subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
60 | http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
61 | })
62 |
63 | server := &http.Server{
64 | Addr: address,
65 | Handler: router,
66 | ReadTimeout: httpServerReadTimeout,
67 | WriteTimeout: httpServerWriteTimeout,
68 | }
69 | if address == "" || address == ":" {
70 | address = ":http"
71 | }
72 | ln, err := net.Listen("tcp", address)
73 | if err != nil {
74 | return err
75 | }
76 |
77 | go server.Serve(ln)
78 | return
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/util/net/kcp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package net
16 |
17 | import (
18 | "fmt"
19 | "net"
20 |
21 | kcp "github.com/fatedier/kcp-go"
22 | )
23 |
24 | type KCPListener struct {
25 | listener net.Listener
26 | acceptCh chan net.Conn
27 | closeFlag bool
28 | }
29 |
30 | func ListenKcp(address string) (l *KCPListener, err error) {
31 | listener, err := kcp.ListenWithOptions(address, nil, 10, 3)
32 | if err != nil {
33 | return l, err
34 | }
35 | listener.SetReadBuffer(4194304)
36 | listener.SetWriteBuffer(4194304)
37 |
38 | l = &KCPListener{
39 | listener: listener,
40 | acceptCh: make(chan net.Conn),
41 | closeFlag: false,
42 | }
43 |
44 | go func() {
45 | for {
46 | conn, err := listener.AcceptKCP()
47 | if err != nil {
48 | if l.closeFlag {
49 | close(l.acceptCh)
50 | return
51 | }
52 | continue
53 | }
54 | conn.SetStreamMode(true)
55 | conn.SetWriteDelay(true)
56 | conn.SetNoDelay(1, 20, 2, 1)
57 | conn.SetMtu(1350)
58 | conn.SetWindowSize(1024, 1024)
59 | conn.SetACKNoDelay(false)
60 |
61 | l.acceptCh <- conn
62 | }
63 | }()
64 | return l, err
65 | }
66 |
67 | func (l *KCPListener) Accept() (net.Conn, error) {
68 | conn, ok := <-l.acceptCh
69 | if !ok {
70 | return conn, fmt.Errorf("channel for kcp listener closed")
71 | }
72 | return conn, nil
73 | }
74 |
75 | func (l *KCPListener) Close() error {
76 | if !l.closeFlag {
77 | l.closeFlag = true
78 | l.listener.Close()
79 | }
80 | return nil
81 | }
82 |
83 | func (l *KCPListener) Addr() net.Addr {
84 | return l.listener.Addr()
85 | }
86 |
87 | func NewKCPConnFromUDP(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
88 | kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
89 | if err != nil {
90 | return nil, err
91 | }
92 | kcpConn.SetStreamMode(true)
93 | kcpConn.SetWriteDelay(true)
94 | kcpConn.SetNoDelay(1, 20, 2, 1)
95 | kcpConn.SetMtu(1350)
96 | kcpConn.SetWindowSize(1024, 1024)
97 | kcpConn.SetACKNoDelay(false)
98 | return kcpConn, nil
99 | }
100 |
--------------------------------------------------------------------------------
/test/e2e/framework/mockservers.go:
--------------------------------------------------------------------------------
1 | package framework
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 |
8 | "github.com/fatedier/frp/test/e2e/framework/consts"
9 | "github.com/fatedier/frp/test/e2e/mock/server"
10 | "github.com/fatedier/frp/test/e2e/mock/server/httpserver"
11 | "github.com/fatedier/frp/test/e2e/mock/server/streamserver"
12 | "github.com/fatedier/frp/test/e2e/pkg/port"
13 | )
14 |
15 | const (
16 | TCPEchoServerPort = "TCPEchoServerPort"
17 | UDPEchoServerPort = "UDPEchoServerPort"
18 | UDSEchoServerAddr = "UDSEchoServerAddr"
19 | HTTPSimpleServerPort = "HTTPSimpleServerPort"
20 | )
21 |
22 | type MockServers struct {
23 | tcpEchoServer server.Server
24 | udpEchoServer server.Server
25 | udsEchoServer server.Server
26 | httpSimpleServer server.Server
27 | }
28 |
29 | func NewMockServers(portAllocator *port.Allocator) *MockServers {
30 | s := &MockServers{}
31 | tcpPort := portAllocator.Get()
32 | udpPort := portAllocator.Get()
33 | httpPort := portAllocator.Get()
34 | s.tcpEchoServer = streamserver.New(streamserver.TCP, streamserver.WithBindPort(tcpPort))
35 | s.udpEchoServer = streamserver.New(streamserver.UDP, streamserver.WithBindPort(udpPort))
36 | s.httpSimpleServer = httpserver.New(httpserver.WithBindPort(httpPort), httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
37 | w.Write([]byte(consts.TestString))
38 | })))
39 |
40 | udsIndex := portAllocator.Get()
41 | udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex)
42 | os.Remove(udsAddr)
43 | s.udsEchoServer = streamserver.New(streamserver.Unix, streamserver.WithBindAddr(udsAddr))
44 | return s
45 | }
46 |
47 | func (m *MockServers) Run() error {
48 | if err := m.tcpEchoServer.Run(); err != nil {
49 | return err
50 | }
51 | if err := m.udpEchoServer.Run(); err != nil {
52 | return err
53 | }
54 | if err := m.udsEchoServer.Run(); err != nil {
55 | return err
56 | }
57 | if err := m.httpSimpleServer.Run(); err != nil {
58 | return err
59 | }
60 | return nil
61 | }
62 |
63 | func (m *MockServers) Close() {
64 | m.tcpEchoServer.Close()
65 | m.udpEchoServer.Close()
66 | m.udsEchoServer.Close()
67 | m.httpSimpleServer.Close()
68 | os.Remove(m.udsEchoServer.BindAddr())
69 | }
70 |
71 | func (m *MockServers) GetTemplateParams() map[string]interface{} {
72 | ret := make(map[string]interface{})
73 | ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
74 | ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
75 | ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
76 | ret[HTTPSimpleServerPort] = m.httpSimpleServer.BindPort()
77 | return ret
78 | }
79 |
80 | func (m *MockServers) GetParam(key string) interface{} {
81 | params := m.GetTemplateParams()
82 | if v, ok := params[key]; ok {
83 | return v
84 | }
85 | return nil
86 | }
87 |
--------------------------------------------------------------------------------
/web/frpc/src/components/Overview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
73 |
74 |
76 |
--------------------------------------------------------------------------------
/cmd/frpc/sub/tcpmux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 guylewin, guy@lewin.co.il
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package sub
16 |
17 | import (
18 | "fmt"
19 | "os"
20 | "strings"
21 |
22 | "github.com/spf13/cobra"
23 |
24 | "github.com/fatedier/frp/pkg/config"
25 | "github.com/fatedier/frp/pkg/consts"
26 | )
27 |
28 | func init() {
29 | RegisterCommonFlags(tcpMuxCmd)
30 |
31 | tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
32 | tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
33 | tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
34 | tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
35 | tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
36 | tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
37 | tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
38 | tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
39 |
40 | rootCmd.AddCommand(tcpMuxCmd)
41 | }
42 |
43 | var tcpMuxCmd = &cobra.Command{
44 | Use: "tcpmux",
45 | Short: "Run frpc with a single tcpmux proxy",
46 | RunE: func(cmd *cobra.Command, args []string) error {
47 | clientCfg, err := parseClientCommonCfgFromCmd()
48 | if err != nil {
49 | fmt.Println(err)
50 | os.Exit(1)
51 | }
52 |
53 | cfg := &config.TCPMuxProxyConf{}
54 | var prefix string
55 | if user != "" {
56 | prefix = user + "."
57 | }
58 | cfg.ProxyName = prefix + proxyName
59 | cfg.ProxyType = consts.TCPMuxProxy
60 | cfg.LocalIP = localIP
61 | cfg.LocalPort = localPort
62 | cfg.CustomDomains = strings.Split(customDomains, ",")
63 | cfg.SubDomain = subDomain
64 | cfg.Multiplexer = multiplexer
65 | cfg.UseEncryption = useEncryption
66 | cfg.UseCompression = useCompression
67 |
68 | err = cfg.CheckForCli()
69 | if err != nil {
70 | fmt.Println(err)
71 | os.Exit(1)
72 | }
73 |
74 | proxyConfs := map[string]config.ProxyConf{
75 | cfg.ProxyName: cfg,
76 | }
77 | err = startService(clientCfg, proxyConfs, nil, "")
78 | if err != nil {
79 | fmt.Println(err)
80 | os.Exit(1)
81 | }
82 | return nil
83 | },
84 | }
85 |
--------------------------------------------------------------------------------
/server/visitor/visitor.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package visitor
16 |
17 | import (
18 | "fmt"
19 | "io"
20 | "net"
21 | "sync"
22 |
23 | frpNet "github.com/fatedier/frp/pkg/util/net"
24 | "github.com/fatedier/frp/pkg/util/util"
25 |
26 | frpIo "github.com/fatedier/golib/io"
27 | )
28 |
29 | // Manager for visitor listeners.
30 | type Manager struct {
31 | visitorListeners map[string]*frpNet.CustomListener
32 | skMap map[string]string
33 |
34 | mu sync.RWMutex
35 | }
36 |
37 | func NewManager() *Manager {
38 | return &Manager{
39 | visitorListeners: make(map[string]*frpNet.CustomListener),
40 | skMap: make(map[string]string),
41 | }
42 | }
43 |
44 | func (vm *Manager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
45 | vm.mu.Lock()
46 | defer vm.mu.Unlock()
47 |
48 | if _, ok := vm.visitorListeners[name]; ok {
49 | err = fmt.Errorf("custom listener for [%s] is repeated", name)
50 | return
51 | }
52 |
53 | l = frpNet.NewCustomListener()
54 | vm.visitorListeners[name] = l
55 | vm.skMap[name] = sk
56 | return
57 | }
58 |
59 | func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
60 | useEncryption bool, useCompression bool) (err error) {
61 |
62 | vm.mu.RLock()
63 | defer vm.mu.RUnlock()
64 |
65 | if l, ok := vm.visitorListeners[name]; ok {
66 | var sk string
67 | if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
68 | err = fmt.Errorf("visitor connection of [%s] auth failed", name)
69 | return
70 | }
71 |
72 | var rwc io.ReadWriteCloser = conn
73 | if useEncryption {
74 | if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil {
75 | err = fmt.Errorf("create encryption connection failed: %v", err)
76 | return
77 | }
78 | }
79 | if useCompression {
80 | rwc = frpIo.WithCompression(rwc)
81 | }
82 | err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn))
83 | } else {
84 | err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
85 | return
86 | }
87 | return
88 | }
89 |
90 | func (vm *Manager) CloseListener(name string) {
91 | vm.mu.Lock()
92 | defer vm.mu.Unlock()
93 |
94 | delete(vm.visitorListeners, name)
95 | delete(vm.skMap, name)
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/util/vhost/resource.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package vhost
16 |
17 | import (
18 | "bytes"
19 | "io"
20 | "net/http"
21 | "os"
22 |
23 | frpLog "github.com/fatedier/frp/pkg/util/log"
24 | "github.com/fatedier/frp/pkg/util/version"
25 | )
26 |
27 | var (
28 | NotFoundPagePath = ""
29 | )
30 |
31 | const (
32 | NotFound = `
33 |
34 |
35 | Not Found
36 |
43 |
44 |
45 | The page you requested was not found.
46 | Sorry, the page you are looking for is currently unavailable.
47 | Please try again later.
48 | The server is powered by frp.
49 | Faithfully yours, frp.
50 |
51 |
52 | `
53 | )
54 |
55 | func getNotFoundPageContent() []byte {
56 | var (
57 | buf []byte
58 | err error
59 | )
60 | if NotFoundPagePath != "" {
61 | buf, err = os.ReadFile(NotFoundPagePath)
62 | if err != nil {
63 | frpLog.Warn("read custom 404 page error: %v", err)
64 | buf = []byte(NotFound)
65 | }
66 | } else {
67 | buf = []byte(NotFound)
68 | }
69 | return buf
70 | }
71 |
72 | func notFoundResponse() *http.Response {
73 | header := make(http.Header)
74 | header.Set("server", "frp/"+version.Full())
75 | header.Set("Content-Type", "text/html")
76 |
77 | res := &http.Response{
78 | Status: "Not Found",
79 | StatusCode: 404,
80 | Proto: "HTTP/1.0",
81 | ProtoMajor: 1,
82 | ProtoMinor: 0,
83 | Header: header,
84 | Body: io.NopCloser(bytes.NewReader(getNotFoundPageContent())),
85 | }
86 | return res
87 | }
88 |
89 | func noAuthResponse() *http.Response {
90 | header := make(map[string][]string)
91 | header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
92 | res := &http.Response{
93 | Status: "401 Not authorized",
94 | StatusCode: 401,
95 | Proto: "HTTP/1.1",
96 | ProtoMajor: 1,
97 | ProtoMinor: 1,
98 | Header: header,
99 | }
100 | return res
101 | }
102 |
--------------------------------------------------------------------------------
/test/e2e/mock/server/streamserver/server.go:
--------------------------------------------------------------------------------
1 | package streamserver
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "net"
8 |
9 | libnet "github.com/fatedier/frp/pkg/util/net"
10 | "github.com/fatedier/frp/test/e2e/pkg/rpc"
11 | )
12 |
13 | type Type string
14 |
15 | const (
16 | TCP Type = "tcp"
17 | UDP Type = "udp"
18 | Unix Type = "unix"
19 | )
20 |
21 | type Server struct {
22 | netType Type
23 | bindAddr string
24 | bindPort int
25 | respContent []byte
26 |
27 | handler func(net.Conn)
28 |
29 | l net.Listener
30 | }
31 |
32 | type Option func(*Server) *Server
33 |
34 | func New(netType Type, options ...Option) *Server {
35 | s := &Server{
36 | netType: netType,
37 | bindAddr: "127.0.0.1",
38 | }
39 | s.handler = s.handle
40 |
41 | for _, option := range options {
42 | s = option(s)
43 | }
44 | return s
45 | }
46 |
47 | func WithBindAddr(addr string) Option {
48 | return func(s *Server) *Server {
49 | s.bindAddr = addr
50 | return s
51 | }
52 | }
53 |
54 | func WithBindPort(port int) Option {
55 | return func(s *Server) *Server {
56 | s.bindPort = port
57 | return s
58 | }
59 | }
60 |
61 | func WithRespContent(content []byte) Option {
62 | return func(s *Server) *Server {
63 | s.respContent = content
64 | return s
65 | }
66 | }
67 |
68 | func WithCustomHandler(handler func(net.Conn)) Option {
69 | return func(s *Server) *Server {
70 | s.handler = handler
71 | return s
72 | }
73 | }
74 |
75 | func (s *Server) Run() error {
76 | if err := s.initListener(); err != nil {
77 | return err
78 | }
79 |
80 | go func() {
81 | for {
82 | c, err := s.l.Accept()
83 | if err != nil {
84 | return
85 | }
86 | go s.handler(c)
87 | }
88 | }()
89 | return nil
90 | }
91 |
92 | func (s *Server) Close() error {
93 | if s.l != nil {
94 | return s.l.Close()
95 | }
96 | return nil
97 | }
98 |
99 | func (s *Server) initListener() (err error) {
100 | switch s.netType {
101 | case TCP:
102 | s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
103 | case UDP:
104 | s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
105 | case Unix:
106 | s.l, err = net.Listen("unix", s.bindAddr)
107 | default:
108 | return fmt.Errorf("unknown server type: %s", s.netType)
109 | }
110 | return err
111 | }
112 |
113 | func (s *Server) handle(c net.Conn) {
114 | defer c.Close()
115 |
116 | var reader io.Reader = c
117 | if s.netType == UDP {
118 | reader = bufio.NewReader(c)
119 | }
120 | for {
121 | buf, err := rpc.ReadBytes(reader)
122 | if err != nil {
123 | return
124 | }
125 |
126 | if len(s.respContent) > 0 {
127 | buf = s.respContent
128 | }
129 | rpc.WriteBytes(c, buf)
130 | }
131 | }
132 |
133 | func (s *Server) BindAddr() string {
134 | return s.bindAddr
135 | }
136 |
137 | func (s *Server) BindPort() int {
138 | return s.bindPort
139 | }
140 |
--------------------------------------------------------------------------------
/server/proxy/tcpmux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 guylewin, guy@lewin.co.il
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proxy
16 |
17 | import (
18 | "fmt"
19 | "net"
20 | "strings"
21 |
22 | "github.com/fatedier/frp/pkg/config"
23 | "github.com/fatedier/frp/pkg/consts"
24 | "github.com/fatedier/frp/pkg/util/util"
25 | "github.com/fatedier/frp/pkg/util/vhost"
26 | )
27 |
28 | type TCPMuxProxy struct {
29 | *BaseProxy
30 | cfg *config.TCPMuxProxyConf
31 | }
32 |
33 | func (pxy *TCPMuxProxy) httpConnectListen(domain string, addrs []string) (_ []string, err error) {
34 | var l net.Listener
35 | if pxy.cfg.Group != "" {
36 | l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, domain)
37 | } else {
38 | routeConfig := &vhost.RouteConfig{
39 | Domain: domain,
40 | }
41 | l, err = pxy.rc.TCPMuxHTTPConnectMuxer.Listen(pxy.ctx, routeConfig)
42 | }
43 | if err != nil {
44 | return nil, err
45 | }
46 | pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", domain)
47 | pxy.listeners = append(pxy.listeners, l)
48 | return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TCPMuxHTTPConnectPort)), nil
49 | }
50 |
51 | func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
52 | addrs := make([]string, 0)
53 | for _, domain := range pxy.cfg.CustomDomains {
54 | if domain == "" {
55 | continue
56 | }
57 |
58 | addrs, err = pxy.httpConnectListen(domain, addrs)
59 | if err != nil {
60 | return "", err
61 | }
62 | }
63 |
64 | if pxy.cfg.SubDomain != "" {
65 | addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost, addrs)
66 | if err != nil {
67 | return "", err
68 | }
69 | }
70 |
71 | pxy.startListenHandler(pxy, HandleUserTCPConnection)
72 | remoteAddr = strings.Join(addrs, ",")
73 | return remoteAddr, err
74 | }
75 |
76 | func (pxy *TCPMuxProxy) Run() (remoteAddr string, err error) {
77 | switch pxy.cfg.Multiplexer {
78 | case consts.HTTPConnectTCPMultiplexer:
79 | remoteAddr, err = pxy.httpConnectRun()
80 | default:
81 | err = fmt.Errorf("unknown multiplexer [%s]", pxy.cfg.Multiplexer)
82 | }
83 |
84 | if err != nil {
85 | pxy.Close()
86 | }
87 | return remoteAddr, err
88 | }
89 |
90 | func (pxy *TCPMuxProxy) GetConf() config.ProxyConf {
91 | return pxy.cfg
92 | }
93 |
94 | func (pxy *TCPMuxProxy) Close() {
95 | pxy.BaseProxy.Close()
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/util/vhost/https.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package vhost
16 |
17 | import (
18 | "crypto/tls"
19 | "io"
20 | "net"
21 | "time"
22 |
23 | gnet "github.com/fatedier/golib/net"
24 | )
25 |
26 | type HTTPSMuxer struct {
27 | *Muxer
28 | }
29 |
30 | func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, error) {
31 | mux, err := NewMuxer(listener, GetHTTPSHostname, nil, nil, nil, timeout)
32 | return &HTTPSMuxer{mux}, err
33 | }
34 |
35 | func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
36 | reqInfoMap := make(map[string]string, 0)
37 | sc, rd := gnet.NewSharedConn(c)
38 |
39 | clientHello, err := readClientHello(rd)
40 | if err != nil {
41 | return nil, reqInfoMap, err
42 | }
43 |
44 | reqInfoMap["Host"] = clientHello.ServerName
45 | reqInfoMap["Scheme"] = "https"
46 | return sc, reqInfoMap, nil
47 | }
48 |
49 | func readClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) {
50 | var hello *tls.ClientHelloInfo
51 |
52 | // Note that Handshake always fails because the readOnlyConn is not a real connection.
53 | // As long as the Client Hello is successfully read, the failure should only happen after GetConfigForClient is called,
54 | // so we only care about the error if hello was never set.
55 | err := tls.Server(readOnlyConn{reader: reader}, &tls.Config{
56 | GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) {
57 | hello = &tls.ClientHelloInfo{}
58 | *hello = *argHello
59 | return nil, nil
60 | },
61 | }).Handshake()
62 |
63 | if hello == nil {
64 | return nil, err
65 | }
66 | return hello, nil
67 | }
68 |
69 | type readOnlyConn struct {
70 | reader io.Reader
71 | }
72 |
73 | func (conn readOnlyConn) Read(p []byte) (int, error) { return conn.reader.Read(p) }
74 | func (conn readOnlyConn) Write(p []byte) (int, error) { return 0, io.ErrClosedPipe }
75 | func (conn readOnlyConn) Close() error { return nil }
76 | func (conn readOnlyConn) LocalAddr() net.Addr { return nil }
77 | func (conn readOnlyConn) RemoteAddr() net.Addr { return nil }
78 | func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil }
79 | func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil }
80 | func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil }
81 |
--------------------------------------------------------------------------------
/pkg/config/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | import (
18 | "encoding/json"
19 | "errors"
20 | "strconv"
21 | "strings"
22 | )
23 |
24 | const (
25 | MB = 1024 * 1024
26 | KB = 1024
27 | )
28 |
29 | type BandwidthQuantity struct {
30 | s string // MB or KB
31 |
32 | i int64 // bytes
33 | }
34 |
35 | func NewBandwidthQuantity(s string) (BandwidthQuantity, error) {
36 | q := BandwidthQuantity{}
37 | err := q.UnmarshalString(s)
38 | if err != nil {
39 | return q, err
40 | }
41 | return q, nil
42 | }
43 |
44 | func MustBandwidthQuantity(s string) BandwidthQuantity {
45 | q := BandwidthQuantity{}
46 | err := q.UnmarshalString(s)
47 | if err != nil {
48 | panic(err)
49 | }
50 | return q
51 | }
52 |
53 | func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
54 | if q == nil && u == nil {
55 | return true
56 | }
57 | if q != nil && u != nil {
58 | return q.i == u.i
59 | }
60 | return false
61 | }
62 |
63 | func (q *BandwidthQuantity) String() string {
64 | return q.s
65 | }
66 |
67 | func (q *BandwidthQuantity) UnmarshalString(s string) error {
68 | s = strings.TrimSpace(s)
69 | if s == "" {
70 | return nil
71 | }
72 |
73 | var (
74 | base int64
75 | f float64
76 | err error
77 | )
78 | if strings.HasSuffix(s, "MB") {
79 | base = MB
80 | fstr := strings.TrimSuffix(s, "MB")
81 | f, err = strconv.ParseFloat(fstr, 64)
82 | if err != nil {
83 | return err
84 | }
85 | } else if strings.HasSuffix(s, "KB") {
86 | base = KB
87 | fstr := strings.TrimSuffix(s, "KB")
88 | f, err = strconv.ParseFloat(fstr, 64)
89 | if err != nil {
90 | return err
91 | }
92 | } else {
93 | return errors.New("unit not support")
94 | }
95 |
96 | q.s = s
97 | q.i = int64(f * float64(base))
98 | return nil
99 | }
100 |
101 | func (q *BandwidthQuantity) UnmarshalJSON(b []byte) error {
102 | if len(b) == 4 && string(b) == "null" {
103 | return nil
104 | }
105 |
106 | var str string
107 | err := json.Unmarshal(b, &str)
108 | if err != nil {
109 | return err
110 | }
111 |
112 | return q.UnmarshalString(str)
113 | }
114 |
115 | func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
116 | return []byte("\"" + q.s + "\""), nil
117 | }
118 |
119 | func (q *BandwidthQuantity) Bytes() int64 {
120 | return q.i
121 | }
122 |
--------------------------------------------------------------------------------
/pkg/transport/tls.go:
--------------------------------------------------------------------------------
1 | package transport
2 |
3 | import (
4 | "crypto/rand"
5 | "crypto/rsa"
6 | "crypto/tls"
7 | "crypto/x509"
8 | "encoding/pem"
9 | "math/big"
10 | "os"
11 | )
12 |
13 | func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
14 | tlsCert, err := tls.LoadX509KeyPair(certfile, keyfile)
15 | if err != nil {
16 | return nil, err
17 | }
18 | return &tlsCert, nil
19 | }
20 |
21 | func newRandomTLSKeyPair() *tls.Certificate {
22 | key, err := rsa.GenerateKey(rand.Reader, 1024)
23 | if err != nil {
24 | panic(err)
25 | }
26 | template := x509.Certificate{SerialNumber: big.NewInt(1)}
27 | certDER, err := x509.CreateCertificate(
28 | rand.Reader,
29 | &template,
30 | &template,
31 | &key.PublicKey,
32 | key)
33 | if err != nil {
34 | panic(err)
35 | }
36 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
37 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
38 |
39 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
40 | if err != nil {
41 | panic(err)
42 | }
43 | return &tlsCert
44 | }
45 |
46 | // Only support one ca file to add
47 | func newCertPool(caPath string) (*x509.CertPool, error) {
48 | pool := x509.NewCertPool()
49 |
50 | caCrt, err := os.ReadFile(caPath)
51 | if err != nil {
52 | return nil, err
53 | }
54 |
55 | pool.AppendCertsFromPEM(caCrt)
56 |
57 | return pool, nil
58 | }
59 |
60 | func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
61 | var base = &tls.Config{}
62 |
63 | if certPath == "" || keyPath == "" {
64 | // server will generate tls conf by itself
65 | cert := newRandomTLSKeyPair()
66 | base.Certificates = []tls.Certificate{*cert}
67 | } else {
68 | cert, err := newCustomTLSKeyPair(certPath, keyPath)
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | base.Certificates = []tls.Certificate{*cert}
74 | }
75 |
76 | if caPath != "" {
77 | pool, err := newCertPool(caPath)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | base.ClientAuth = tls.RequireAndVerifyClientCert
83 | base.ClientCAs = pool
84 | }
85 |
86 | return base, nil
87 | }
88 |
89 | func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Config, error) {
90 | var base = &tls.Config{}
91 |
92 | if certPath == "" || keyPath == "" {
93 | // client will not generate tls conf by itself
94 | } else {
95 | cert, err := newCustomTLSKeyPair(certPath, keyPath)
96 | if err != nil {
97 | return nil, err
98 | }
99 |
100 | base.Certificates = []tls.Certificate{*cert}
101 | }
102 |
103 | if caPath != "" {
104 | pool, err := newCertPool(caPath)
105 | if err != nil {
106 | return nil, err
107 | }
108 |
109 | base.RootCAs = pool
110 | base.ServerName = serverName
111 | base.InsecureSkipVerify = false
112 | } else {
113 | base.InsecureSkipVerify = true
114 | }
115 |
116 | return base, nil
117 | }
118 |
--------------------------------------------------------------------------------
/pkg/config/parse.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The frp Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | import (
18 | "bytes"
19 | "fmt"
20 | "os"
21 | "path/filepath"
22 | )
23 |
24 | func ParseClientConfig(filePath string) (
25 | cfg ClientCommonConf,
26 | pxyCfgs map[string]ProxyConf,
27 | visitorCfgs map[string]VisitorConf,
28 | err error,
29 | ) {
30 | var content []byte
31 | content, err = GetRenderedConfFromFile(filePath)
32 | if err != nil {
33 | return
34 | }
35 | configBuffer := bytes.NewBuffer(nil)
36 | configBuffer.Write(content)
37 |
38 | // Parse common section.
39 | cfg, err = UnmarshalClientConfFromIni(content)
40 | if err != nil {
41 | return
42 | }
43 | cfg.Complete()
44 | if err = cfg.Validate(); err != nil {
45 | err = fmt.Errorf("Parse config error: %v", err)
46 | return
47 | }
48 |
49 | // Aggregate proxy configs from include files.
50 | var buf []byte
51 | buf, err = getIncludeContents(cfg.IncludeConfigFiles)
52 | if err != nil {
53 | err = fmt.Errorf("getIncludeContents error: %v", err)
54 | return
55 | }
56 | configBuffer.WriteString("\n")
57 | configBuffer.Write(buf)
58 |
59 | // Parse all proxy and visitor configs.
60 | pxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start)
61 | if err != nil {
62 | return
63 | }
64 | return
65 | }
66 |
67 | // getIncludeContents renders all configs from paths.
68 | // files format can be a single file path or directory or regex path.
69 | func getIncludeContents(paths []string) ([]byte, error) {
70 | out := bytes.NewBuffer(nil)
71 | for _, path := range paths {
72 | absDir, err := filepath.Abs(filepath.Dir(path))
73 | if err != nil {
74 | return nil, err
75 | }
76 | if _, err := os.Stat(absDir); os.IsNotExist(err) {
77 | return nil, err
78 | }
79 | files, err := os.ReadDir(absDir)
80 | if err != nil {
81 | return nil, err
82 | }
83 | for _, fi := range files {
84 | if fi.IsDir() {
85 | continue
86 | }
87 | absFile := filepath.Join(absDir, fi.Name())
88 | if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
89 | tmpContent, err := GetRenderedConfFromFile(absFile)
90 | if err != nil {
91 | return nil, fmt.Errorf("render extra config %s error: %v", absFile, err)
92 | }
93 | out.Write(tmpContent)
94 | out.WriteString("\n")
95 | }
96 | }
97 | }
98 | return out.Bytes(), nil
99 | }
100 |
--------------------------------------------------------------------------------
/pkg/plugin/client/http2https.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 fatedier, fatedier@gmail.com
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package plugin
16 |
17 | import (
18 | "crypto/tls"
19 | "fmt"
20 | "io"
21 | "net"
22 | "net/http"
23 | "net/http/httputil"
24 | "strings"
25 |
26 | frpNet "github.com/fatedier/frp/pkg/util/net"
27 | )
28 |
29 | const PluginHTTP2HTTPS = "http2https"
30 |
31 | func init() {
32 | Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
33 | }
34 |
35 | type HTTP2HTTPSPlugin struct {
36 | hostHeaderRewrite string
37 | localAddr string
38 | headers map[string]string
39 |
40 | l *Listener
41 | s *http.Server
42 | }
43 |
44 | func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
45 | localAddr := params["plugin_local_addr"]
46 | hostHeaderRewrite := params["plugin_host_header_rewrite"]
47 | headers := make(map[string]string)
48 | for k, v := range params {
49 | if !strings.HasPrefix(k, "plugin_header_") {
50 | continue
51 | }
52 | if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
53 | headers[k] = v
54 | }
55 | }
56 |
57 | if localAddr == "" {
58 | return nil, fmt.Errorf("plugin_local_addr is required")
59 | }
60 |
61 | listener := NewProxyListener()
62 |
63 | p := &HTTP2HTTPSPlugin{
64 | localAddr: localAddr,
65 | hostHeaderRewrite: hostHeaderRewrite,
66 | headers: headers,
67 | l: listener,
68 | }
69 |
70 | tr := &http.Transport{
71 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
72 | }
73 |
74 | rp := &httputil.ReverseProxy{
75 | Director: func(req *http.Request) {
76 | req.URL.Scheme = "https"
77 | req.URL.Host = p.localAddr
78 | if p.hostHeaderRewrite != "" {
79 | req.Host = p.hostHeaderRewrite
80 | }
81 | for k, v := range p.headers {
82 | req.Header.Set(k, v)
83 | }
84 | },
85 | Transport: tr,
86 | }
87 |
88 | p.s = &http.Server{
89 | Handler: rp,
90 | }
91 |
92 | go p.s.Serve(listener)
93 |
94 | return p, nil
95 | }
96 |
97 | func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
98 | wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
99 | p.l.PutConn(wrapConn)
100 | }
101 |
102 | func (p *HTTP2HTTPSPlugin) Name() string {
103 | return PluginHTTP2HTTPS
104 | }
105 |
106 | func (p *HTTP2HTTPSPlugin) Close() error {
107 | if err := p.s.Close(); err != nil {
108 | return err
109 | }
110 | return nil
111 | }
112 |
--------------------------------------------------------------------------------
/test/e2e/framework/process.go:
--------------------------------------------------------------------------------
1 | package framework
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "time"
8 |
9 | flog "github.com/fatedier/frp/pkg/util/log"
10 | "github.com/fatedier/frp/test/e2e/pkg/process"
11 | )
12 |
13 | // RunProcesses run multiple processes from templates.
14 | // The first template should always be frps.
15 | func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
16 | templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
17 | for _, t := range serverTemplates {
18 | templates = append(templates, t)
19 | }
20 | for _, t := range clientTemplates {
21 | templates = append(templates, t)
22 | }
23 | outs, ports, err := f.RenderTemplates(templates)
24 | ExpectNoError(err)
25 | ExpectTrue(len(templates) > 0)
26 |
27 | for name, port := range ports {
28 | f.usedPorts[name] = port
29 | }
30 |
31 | for i := range serverTemplates {
32 | path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
33 | err = os.WriteFile(path, []byte(outs[i]), 0666)
34 | ExpectNoError(err)
35 | flog.Trace("[%s] %s", path, outs[i])
36 |
37 | p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
38 | f.serverConfPaths = append(f.serverConfPaths, path)
39 | f.serverProcesses = append(f.serverProcesses, p)
40 | err = p.Start()
41 | ExpectNoError(err)
42 | }
43 | time.Sleep(time.Second)
44 |
45 | for i := range clientTemplates {
46 | index := i + len(serverTemplates)
47 | path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
48 | err = os.WriteFile(path, []byte(outs[index]), 0666)
49 | ExpectNoError(err)
50 | flog.Trace("[%s] %s", path, outs[index])
51 |
52 | p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
53 | f.clientConfPaths = append(f.clientConfPaths, path)
54 | f.clientProcesses = append(f.clientProcesses, p)
55 | err = p.Start()
56 | ExpectNoError(err)
57 | time.Sleep(500 * time.Millisecond)
58 | }
59 | time.Sleep(500 * time.Millisecond)
60 | }
61 |
62 | func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
63 | p := process.NewWithEnvs(TestContext.FRPServerPath, args, f.osEnvs)
64 | f.serverProcesses = append(f.serverProcesses, p)
65 | err := p.Start()
66 | if err != nil {
67 | return p, p.StdOutput(), err
68 | }
69 | // sleep for a while to get std output
70 | time.Sleep(500 * time.Millisecond)
71 | return p, p.StdOutput(), nil
72 | }
73 |
74 | func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
75 | p := process.NewWithEnvs(TestContext.FRPClientPath, args, f.osEnvs)
76 | f.clientProcesses = append(f.clientProcesses, p)
77 | err := p.Start()
78 | if err != nil {
79 | return p, p.StdOutput(), err
80 | }
81 | time.Sleep(500 * time.Millisecond)
82 | return p, p.StdOutput(), nil
83 | }
84 |
85 | func (f *Framework) GenerateConfigFile(content string) string {
86 | f.configFileIndex++
87 | path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
88 | err := os.WriteFile(path, []byte(content), 0666)
89 | ExpectNoError(err)
90 | return path
91 | }
92 |
--------------------------------------------------------------------------------