Compare commits

...

26 Commits

Author SHA1 Message Date
fatedier
cfd1a3128a Merge pull request #2426 from fatedier/dev
update workflow file
2021-06-03 00:59:21 +08:00
fatedier
2393923870 update build image go version 2021-06-03 00:56:07 +08:00
fatedier
5f594e9a71 fix goreleaser 2021-06-03 00:46:56 +08:00
fatedier
57577ea044 Merge pull request #2425 from fatedier/dev
bump version
2021-06-03 00:14:32 +08:00
fatedier
8637077d90 update version 2021-06-03 00:10:35 +08:00
fatedier
ccb85a9926 update README 2021-06-03 00:07:51 +08:00
fatedier
02b12df887 frpc: consider include configs for verify and reload command (#2424) 2021-06-02 23:54:22 +08:00
fatedier
c32a2ed140 frpc: support 'includes' in common section to include proxy configs in other files(#2421) 2021-06-02 00:39:05 +08:00
Wu Han
9ae322cccf readme: fix toc hyperlinx (#2401) 2021-05-17 17:29:13 +08:00
fatedier
9cebfccb39 cmd: add verify command to verify if config file syntax is valid (#2389) 2021-05-12 12:15:22 +08:00
fatedier
630dad50ed web: support sudp in dashboard (#2385) 2021-05-11 13:37:14 +08:00
fatedier
0d84da91d4 change default value of dashboard_user and dashboard_pwd to empty string (#2383) 2021-05-10 23:04:26 +08:00
fatedier
2408f1df04 frpc: fix that login_fail_exit invalid if protocol=kcp (#2363) 2021-04-22 18:33:10 +08:00
fatedier
fbaa5f866e add e2e tests (#2334) 2021-03-31 16:57:39 +08:00
fatedier
c5c79e4148 Merge pull request #2324 from fatedier/dev
bump version v0.36.2
2021-03-22 14:56:48 +08:00
fatedier
9a849a29e9 fix config parse logic (#2323) 2021-03-22 14:53:30 +08:00
Splash
6b80861bd6 Fix log_file does not work in config files (#2316) 2021-03-22 11:30:12 +08:00
fatedier
fa0e84382e autogen code 2021-03-19 17:39:26 +08:00
bobo liu
1a11b28f8d config: inline is NOT SUPPORTED in encoding/json (#2304) 2021-03-19 17:36:39 +08:00
YK-Samgo
bed13d7ef1 Support reverseproxy to dashboard with additional parts in path (#2289)
/api/ -> ../api/   in vendor.js
    Support reverseproxy to dashboard with addtional parts in path.
2021-03-19 17:31:16 +08:00
fatedier
e7d76b180d update README.md (#2314) 2021-03-19 16:06:22 +08:00
wuqing
dba8925eaa makefile add arm64 2021-03-19 14:01:26 +08:00
fatedier
55da58eca4 Merge pull request #2310 from fatedier/dev
bump version
2021-03-18 11:14:56 +08:00
wuqing
fdef7448a7 update version 2021-03-18 11:12:10 +08:00
Chotow
0ff27fc9ac fix(server): listen udp port failed (#2308)
fix #2306
2021-03-18 11:05:03 +08:00
fatedier
9f8db314d6 update 2021-03-17 11:43:03 +08:00
77 changed files with 1355 additions and 607 deletions

View File

@@ -14,27 +14,30 @@ jobs:
name: Build Golang project name: Build Golang project
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- - name: Set up Go 1.x
name: Set up Go 1.x
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.15 go-version: 1.16
-
run: go version - run: |
- # https://github.com/actions/setup-go/issues/107
name: Check out code into the Go module directory cp -f `which go` /usr/bin/go
- run: go version
- name: Check out code into the Go module directory
uses: actions/checkout@v2 uses: actions/checkout@v2
-
name: Build - name: Build
run: make build run: make build
-
name: Archive artifacts for frpc - name: Archive artifacts for frpc
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
with: with:
name: frpc name: frpc
path: bin/frpc path: bin/frpc
-
name: Archive artifacts for frps - name: Archive artifacts for frps
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
with: with:
name: frps name: frps
@@ -46,42 +49,41 @@ jobs:
needs: binary needs: binary
steps: steps:
# environment # environment
- - name: Checkout
name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
fetch-depth: '0' fetch-depth: '0'
-
name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
# download binaries of frpc and frps # download binaries of frpc and frps
- - name: Download binary of frpc
name: Download binary of frpc
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
with: with:
name: frpc name: frpc
path: bin/frpc path: bin/frpc
-
name: Download binary of frps - name: Download binary of frps
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
with: with:
name: frps name: frps
path: bin/frps path: bin/frps
# get image tag name # get image tag name
- - name: Get Image Tag Name
name: Get Image Tag Name
run: | run: |
if [ x${{ github.event.inputs.tag }} == x"" ]; then if [ x${{ github.event.inputs.tag }} == x"" ]; then
echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
else else
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
fi fi
# prepare image tags # prepare image tags
- - name: Prepare Image Tags
name: Prepare Image Tags
run: | run: |
echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV
echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV
@@ -89,9 +91,9 @@ jobs:
echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
# build images # build images
- - name: Build Images
name: Build Images
run: | run: |
# for Docker hub # for Docker hub
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} . docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} .
@@ -99,16 +101,16 @@ jobs:
# for GPR # for GPR
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} . docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} .
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} . docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} .
# push to dockerhub # push to dockerhub
- - name: Publish to Dockerhub
name: Publish to Dockerhub
run: | run: |
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
docker push ${{ env.TAG_FRPC }} docker push ${{ env.TAG_FRPC }}
docker push ${{ env.TAG_FRPS }} docker push ${{ env.TAG_FRPS }}
# push to gpr # push to gpr
- - name: Publish to GPR
name: Publish to GPR
run: | run: |
echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin
docker push ${{ env.TAG_FRPC_GPR }} docker push ${{ env.TAG_FRPC_GPR }}

View File

@@ -16,6 +16,10 @@ jobs:
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16 go-version: 1.16
- run: |
# https://github.com/actions/setup-go/issues/107
cp -f `which go` /usr/bin/go
- name: Make All - name: Make All
run: | run: |

View File

@@ -2,7 +2,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
export GO111MODULE=on export GO111MODULE=on
LDFLAGS := -s -w LDFLAGS := -s -w
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat 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
all: build all: build

View File

@@ -24,12 +24,13 @@ frp also has a P2P connect mode.
* [Forward DNS query request](#forward-dns-query-request) * [Forward DNS query request](#forward-dns-query-request)
* [Forward Unix domain socket](#forward-unix-domain-socket) * [Forward Unix domain socket](#forward-unix-domain-socket)
* [Expose a simple HTTP file server](#expose-a-simple-http-file-server) * [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service) * [Enable HTTPS for local HTTP(S) service](#enable-https-for-local-https-service)
* [Expose your service privately](#expose-your-service-privately) * [Expose your service privately](#expose-your-service-privately)
* [P2P Mode](#p2p-mode) * [P2P Mode](#p2p-mode)
* [Features](#features) * [Features](#features)
* [Configuration Files](#configuration-files) * [Configuration Files](#configuration-files)
* [Using Environment Variables](#using-environment-variables) * [Using Environment Variables](#using-environment-variables)
* [Split Configures Into Different Files](#split-configures-into-different-files)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
* [Admin UI](#admin-ui) * [Admin UI](#admin-ui)
* [Monitor](#monitor) * [Monitor](#monitor)
@@ -412,6 +413,27 @@ export FRP_SSH_REMOTE_PORT="6000"
`frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`. `frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`.
### Split Configures Into Different Files
You can split multiple proxy configs into different files and include them in the main file.
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
includes=./confd/*.ini
```
```ini
# ./confd/test.ini
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
```
### Dashboard ### Dashboard
Check frp's status and proxies' statistics information by Dashboard. Check frp's status and proxies' statistics information by Dashboard.
@@ -421,12 +443,12 @@ Configure a port for dashboard to enable this feature:
```ini ```ini
[common] [common]
dashboard_port = 7500 dashboard_port = 7500
# dashboard's username and password are both optionalif not set, default is admin. # dashboard's username and password are both optional
dashboard_user = admin dashboard_user = admin
dashboard_pwd = admin dashboard_pwd = admin
``` ```
Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin` by default. Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin`.
![dashboard](/doc/pic/dashboard.png) ![dashboard](/doc/pic/dashboard.png)
@@ -444,7 +466,7 @@ admin_user = admin
admin_pwd = admin admin_pwd = admin
``` ```
Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin` by default. Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin`.
### Monitor ### Monitor
@@ -624,10 +646,12 @@ admin_addr = 127.0.0.1
admin_port = 7400 admin_port = 7400
``` ```
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let `frpc` create or update or delete proxies. Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let `frpc` create or update or remove proxies.
**Note that parameters in [common] section won't be modified except 'start'.** **Note that parameters in [common] section won't be modified except 'start'.**
You can run command `frpc verify -c ./frpc.ini` before reloading to check if there are config errors.
### Get proxy status from client ### Get proxy status from client
Use `frpc status -c ./frpc.ini` to get status of all proxies. The `admin_addr` and `admin_port` fields are required for enabling HTTP API. Use `frpc status -c ./frpc.ini` to get status of all proxies. The `admin_addr` and `admin_port` fields are required for enabling HTTP API.
@@ -732,7 +756,7 @@ This feature is suitable for a large number of short connections.
Load balancing is supported by `group`. Load balancing is supported by `group`.
This feature is only available for types `tcp` and `http` now. This feature is only available for types `tcp`, `http`, `tcpmux` now.
```ini ```ini
# frpc.ini # frpc.ini
@@ -1006,7 +1030,7 @@ frpc will generate 8 proxies like `test_tcp_0`, `test_tcp_1`, ..., `test_tcp_7`.
frpc only forwards requests to local TCP or UDP ports by default. frpc only forwards requests to local TCP or UDP ports by default.
Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage). Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file`, `http2https`, `https2http`, `https2https` and you can see [example usage](#example-usage).
Specify which plugin to use with the `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` are not used for plugin. Specify which plugin to use with the `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` are not used for plugin.

View File

@@ -1,12 +0,0 @@
### New
* New plugin `https2https`.
* frpc supports `tls_server_name` to override the default value from `server_addr`.
### Improvement
* Increase reconnect frequency if it occurs an network error between frpc and frps.
### Fix
* Fix panic issue about xtcp.

View File

@@ -1 +1 @@
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?f30e0e5ff7dbde4611e0"></script><script type="text/javascript" src="vendor.js?a82aed5fb0b844cbdb29"></script></body> </html> <!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5a0eb52788515d02ca46"></script><script type="text/javascript" src="vendor.js?3e5221f064f1295497bf"></script></body> </html>

View File

@@ -1 +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<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"a82aed5fb0b844cbdb29"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]); !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,u,c){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"3e5221f064f1295497bf"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?b8b55d8156200869417b"></script><script type="text/javascript" src="vendor.js?3e078a9d741093b909de"></script></body> </html> <!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?41dbccdbc87e6bcbd79c"></script><script type="text/javascript" src="vendor.js?d7109b07f8f86bab2eeb"></script></body> </html>

View File

@@ -1 +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,u,c){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"3e078a9d741093b909de"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]); !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,u,c){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"d7109b07f8f86bab2eeb"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -33,36 +33,19 @@ type GeneralResponse struct {
} }
// GET api/reload // GET api/reload
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200} res := GeneralResponse{Code: 200}
log.Info("Http request [/api/reload]") log.Info("api request [/api/reload]")
defer func() { defer func() {
log.Info("Http response [/api/reload], code [%d]", res.Code) log.Info("api response [/api/reload], code [%d]", res.Code)
w.WriteHeader(res.Code) w.WriteHeader(res.Code)
if len(res.Msg) > 0 { if len(res.Msg) > 0 {
w.Write([]byte(res.Msg)) w.Write([]byte(res.Msg))
} }
}() }()
content, err := config.GetRenderedConfFromFile(svr.cfgFile) _, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(svr.cfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("reload frpc config file error: %s", res.Msg)
return
}
newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("reload frpc common section error: %s", res.Msg)
return
}
pxyCfgs, visitorCfgs, err := config.LoadAllProxyConfsFromIni(svr.cfg.User, content, newCommonCfg.Start)
if err != nil { if err != nil {
res.Code = 400 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
@@ -70,8 +53,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
return return
} }
err = svr.ReloadConf(pxyCfgs, visitorCfgs) if err = svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
if err != nil {
res.Code = 500 res.Code = 500
res.Msg = err.Error() res.Msg = err.Error()
log.Warn("reload frpc proxy config error: %s", res.Msg) log.Warn("reload frpc proxy config error: %s", res.Msg)

View File

@@ -227,7 +227,7 @@ func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
pw.mu.RLock() pw.mu.RLock()
pxy := pw.pxy pxy := pw.pxy
pw.mu.RUnlock() pw.mu.RUnlock()
if pxy != nil { if pxy != nil && pw.Phase == ProxyPhaseRunning {
xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn, m) go pxy.InWorkConn(workConn, m)
} else { } else {

View File

@@ -366,7 +366,7 @@ func (sv *SUDPVisitor) Run() (err error) {
sv.sendCh = make(chan *msg.UDPPacket, 1024) sv.sendCh = make(chan *msg.UDPPacket, 1024)
sv.readCh = make(chan *msg.UDPPacket, 1024) sv.readCh = make(chan *msg.UDPPacket, 1024)
xl.Info("sudp start to work") xl.Info("sudp start to work, listen on %s", addr)
go sv.dispatcher() go sv.dispatcher()
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize)) go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize))
@@ -446,7 +446,7 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) {
case *msg.UDPPacket: case *msg.UDPPacket:
if errRet := errors.PanicToError(func() { if errRet := errors.PanicToError(func() {
sv.readCh <- m sv.readCh <- m
xl.Trace("frpc visitor get udp packet from frpc") xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
}); errRet != nil { }); errRet != nil {
xl.Info("reader goroutine for udp work connection closed") xl.Info("reader goroutine for udp work connection closed")
return return
@@ -475,6 +475,7 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) {
xl.Warn("sender goroutine for udp work connection closed: %v", errRet) xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
return return
} }
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
case <-closeCh: case <-closeCh:
return return
} }

View File

@@ -47,7 +47,7 @@ var httpCmd = &cobra.Command{
Use: "http", Use: "http",
Short: "Run frpc with a single http proxy", Short: "Run frpc with a single http proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -43,7 +43,7 @@ var httpsCmd = &cobra.Command{
Use: "https", Use: "https",
Short: "Run frpc with a single https proxy", Short: "Run frpc with a single https proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -35,19 +35,13 @@ var reloadCmd = &cobra.Command{
Use: "reload", Use: "reload",
Short: "Hot-Reload frpc configuration", Short: "Hot-Reload frpc configuration",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
iniContent, err := config.GetRenderedConfFromFile(cfgFile) cfg, _, _, err := config.ParseClientConfig(cfgFile)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent) err = reload(cfg)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = reload(clientCfg)
if err != nil { if err != nil {
fmt.Printf("frpc reload error: %v\n", err) fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1) os.Exit(1)

View File

@@ -129,23 +129,6 @@ func handleSignal(svr *client.Service) {
close(kcpDoneCh) close(kcpDoneCh)
} }
func parseClientCommonCfg(fileType int, source []byte) (cfg config.ClientCommonConf, err error) {
if fileType == CfgFileTypeIni {
cfg, err = config.UnmarshalClientConfFromIni(source)
} else if fileType == CfgFileTypeCmd {
cfg, err = parseClientCommonCfgFromCmd()
}
if err != nil {
return
}
err = cfg.Check()
if err != nil {
return
}
return
}
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg = config.GetDefaultClientConf() cfg = config.GetDefaultClientConf()
@@ -167,11 +150,6 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg.LogLevel = logLevel cfg.LogLevel = logLevel
cfg.LogFile = logFile cfg.LogFile = logFile
cfg.LogMaxDays = int64(logMaxDays) cfg.LogMaxDays = int64(logMaxDays)
if logFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
cfg.DisableLogColor = disableLogColor cfg.DisableLogColor = disableLogColor
// Only token authentication is supported in cmd mode // Only token authentication is supported in cmd mode
@@ -179,28 +157,20 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg.Token = token cfg.Token = token
cfg.TLSEnable = tlsEnable cfg.TLSEnable = tlsEnable
cfg.Complete()
if err = cfg.Validate(); err != nil {
err = fmt.Errorf("Parse config error: %v", err)
return
}
return return
} }
func runClient(cfgFilePath string) (err error) { func runClient(cfgFilePath string) error {
var content []byte cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
content, err = config.GetRenderedConfFromFile(cfgFilePath)
if err != nil { if err != nil {
return return err
} }
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
if err != nil {
return
}
pxyCfgs, visitorCfgs, err := config.LoadAllProxyConfsFromIni(cfg.User, content, cfg.Start)
if err != nil {
return
}
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
return
} }
func startService( func startService(
@@ -238,7 +208,7 @@ func startService(
} }
err = svr.Run() err = svr.Run()
if cfg.Protocol == "kcp" { if err == nil && cfg.Protocol == "kcp" {
<-kcpDoneCh <-kcpDoneCh
} }
return return

View File

@@ -38,20 +38,13 @@ var statusCmd = &cobra.Command{
Use: "status", Use: "status",
Short: "Overview of all proxies status", Short: "Overview of all proxies status",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
iniContent, err := config.GetRenderedConfFromFile(cfgFile) cfg, _, _, err := config.ParseClientConfig(cfgFile)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent) if err = status(cfg); err != nil {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = status(clientCfg)
if err != nil {
fmt.Printf("frpc get status error: %v\n", err) fmt.Printf("frpc get status error: %v\n", err)
os.Exit(1) os.Exit(1)
} }

View File

@@ -45,7 +45,7 @@ var stcpCmd = &cobra.Command{
Use: "stcp", Use: "stcp",
Short: "Run frpc with a single stcp proxy", Short: "Run frpc with a single stcp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -45,7 +45,7 @@ var sudpCmd = &cobra.Command{
Use: "sudp", Use: "sudp",
Short: "Run frpc with a single sudp proxy", Short: "Run frpc with a single sudp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -41,7 +41,7 @@ var tcpCmd = &cobra.Command{
Use: "tcp", Use: "tcp",
Short: "Run frpc with a single tcp proxy", Short: "Run frpc with a single tcp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -44,7 +44,7 @@ var tcpMuxCmd = &cobra.Command{
Use: "tcpmux", Use: "tcpmux",
Short: "Run frpc with a single tcpmux proxy", Short: "Run frpc with a single tcpmux proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -41,7 +41,7 @@ var udpCmd = &cobra.Command{
Use: "udp", Use: "udp",
Short: "Run frpc with a single udp proxy", Short: "Run frpc with a single udp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

43
cmd/frpc/sub/verify.go Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2021 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"github.com/fatedier/frp/pkg/config"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(verifyCmd)
}
var verifyCmd = &cobra.Command{
Use: "verify",
Short: "Verify that the configures is valid",
RunE: func(cmd *cobra.Command, args []string) error {
_, _, _, err := config.ParseClientConfig(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile)
return nil
},
}

View File

@@ -45,7 +45,7 @@ var xtcpCmd = &cobra.Command{
Use: "xtcp", Use: "xtcp",
Short: "Run frpc with a single xtcp proxy", Short: "Run frpc with a single xtcp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) clientCfg, err := parseClientCommonCfgFromCmd()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -105,7 +105,6 @@ var rootCmd = &cobra.Command{
var cfg config.ServerCommonConf var cfg config.ServerCommonConf
var err error var err error
if cfgFile != "" { if cfgFile != "" {
log.Info("frps uses config file: %s", cfgFile)
var content []byte var content []byte
content, err = config.GetRenderedConfFromFile(cfgFile) content, err = config.GetRenderedConfFromFile(cfgFile)
if err != nil { if err != nil {
@@ -113,7 +112,6 @@ var rootCmd = &cobra.Command{
} }
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content) cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
} else { } else {
log.Info("frps uses command line arguments for config")
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil) cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil)
} }
if err != nil { if err != nil {
@@ -144,9 +142,10 @@ func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonC
if err != nil { if err != nil {
return return
} }
cfg.Complete()
err = cfg.Check() err = cfg.Validate()
if err != nil { if err != nil {
err = fmt.Errorf("Parse config error: %v", err)
return return
} }
return return
@@ -190,18 +189,19 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
} }
} }
cfg.MaxPortsPerClient = maxPortsPerClient cfg.MaxPortsPerClient = maxPortsPerClient
if logFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
cfg.DisableLogColor = disableLogColor cfg.DisableLogColor = disableLogColor
return return
} }
func runServer(cfg config.ServerCommonConf) (err error) { func runServer(cfg config.ServerCommonConf) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor) log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
if cfgFile != "" {
log.Info("frps uses config file: %s", cfgFile)
} else {
log.Info("frps uses command line arguments for config")
}
svr, err := server.NewService(cfg) svr, err := server.NewService(cfg)
if err != nil { if err != nil {
return err return err

53
cmd/frps/verify.go Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2021 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"github.com/fatedier/frp/pkg/config"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(verifyCmd)
}
var verifyCmd = &cobra.Command{
Use: "verify",
Short: "Verify that the configures is valid",
RunE: func(cmd *cobra.Command, args []string) error {
if cfgFile == "" {
fmt.Println("no config file is specified")
return nil
}
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
_, err = parseServerCommonCfg(CfgFileTypeIni, iniContent)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
return nil
},
}

View File

@@ -102,6 +102,9 @@ meta_var2 = 234
# It affects the udp and sudp proxy. # It affects the udp and sudp proxy.
udp_packet_size = 1500 udp_packet_size = 1500
# include other config files for proxies.
# includes = ./confd/*.ini
# 'ssh' is the unique proxy name # 'ssh' is the unique proxy name
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh' # if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
[ssh] [ssh]

2
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.0 github.com/gorilla/websocket v1.4.0
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/cpuid v1.2.0 // indirect github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.9.1 // indirect github.com/klauspost/reedsolomon v1.9.1 // indirect

4
go.sum
View File

@@ -78,8 +78,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=

View File

@@ -17,6 +17,7 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/fatedier/frp/pkg/auth" "github.com/fatedier/frp/pkg/auth"
@@ -29,7 +30,7 @@ import (
// recommended to use GetDefaultClientConf instead of creating this object // recommended to use GetDefaultClientConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values. // directly, so that all unspecified fields have reasonable default values.
type ClientCommonConf struct { type ClientCommonConf struct {
auth.ClientConfig `ini:",extends" json:"inline"` auth.ClientConfig `ini:",extends"`
// ServerAddr specifies the address of the server to connect to. By // ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0". // default, this value is "0.0.0.0".
@@ -68,10 +69,10 @@ type ClientCommonConf struct {
// is 0. // is 0.
AdminPort int `ini:"admin_port" json:"admin_port"` AdminPort int `ini:"admin_port" json:"admin_port"`
// AdminUser specifies the username that the admin server will use for // AdminUser specifies the username that the admin server will use for
// login. By default, this value is "admin". // login.
AdminUser string `ini:"admin_user" json:"admin_user"` AdminUser string `ini:"admin_user" json:"admin_user"`
// AdminPwd specifies the password that the admin server will use for // AdminPwd specifies the password that the admin server will use for
// login. By default, this value is "admin". // login.
AdminPwd string `ini:"admin_pwd" json:"admin_pwd"` AdminPwd string `ini:"admin_pwd" json:"admin_pwd"`
// AssetsDir specifies the local directory that the admin server will load // AssetsDir specifies the local directory that the admin server will load
// resources from. If this value is "", assets will be loaded from the // resources from. If this value is "", assets will be loaded from the
@@ -136,50 +137,61 @@ type ClientCommonConf struct {
// UDPPacketSize specifies the udp packet size // UDPPacketSize specifies the udp packet size
// By default, this value is 1500 // By default, this value is 1500
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
// Include other config files for proxies.
IncludeConfigFiles []string `ini:"includes" json:"includes"`
} }
// GetDefaultClientConf returns a client configuration with default values. // GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf { func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{ return ClientCommonConf{
ClientConfig: auth.GetDefaultClientConf(), ClientConfig: auth.GetDefaultClientConf(),
ServerAddr: "0.0.0.0", ServerAddr: "0.0.0.0",
ServerPort: 7000, ServerPort: 7000,
HTTPProxy: os.Getenv("http_proxy"), HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
DisableLogColor: false, DisableLogColor: false,
AdminAddr: "127.0.0.1", AdminAddr: "127.0.0.1",
AdminPort: 0, AdminPort: 0,
AdminUser: "", AdminUser: "",
AdminPwd: "", AdminPwd: "",
AssetsDir: "", AssetsDir: "",
PoolCount: 1, PoolCount: 1,
TCPMux: true, TCPMux: true,
User: "", User: "",
DNSServer: "", DNSServer: "",
LoginFailExit: true, LoginFailExit: true,
Start: make([]string, 0), Start: make([]string, 0),
Protocol: "tcp", Protocol: "tcp",
TLSEnable: false, TLSEnable: false,
TLSCertFile: "", TLSCertFile: "",
TLSKeyFile: "", TLSKeyFile: "",
TLSTrustedCaFile: "", TLSTrustedCaFile: "",
HeartbeatInterval: 30, HeartbeatInterval: 30,
HeartbeatTimeout: 90, HeartbeatTimeout: 90,
Metas: make(map[string]string), Metas: make(map[string]string),
UDPPacketSize: 1500, UDPPacketSize: 1500,
IncludeConfigFiles: make([]string, 0),
} }
} }
func (cfg *ClientCommonConf) Check() error { func (cfg *ClientCommonConf) Complete() {
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
}
func (cfg *ClientCommonConf) Validate() error {
if cfg.HeartbeatInterval <= 0 { if cfg.HeartbeatInterval <= 0 {
return fmt.Errorf("Parse conf error: invalid heartbeat_interval") return fmt.Errorf("invalid heartbeat_interval")
} }
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval { if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
return fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval") return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
} }
if cfg.TLSEnable == false { if cfg.TLSEnable == false {
@@ -196,6 +208,19 @@ func (cfg *ClientCommonConf) Check() error {
} }
} }
if cfg.Protocol != "tcp" && cfg.Protocol != "kcp" && cfg.Protocol != "websocket" {
return fmt.Errorf("invalid protocol")
}
for _, f := range cfg.IncludeConfigFiles {
absDir, err := filepath.Abs(filepath.Dir(f))
if err != nil {
return fmt.Errorf("include: parse directory of %s failed: %v", f, absDir)
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return fmt.Errorf("include: directory of %s not exist", f)
}
}
return nil return nil
} }
@@ -224,7 +249,6 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
} }
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_") common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
return common, nil return common, nil
} }
@@ -278,7 +302,7 @@ func LoadAllProxyConfsFromIni(
for _, section := range rangeSections { for _, section := range rangeSections {
err = renderRangeProxyTemplates(f, section) err = renderRangeProxyTemplates(f, section)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("fail to render range-section[%s] with error: %v", section.Name(), err) return nil, nil, fmt.Errorf("failed to render template for proxy %s: %v", section.Name(), err)
} }
} }
@@ -303,7 +327,7 @@ func LoadAllProxyConfsFromIni(
case "server": case "server":
newConf, newErr := NewProxyConfFromIni(prefix, name, section) newConf, newErr := NewProxyConfFromIni(prefix, name, section)
if newErr != nil { if newErr != nil {
return nil, nil, fmt.Errorf("fail to parse section[%s], err: %v", name, newErr) return nil, nil, fmt.Errorf("failed to parse proxy %s, err: %v", name, newErr)
} }
proxyConfs[prefix+name] = newConf proxyConfs[prefix+name] = newConf
case "visitor": case "visitor":
@@ -313,7 +337,7 @@ func LoadAllProxyConfsFromIni(
} }
visitorConfs[prefix+name] = newConf visitorConfs[prefix+name] = newConf
default: default:
return nil, nil, fmt.Errorf("section[%s] role should be 'server' or 'visitor'", name) return nil, nil, fmt.Errorf("proxy %s role should be 'server' or 'visitor'", name)
} }
} }
return proxyConfs, visitorConfs, nil return proxyConfs, visitorConfs, nil

View File

@@ -290,12 +290,13 @@ func Test_LoadClientCommonConf(t *testing.T) {
"var1": "123", "var1": "123",
"var2": "234", "var2": "234",
}, },
UDPPacketSize: 1509, UDPPacketSize: 1509,
IncludeConfigFiles: []string{},
} }
common, err := UnmarshalClientConfFromIni(testClientBytesWithFull) common, err := UnmarshalClientConfFromIni(testClientBytesWithFull)
assert.NoError(err) assert.NoError(err)
assert.Equal(expected, common) assert.EqualValues(expected, common)
} }
func Test_LoadClientBasicConf(t *testing.T) { func Test_LoadClientBasicConf(t *testing.T) {
@@ -641,5 +642,4 @@ func Test_LoadClientBasicConf(t *testing.T) {
assert.NoError(err) assert.NoError(err)
assert.Equal(proxyExpected, proxyActual) assert.Equal(proxyExpected, proxyActual)
assert.Equal(visitorExpected, visitorActual) assert.Equal(visitorExpected, visitorActual)
} }

100
pkg/config/parse.go Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2021 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
func ParseClientConfig(filePath string) (
cfg ClientCommonConf,
pxyCfgs map[string]ProxyConf,
visitorCfgs map[string]VisitorConf,
err error,
) {
var content []byte
content, err = GetRenderedConfFromFile(filePath)
if err != nil {
return
}
configBuffer := bytes.NewBuffer(nil)
configBuffer.Write(content)
// Parse common section.
cfg, err = UnmarshalClientConfFromIni(content)
if err != nil {
return
}
cfg.Complete()
if err = cfg.Validate(); err != nil {
err = fmt.Errorf("Parse config error: %v", err)
return
}
// Aggregate proxy configs from include files.
var buf []byte
buf, err = getIncludeContents(cfg.IncludeConfigFiles)
if err != nil {
err = fmt.Errorf("getIncludeContents error: %v", err)
return
}
configBuffer.WriteString("\n")
configBuffer.Write(buf)
// Parse all proxy and visitor configs.
pxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start)
if err != nil {
return
}
return
}
// getIncludeContents renders all configs from paths.
// files format can be a single file path or directory or regex path.
func getIncludeContents(paths []string) ([]byte, error) {
out := bytes.NewBuffer(nil)
for _, path := range paths {
absDir, err := filepath.Abs(filepath.Dir(path))
if err != nil {
return nil, err
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return nil, err
}
files, err := ioutil.ReadDir(absDir)
if err != nil {
return nil, err
}
for _, fi := range files {
if fi.IsDir() {
continue
}
absFile := filepath.Join(absDir, fi.Name())
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
tmpContent, err := GetRenderedConfFromFile(absFile)
if err != nil {
return nil, fmt.Errorf("render extra config %s error: %v", absFile, err)
}
out.Write(tmpContent)
out.WriteString("\n")
}
}
}
return out.Bytes(), nil
}

View File

@@ -143,9 +143,8 @@ type BaseProxyConf struct {
// meta info for each proxy // meta info for each proxy
Metas map[string]string `ini:"-" json:"metas"` Metas map[string]string `ini:"-" json:"metas"`
// TODO: LocalSvrConf => LocalAppConf LocalSvrConf `ini:",extends"`
LocalSvrConf `ini:",extends" json:"inline"` HealthCheckConf `ini:",extends"`
HealthCheckConf `ini:",extends" json:"inline"`
} }
type DomainConf struct { type DomainConf struct {
@@ -155,8 +154,8 @@ type DomainConf struct {
// HTTP // HTTP
type HTTPProxyConf struct { type HTTPProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends" json:"inline"` DomainConf `ini:",extends"`
Locations []string `ini:"locations" json:"locations"` Locations []string `ini:"locations" json:"locations"`
HTTPUser string `ini:"http_user" json:"http_user"` HTTPUser string `ini:"http_user" json:"http_user"`
@@ -167,27 +166,27 @@ type HTTPProxyConf struct {
// HTTPS // HTTPS
type HTTPSProxyConf struct { type HTTPSProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends" json:"inline"` DomainConf `ini:",extends"`
} }
// TCP // TCP
type TCPProxyConf struct { type TCPProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
RemotePort int `ini:"remote_port" json:"remote_port"` RemotePort int `ini:"remote_port" json:"remote_port"`
} }
// TCPMux // TCPMux
type TCPMuxProxyConf struct { type TCPMuxProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends" json:"inline"` DomainConf `ini:",extends"`
Multiplexer string `ini:"multiplexer"` Multiplexer string `ini:"multiplexer"`
} }
// STCP // STCP
type STCPProxyConf struct { type STCPProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
Role string `ini:"role" json:"role"` Role string `ini:"role" json:"role"`
Sk string `ini:"sk" json:"sk"` Sk string `ini:"sk" json:"sk"`
@@ -195,7 +194,7 @@ type STCPProxyConf struct {
// XTCP // XTCP
type XTCPProxyConf struct { type XTCPProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
Role string `ini:"role" json:"role"` Role string `ini:"role" json:"role"`
Sk string `ini:"sk" json:"sk"` Sk string `ini:"sk" json:"sk"`
@@ -203,14 +202,14 @@ type XTCPProxyConf struct {
// UDP // UDP
type UDPProxyConf struct { type UDPProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
RemotePort int `ini:"remote_port" json:"remote_port"` RemotePort int `ini:"remote_port" json:"remote_port"`
} }
// SUDP // SUDP
type SUDPProxyConf struct { type SUDPProxyConf struct {
BaseProxyConf `ini:",extends" json:"inline"` BaseProxyConf `ini:",extends"`
Role string `ini:"role" json:"role"` Role string `ini:"role" json:"role"`
Sk string `ini:"sk" json:"sk"` Sk string `ini:"sk" json:"sk"`
@@ -274,7 +273,7 @@ func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf,
conf := DefaultProxyConf(proxyType) conf := DefaultProxyConf(proxyType)
if conf == nil { if conf == nil {
return nil, fmt.Errorf("proxy [%s] type [%s] error", name, proxyType) return nil, fmt.Errorf("proxy %s has invalid type [%s]", name, proxyType)
} }
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil { if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {

View File

@@ -29,7 +29,7 @@ import (
// recommended to use GetDefaultServerConf instead of creating this object // recommended to use GetDefaultServerConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values. // directly, so that all unspecified fields have reasonable default values.
type ServerCommonConf struct { type ServerCommonConf struct {
auth.ServerConfig `ini:",extends" json:"inline"` auth.ServerConfig `ini:",extends"`
// BindAddr specifies the address that the server binds to. By default, // BindAddr specifies the address that the server binds to. By default,
// this value is "0.0.0.0". // this value is "0.0.0.0".
@@ -46,7 +46,7 @@ type ServerCommonConf struct {
// this value is 0. // this value is 0.
KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port"` KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port"`
// ProxyBindAddr specifies the address that the proxy binds to. This value // ProxyBindAddr specifies the address that the proxy binds to. This value
// may be the same as BindAddr. By default, this value is "0.0.0.0". // may be the same as BindAddr.
ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"` ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"`
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost // VhostHTTPPort specifies the port that the server listens for HTTP Vhost
// requests. If this value is 0, the server will not listen for HTTP // requests. If this value is 0, the server will not listen for HTTP
@@ -72,10 +72,10 @@ type ServerCommonConf struct {
// 0. // 0.
DashboardPort int `ini:"dashboard_port" json:"dashboard_port"` DashboardPort int `ini:"dashboard_port" json:"dashboard_port"`
// DashboardUser specifies the username that the dashboard will use for // DashboardUser specifies the username that the dashboard will use for
// login. By default, this value is "admin". // login.
DashboardUser string `ini:"dashboard_user" json:"dashboard_user"` DashboardUser string `ini:"dashboard_user" json:"dashboard_user"`
// DashboardUser specifies the password that the dashboard will use for // DashboardPwd specifies the password that the dashboard will use for
// login. By default, this value is "admin". // login.
DashboardPwd string `ini:"dashboard_pwd" json:"dashboard_pwd"` DashboardPwd string `ini:"dashboard_pwd" json:"dashboard_pwd"`
// EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} // EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port}
// in /metrics api. // in /metrics api.
@@ -174,15 +174,15 @@ func GetDefaultServerConf() ServerCommonConf {
BindPort: 7000, BindPort: 7000,
BindUDPPort: 0, BindUDPPort: 0,
KCPBindPort: 0, KCPBindPort: 0,
ProxyBindAddr: "0.0.0.0", ProxyBindAddr: "",
VhostHTTPPort: 0, VhostHTTPPort: 0,
VhostHTTPSPort: 0, VhostHTTPSPort: 0,
TCPMuxHTTPConnectPort: 0, TCPMuxHTTPConnectPort: 0,
VhostHTTPTimeout: 60, VhostHTTPTimeout: 60,
DashboardAddr: "0.0.0.0", DashboardAddr: "0.0.0.0",
DashboardPort: 0, DashboardPort: 0,
DashboardUser: "admin", DashboardUser: "",
DashboardPwd: "admin", DashboardPwd: "",
EnablePrometheus: false, EnablePrometheus: false,
AssetsDir: "", AssetsDir: "",
LogFile: "console", LogFile: "console",
@@ -208,10 +208,6 @@ func GetDefaultServerConf() ServerCommonConf {
} }
} }
func (cfg *ServerCommonConf) Check() error {
return nil
}
func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) { func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
f, err := ini.LoadSources(ini.LoadOptions{ f, err := ini.LoadSources(ini.LoadOptions{
@@ -227,7 +223,6 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
s, err := f.GetSection("common") s, err := f.GetSection("common")
if err != nil { if err != nil {
// TODO: add error info
return ServerCommonConf{}, err return ServerCommonConf{}, err
} }
@@ -242,7 +237,7 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
if allowPortStr != "" { if allowPortStr != "" {
allowPorts, err := util.ParseRangeNumbers(allowPortStr) allowPorts, err := util.ParseRangeNumbers(allowPortStr)
if err != nil { if err != nil {
return ServerCommonConf{}, fmt.Errorf("Parse conf error: allow_ports: %v", err) return ServerCommonConf{}, fmt.Errorf("invalid allow_ports: %v", err)
} }
for _, port := range allowPorts { for _, port := range allowPorts {
common.AllowPorts[int(port)] = struct{}{} common.AllowPorts[int(port)] = struct{}{}
@@ -269,6 +264,26 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
return common, nil return common, nil
} }
func (cfg *ServerCommonConf) Complete() {
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
if cfg.ProxyBindAddr == "" {
cfg.ProxyBindAddr = cfg.BindAddr
}
if cfg.TLSTrustedCaFile != "" {
cfg.TLSOnly = true
}
}
func (cfg *ServerCommonConf) Validate() error {
return nil
}
func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) { func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) {
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin.")) name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))

View File

@@ -18,7 +18,7 @@ import (
"testing" "testing"
"github.com/fatedier/frp/pkg/auth" "github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/plugin/server" plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -133,7 +133,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
}, },
MaxPoolCount: 59, MaxPoolCount: 59,
MaxPortsPerClient: 9, MaxPortsPerClient: 9,
TLSOnly: false, TLSOnly: true,
TLSCertFile: "server.crt", TLSCertFile: "server.crt",
TLSKeyFile: "server.key", TLSKeyFile: "server.key",
TLSTrustedCaFile: "ca.crt", TLSTrustedCaFile: "ca.crt",
@@ -177,11 +177,11 @@ func Test_LoadServerCommonConf(t *testing.T) {
BindAddr: "0.0.0.9", BindAddr: "0.0.0.9",
BindPort: 7009, BindPort: 7009,
BindUDPPort: 7008, BindUDPPort: 7008,
ProxyBindAddr: "0.0.0.0", ProxyBindAddr: "0.0.0.9",
VhostHTTPTimeout: 60, VhostHTTPTimeout: 60,
DashboardAddr: "0.0.0.0", DashboardAddr: "0.0.0.0",
DashboardUser: "admin", DashboardUser: "",
DashboardPwd: "admin", DashboardPwd: "",
EnablePrometheus: false, EnablePrometheus: false,
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
@@ -202,6 +202,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
for _, c := range testcases { for _, c := range testcases {
actual, err := UnmarshalServerConfFromIni(c.source) actual, err := UnmarshalServerConfFromIni(c.source)
assert.NoError(err) assert.NoError(err)
actual.Complete()
assert.Equal(c.expected, actual) assert.Equal(c.expected, actual)
} }
} }

View File

@@ -52,15 +52,15 @@ type BaseVisitorConf struct {
} }
type SUDPVisitorConf struct { type SUDPVisitorConf struct {
BaseVisitorConf `ini:",extends" json:"inline"` BaseVisitorConf `ini:",extends"`
} }
type STCPVisitorConf struct { type STCPVisitorConf struct {
BaseVisitorConf `ini:",extends" json:"inline"` BaseVisitorConf `ini:",extends"`
} }
type XTCPVisitorConf struct { type XTCPVisitorConf struct {
BaseVisitorConf `ini:",extends" json:"inline"` BaseVisitorConf `ini:",extends"`
} }
// DefaultVisitorConf creates a empty VisitorConf object by visitorType. // DefaultVisitorConf creates a empty VisitorConf object by visitorType.

View File

@@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.36.0" var version string = "0.37.0"
func Full() string { func Full() string {
return version return version

View File

@@ -248,12 +248,10 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
xl.Debug("get work connection from pool") xl.Debug("get work connection from pool")
default: default:
// no work connections available in the poll, send message to frpc to get more // no work connections available in the poll, send message to frpc to get more
err = errors.PanicToError(func() { if err = errors.PanicToError(func() {
ctl.sendCh <- &msg.ReqWorkConn{} ctl.sendCh <- &msg.ReqWorkConn{}
}) }); err != nil {
if err != nil { return nil, fmt.Errorf("control is already closed")
xl.Error("%v", err)
return
} }
select { select {
@@ -357,15 +355,15 @@ func (ctl *Control) stoper() {
ctl.allShutdown.WaitStart() ctl.allShutdown.WaitStart()
ctl.conn.Close()
ctl.readerShutdown.WaitDone()
close(ctl.readCh) close(ctl.readCh)
ctl.managerShutdown.WaitDone() ctl.managerShutdown.WaitDone()
close(ctl.sendCh) close(ctl.sendCh)
ctl.writerShutdown.WaitDone() ctl.writerShutdown.WaitDone()
ctl.conn.Close()
ctl.readerShutdown.WaitDone()
ctl.mu.Lock() ctl.mu.Lock()
defer ctl.mu.Unlock() defer ctl.mu.Unlock()

View File

@@ -60,7 +60,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
pxy.realPort, err = pxy.rc.UDPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) pxy.realPort, err = pxy.rc.UDPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil { if err != nil {
return return "", fmt.Errorf("acquire port %d error: %v", pxy.cfg.RemotePort, err)
} }
defer func() { defer func() {
if err != nil { if err != nil {

View File

@@ -265,7 +265,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
// Create nat hole controller. // Create nat hole controller.
if cfg.BindUDPPort > 0 { if cfg.BindUDPPort > 0 {
var nc *nathole.Controller var nc *nathole.Controller
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindPort)) address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindUDPPort))
nc, err = nathole.NewController(address) nc, err = nathole.NewController(address)
if err != nil { if err != nil {
err = fmt.Errorf("Create nat hole controller error, %v", err) err = fmt.Errorf("Create nat hole controller error, %v", err)

View File

@@ -3,16 +3,16 @@ package basic
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )
var connTimeout = 2 * time.Second
var _ = Describe("[Feature: Basic]", func() { var _ = Describe("[Feature: Basic]", func() {
f := framework.NewDefaultFramework() f := framework.NewDefaultFramework()
@@ -50,21 +50,21 @@ var _ = Describe("[Feature: Basic]", func() {
}{ }{
{ {
proxyName: "normal", proxyName: "normal",
portName: framework.GenPortName("Normal"), portName: port.GenName("Normal"),
}, },
{ {
proxyName: "with-encryption", proxyName: "with-encryption",
portName: framework.GenPortName("WithEncryption"), portName: port.GenName("WithEncryption"),
extraConfig: "use_encryption = true", extraConfig: "use_encryption = true",
}, },
{ {
proxyName: "with-compression", proxyName: "with-compression",
portName: framework.GenPortName("WithCompression"), portName: port.GenName("WithCompression"),
extraConfig: "use_compression = true", extraConfig: "use_compression = true",
}, },
{ {
proxyName: "with-encryption-and-compression", proxyName: "with-encryption-and-compression",
portName: framework.GenPortName("WithEncryptionAndCompression"), portName: port.GenName("WithEncryptionAndCompression"),
extraConfig: ` extraConfig: `
use_encryption = true use_encryption = true
use_compression = true use_compression = true
@@ -80,8 +80,11 @@ var _ = Describe("[Feature: Basic]", func() {
f.RunProcesses([]string{serverConf}, []string{clientConf}) f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests { for _, test := range tests {
framework.ExpectRequest(protocol, f.UsedPorts[test.portName], framework.NewRequestExpect(f).
[]byte(consts.TestString), []byte(consts.TestString), connTimeout, test.proxyName) RequestModify(framework.SetRequestProtocol(protocol)).
PortName(test.portName).
Explain(test.proxyName).
Ensure()
} }
}) })
} }
@@ -139,24 +142,24 @@ var _ = Describe("[Feature: Basic]", func() {
}{ }{
{ {
proxyName: "normal", proxyName: "normal",
bindPortName: framework.GenPortName("Normal"), bindPortName: port.GenName("Normal"),
visitorSK: correctSK, visitorSK: correctSK,
}, },
{ {
proxyName: "with-encryption", proxyName: "with-encryption",
bindPortName: framework.GenPortName("WithEncryption"), bindPortName: port.GenName("WithEncryption"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: "use_encryption = true", extraConfig: "use_encryption = true",
}, },
{ {
proxyName: "with-compression", proxyName: "with-compression",
bindPortName: framework.GenPortName("WithCompression"), bindPortName: port.GenName("WithCompression"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: "use_compression = true", extraConfig: "use_compression = true",
}, },
{ {
proxyName: "with-encryption-and-compression", proxyName: "with-encryption-and-compression",
bindPortName: framework.GenPortName("WithEncryptionAndCompression"), bindPortName: port.GenName("WithEncryptionAndCompression"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: ` extraConfig: `
use_encryption = true use_encryption = true
@@ -165,7 +168,7 @@ var _ = Describe("[Feature: Basic]", func() {
}, },
{ {
proxyName: "with-error-sk", proxyName: "with-error-sk",
bindPortName: framework.GenPortName("WithErrorSK"), bindPortName: port.GenName("WithErrorSK"),
visitorSK: wrongSK, visitorSK: wrongSK,
expectError: true, expectError: true,
}, },
@@ -182,17 +185,92 @@ var _ = Describe("[Feature: Basic]", func() {
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf}) f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf})
for _, test := range tests { for _, test := range tests {
expectResp := []byte(consts.TestString) framework.NewRequestExpect(f).
if test.expectError { RequestModify(framework.SetRequestProtocol(protocol)).
framework.ExpectRequestError(protocol, f.UsedPorts[test.bindPortName], PortName(test.bindPortName).
[]byte(consts.TestString), connTimeout, test.proxyName) Explain(test.proxyName).
continue ExpectError(test.expectError).
} Ensure()
framework.ExpectRequest(protocol, f.UsedPorts[test.bindPortName],
[]byte(consts.TestString), expectResp, connTimeout, test.proxyName)
} }
}) })
} }
}) })
Describe("TCPMUX", func() {
It("Type tcpmux", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
tcpmuxHTTPConnectPortName := port.GenName("TCPMUX")
serverConf += fmt.Sprintf(`
tcpmux_httpconnect_port = {{ .%s }}
`, tcpmuxHTTPConnectPortName)
getProxyConf := func(proxyName string, extra string) string {
return fmt.Sprintf(`
[%s]
type = tcpmux
multiplexer = httpconnect
local_port = {{ .%s }}
custom_domains = %s
`+extra, proxyName, port.GenName(proxyName), proxyName)
}
tests := []struct {
proxyName string
extraConfig string
}{
{
proxyName: "normal",
},
{
proxyName: "with-encryption",
extraConfig: "use_encryption = true",
},
{
proxyName: "with-compression",
extraConfig: "use_compression = true",
},
{
proxyName: "with-encryption-and-compression",
extraConfig: `
use_encryption = true
use_compression = true
`,
},
}
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.extraConfig) + "\n"
localServer := server.New(server.TCP, server.WithBindPort(f.AllocPort()), server.WithRespContent([]byte(test.proxyName)))
f.RunServer(port.GenName(test.proxyName), localServer)
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
// Request without HTTP connect should get error
framework.NewRequestExpect(f).
PortName(tcpmuxHTTPConnectPortName).
ExpectError(true).
Explain("request without HTTP connect expect error").
Ensure()
proxyURL := fmt.Sprintf("http://127.0.0.1:%d", f.PortByName(tcpmuxHTTPConnectPortName))
// Request with incorrect connect hostname
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.Proxy(proxyURL, "invalid")
}).ExpectError(true).Explain("request without HTTP connect expect error").Ensure()
// Request with correct connect hostname
for _, test := range tests {
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.Proxy(proxyURL, test.proxyName)
}).ExpectResp([]byte(test.proxyName)).Explain(test.proxyName).Ensure()
}
})
})
}) })

View File

@@ -6,6 +6,7 @@ import (
"github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )
@@ -16,6 +17,7 @@ type generalTestConfigures struct {
expectError bool expectError bool
} }
// defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) { func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
It(desc, func() { It(desc, func() {
serverConf := consts.DefaultServerConfig serverConf := consts.DefaultServerConfig
@@ -25,6 +27,8 @@ func defineClientServerTest(desc string, f *framework.Framework, configures *gen
%s %s
`, configures.server) `, configures.server)
tcpPortName := port.GenName("TCP")
udpPortName := port.GenName("UDP")
clientConf += fmt.Sprintf(` clientConf += fmt.Sprintf(`
%s %s
@@ -38,23 +42,15 @@ func defineClientServerTest(desc string, f *framework.Framework, configures *gen
local_port = {{ .%s }} local_port = {{ .%s }}
remote_port = {{ .%s }} remote_port = {{ .%s }}
`, configures.client, `, configures.client,
framework.TCPEchoServerPort, framework.GenPortName("TCP"), framework.TCPEchoServerPort, tcpPortName,
framework.UDPEchoServerPort, framework.GenPortName("UDP"), framework.UDPEchoServerPort, udpPortName,
) )
f.RunProcesses([]string{serverConf}, []string{clientConf}) f.RunProcesses([]string{serverConf}, []string{clientConf})
if !configures.expectError { framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
framework.ExpectTCPRequest(f.UsedPorts[framework.GenPortName("TCP")], framework.NewRequestExpect(f).RequestModify(framework.SetRequestProtocol("udp")).
[]byte(consts.TestString), []byte(consts.TestString), connTimeout, "tcp proxy") PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
framework.ExpectUDPRequest(f.UsedPorts[framework.GenPortName("UDP")],
[]byte(consts.TestString), []byte(consts.TestString), connTimeout, "udp proxy")
} else {
framework.ExpectTCPRequestError(f.UsedPorts[framework.GenPortName("TCP")],
[]byte(consts.TestString), connTimeout, "tcp proxy")
framework.ExpectUDPRequestError(f.UsedPorts[framework.GenPortName("UDP")],
[]byte(consts.TestString), connTimeout, "udp proxy")
}
}) })
} }

79
test/e2e/basic/server.go Normal file
View File

@@ -0,0 +1,79 @@
package basic
import (
"fmt"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: Server Manager]", func() {
f := framework.NewDefaultFramework()
It("Ports Whitelist", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf += `
allow_ports = 10000-20000,20002,30000-50000
`
tcpPortName := port.GenName("TCP", port.WithRangePorts(10000, 20000))
udpPortName := port.GenName("UDP", port.WithRangePorts(30000, 50000))
clientConf += fmt.Sprintf(`
[tcp-allowded-in-range]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, framework.TCPEchoServerPort, tcpPortName)
clientConf += fmt.Sprintf(`
[tcp-port-not-allowed]
type = tcp
local_port = {{ .%s }}
remote_port = 20001
`, framework.TCPEchoServerPort)
clientConf += fmt.Sprintf(`
[tcp-port-unavailable]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, framework.TCPEchoServerPort, consts.PortServerName)
clientConf += fmt.Sprintf(`
[udp-allowed-in-range]
type = udp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, framework.UDPEchoServerPort, udpPortName)
clientConf += fmt.Sprintf(`
[udp-port-not-allowed]
type = udp
local_port = {{ .%s }}
remote_port = 20003
`, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// TCP
// Allowed in range
framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).RequestModify(framework.SetRequestPort(20001)).ExpectError(true).Ensure()
// Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
// UDP
// Allowed in range
framework.NewRequestExpect(f).RequestModify(framework.SetRequestProtocol("udp")).PortName(udpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.UDP().Port(20003)
}).ExpectError(true).Ensure()
})
})

View File

@@ -49,7 +49,6 @@ func RunE2ETests(t *testing.T) {
// accepting the byte array. // accepting the byte array.
func setupSuite() { func setupSuite() {
// Run only on Ginkgo node 1 // Run only on Ginkgo node 1
// TODO
} }
// setupSuitePerGinkgoNode is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step. // setupSuitePerGinkgoNode is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.

View File

@@ -2,16 +2,14 @@ package e2e
import ( import (
"fmt" "fmt"
"time"
"github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )
var connTimeout = 2 * time.Second
var _ = Describe("[Feature: Example]", func() { var _ = Describe("[Feature: Example]", func() {
f := framework.NewDefaultFramework() f := framework.NewDefaultFramework()
@@ -20,16 +18,17 @@ var _ = Describe("[Feature: Example]", func() {
serverConf := consts.DefaultServerConfig serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig clientConf := consts.DefaultClientConfig
portName := port.GenName("TCP")
clientConf += fmt.Sprintf(` clientConf += fmt.Sprintf(`
[tcp] [tcp]
type = tcp type = tcp
local_port = {{ .%s }} local_port = {{ .%s }}
remote_port = {{ .%s }} remote_port = {{ .%s }}
`, framework.TCPEchoServerPort, framework.GenPortName("TCP")) `, framework.TCPEchoServerPort, portName)
f.RunProcesses([]string{serverConf}, []string{clientConf}) f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.ExpectTCPRequest(f.UsedPorts[framework.GenPortName("TCP")], []byte(consts.TestString), []byte(consts.TestString), connTimeout) framework.NewRequestExpect(f).PortName(portName).Ensure()
}) })
}) })
}) })

View File

@@ -1,21 +1,38 @@
package consts package consts
import (
"fmt"
"time"
"github.com/fatedier/frp/test/e2e/pkg/port"
)
const ( const (
TestString = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet." TestString = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."
DefaultTimeout = 2 * time.Second
) )
const ( var (
PortServerName = "PortServer" PortServerName string
) PortClientAdmin string
const (
DefaultServerConfig = ` DefaultServerConfig = `
[common] [common]
bind_port = {{ .PortServer }} bind_port = {{ .%s }}
log_level = trace
` `
DefaultClientConfig = ` DefaultClientConfig = `
[common] [common]
server_port = {{ .PortServer }} server_port = {{ .%s }}
log_level = trace
` `
) )
func init() {
PortServerName = port.GenName("Server")
PortClientAdmin = port.GenName("ClientAdmin")
DefaultServerConfig = fmt.Sprintf(DefaultServerConfig, port.GenName("Server"))
DefaultClientConfig = fmt.Sprintf(DefaultClientConfig, port.GenName("Server"))
}

View File

@@ -9,6 +9,7 @@ import (
"strings" "strings"
"text/template" "text/template"
"github.com/fatedier/frp/test/e2e/mock/server"
"github.com/fatedier/frp/test/e2e/pkg/port" "github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/process" "github.com/fatedier/frp/test/e2e/pkg/process"
@@ -25,11 +26,14 @@ type Options struct {
type Framework struct { type Framework struct {
TempDirectory string TempDirectory string
UsedPorts map[string]int
// ports used in this framework indexed by port name.
usedPorts map[string]int
// portAllocator to alloc port for this test case.
portAllocator *port.Allocator portAllocator *port.Allocator
// Multiple mock servers used for e2e testing. // Multiple default mock servers used for e2e testing.
mockServers *MockServers mockServers *MockServers
// To make sure that this framework cleans up after itself, no matter what, // To make sure that this framework cleans up after itself, no matter what,
@@ -44,6 +48,9 @@ type Framework struct {
serverProcesses []*process.Process serverProcesses []*process.Process
clientConfPaths []string clientConfPaths []string
clientProcesses []*process.Process clientProcesses []*process.Process
// Manual registered mock servers.
servers []*server.Server
} }
func NewDefaultFramework() *Framework { func NewDefaultFramework() *Framework {
@@ -59,6 +66,7 @@ func NewDefaultFramework() *Framework {
func NewFramework(opt Options) *Framework { func NewFramework(opt Options) *Framework {
f := &Framework{ f := &Framework{
portAllocator: port.NewAllocator(opt.FromPortIndex, opt.ToPortIndex, opt.TotalParallelNode, opt.CurrentNodeIndex-1), portAllocator: port.NewAllocator(opt.FromPortIndex, opt.ToPortIndex, opt.TotalParallelNode, opt.CurrentNodeIndex-1),
usedPorts: make(map[string]int),
} }
ginkgo.BeforeEach(f.BeforeEach) ginkgo.BeforeEach(f.BeforeEach)
@@ -107,9 +115,14 @@ func (f *Framework) AfterEach() {
f.serverProcesses = nil f.serverProcesses = nil
f.clientProcesses = nil f.clientProcesses = nil
// close mock servers // close default mock servers
f.mockServers.Close() f.mockServers.Close()
// close manual registered mock servers
for _, s := range f.servers {
s.Close()
}
// clean directory // clean directory
os.RemoveAll(f.TempDirectory) os.RemoveAll(f.TempDirectory)
f.TempDirectory = "" f.TempDirectory = ""
@@ -117,10 +130,10 @@ func (f *Framework) AfterEach() {
f.clientConfPaths = nil f.clientConfPaths = nil
// release used ports // release used ports
for _, port := range f.UsedPorts { for _, port := range f.usedPorts {
f.portAllocator.Release(port) f.portAllocator.Release(port)
} }
f.UsedPorts = nil f.usedPorts = make(map[string]int)
} }
var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`) var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`)
@@ -151,16 +164,16 @@ func (f *Framework) genPortsFromTemplates(templates []string) (ports map[string]
}() }()
for name := range ports { for name := range ports {
port := f.portAllocator.Get() port := f.portAllocator.GetByName(name)
if port <= 0 { if port <= 0 {
return nil, fmt.Errorf("can't allocate port") return nil, fmt.Errorf("can't allocate port")
} }
ports[name] = port ports[name] = port
} }
return return
} }
// RenderTemplates alloc all ports for port names placeholder.
func (f *Framework) RenderTemplates(templates []string) (outs []string, ports map[string]int, err error) { func (f *Framework) RenderTemplates(templates []string) (outs []string, ports map[string]int, err error) {
ports, err = f.genPortsFromTemplates(templates) ports, err = f.genPortsFromTemplates(templates)
if err != nil { if err != nil {
@@ -172,6 +185,10 @@ func (f *Framework) RenderTemplates(templates []string) (outs []string, ports ma
params[name] = port params[name] = port
} }
for name, port := range f.usedPorts {
params[name] = port
}
for _, t := range templates { for _, t := range templates {
tmpl, err := template.New("").Parse(t) tmpl, err := template.New("").Parse(t)
if err != nil { if err != nil {
@@ -185,3 +202,26 @@ func (f *Framework) RenderTemplates(templates []string) (outs []string, ports ma
} }
return return
} }
func (f *Framework) PortByName(name string) int {
return f.usedPorts[name]
}
func (f *Framework) AllocPort() int {
port := f.portAllocator.Get()
ExpectTrue(port > 0, "alloc port failed")
return port
}
func (f *Framework) ReleasePort(port int) {
f.portAllocator.Release(port)
}
func (f *Framework) RunServer(portName string, s *server.Server) {
f.servers = append(f.servers, s)
if s.BindPort() > 0 {
f.usedPorts[portName] = s.BindPort()
}
err := s.Run()
ExpectNoError(err, portName)
}

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/fatedier/frp/test/e2e/mock/echoserver" "github.com/fatedier/frp/test/e2e/mock/server"
"github.com/fatedier/frp/test/e2e/pkg/port" "github.com/fatedier/frp/test/e2e/pkg/port"
) )
@@ -15,36 +15,22 @@ const (
) )
type MockServers struct { type MockServers struct {
tcpEchoServer *echoserver.Server tcpEchoServer *server.Server
udpEchoServer *echoserver.Server udpEchoServer *server.Server
udsEchoServer *echoserver.Server udsEchoServer *server.Server
} }
func NewMockServers(portAllocator *port.Allocator) *MockServers { func NewMockServers(portAllocator *port.Allocator) *MockServers {
s := &MockServers{} s := &MockServers{}
tcpPort := portAllocator.Get() tcpPort := portAllocator.Get()
udpPort := portAllocator.Get() udpPort := portAllocator.Get()
s.tcpEchoServer = echoserver.New(echoserver.Options{ s.tcpEchoServer = server.New(server.TCP, server.WithBindPort(tcpPort), server.WithEchoMode(true))
Type: echoserver.TCP, s.udpEchoServer = server.New(server.UDP, server.WithBindPort(udpPort), server.WithEchoMode(true))
BindAddr: "127.0.0.1",
BindPort: int32(tcpPort),
RepeatNum: 1,
})
s.udpEchoServer = echoserver.New(echoserver.Options{
Type: echoserver.UDP,
BindAddr: "127.0.0.1",
BindPort: int32(udpPort),
RepeatNum: 1,
})
udsIndex := portAllocator.Get() udsIndex := portAllocator.Get()
udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex) udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex)
os.Remove(udsAddr) os.Remove(udsAddr)
s.udsEchoServer = echoserver.New(echoserver.Options{ s.udsEchoServer = server.New(server.Unix, server.WithBindAddr(udsAddr), server.WithEchoMode(true))
Type: echoserver.Unix,
BindAddr: udsAddr,
RepeatNum: 1,
})
return s return s
} }
@@ -65,14 +51,14 @@ func (m *MockServers) Close() {
m.tcpEchoServer.Close() m.tcpEchoServer.Close()
m.udpEchoServer.Close() m.udpEchoServer.Close()
m.udsEchoServer.Close() m.udsEchoServer.Close()
os.Remove(m.udsEchoServer.GetOptions().BindAddr) os.Remove(m.udsEchoServer.BindAddr())
} }
func (m *MockServers) GetTemplateParams() map[string]interface{} { func (m *MockServers) GetTemplateParams() map[string]interface{} {
ret := make(map[string]interface{}) ret := make(map[string]interface{})
ret[TCPEchoServerPort] = m.tcpEchoServer.GetOptions().BindPort ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
ret[UDPEchoServerPort] = m.udpEchoServer.GetOptions().BindPort ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
ret[UDSEchoServerAddr] = m.udsEchoServer.GetOptions().BindAddr ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
return ret return ret
} }

View File

@@ -28,7 +28,9 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
ExpectNoError(err) ExpectNoError(err)
ExpectTrue(len(templates) > 0) ExpectTrue(len(templates) > 0)
f.UsedPorts = ports for name, port := range ports {
f.usedPorts[name] = port
}
for i := range serverTemplates { for i := range serverTemplates {
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i)) path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
@@ -40,8 +42,8 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
f.serverProcesses = append(f.serverProcesses, p) f.serverProcesses = append(f.serverProcesses, p)
err = p.Start() err = p.Start()
ExpectNoError(err) ExpectNoError(err)
time.Sleep(500 * time.Millisecond)
} }
time.Sleep(time.Second)
for i := range clientTemplates { for i := range clientTemplates {
index := i + len(serverTemplates) index := i + len(serverTemplates)
@@ -56,4 +58,5 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
ExpectNoError(err) ExpectNoError(err)
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
time.Sleep(500 * time.Millisecond)
} }

View File

@@ -1,51 +1,90 @@
package framework package framework
import ( import (
"time" "github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/request" "github.com/fatedier/frp/test/e2e/pkg/request"
) )
func ExpectRequest(protocol string, port int, in, out []byte, timeout time.Duration, explain ...interface{}) { func SetRequestProtocol(protocol string) func(*request.Request) {
switch protocol { return func(r *request.Request) {
case "tcp": r.Protocol(protocol)
ExpectTCPRequest(port, in, out, timeout, explain...)
case "udp":
ExpectUDPRequest(port, in, out, timeout, explain...)
default:
Failf("ExpectRequest not support protocol: %s", protocol)
} }
} }
func ExpectRequestError(protocol string, port int, in []byte, timeout time.Duration, explain ...interface{}) { func SetRequestPort(port int) func(*request.Request) {
switch protocol { return func(r *request.Request) {
case "tcp": r.Port(port)
ExpectTCPRequestError(port, in, timeout, explain...)
case "udp":
ExpectUDPRequestError(port, in, timeout, explain...)
default:
Failf("ExpectRequestError not support protocol: %s", protocol)
} }
} }
func ExpectTCPRequest(port int, in, out []byte, timeout time.Duration, explain ...interface{}) { // NewRequest return a default TCP request with default timeout and content.
res, err := request.SendTCPRequest(port, in, timeout) func NewRequest() *request.Request {
ExpectNoError(err, explain...) return request.New().
ExpectEqual(string(out), res, explain...) Timeout(consts.DefaultTimeout).
Body([]byte(consts.TestString))
} }
func ExpectTCPRequestError(port int, in []byte, timeout time.Duration, explain ...interface{}) { func ExpectResponse(req *request.Request, expectResp []byte, explain ...interface{}) {
_, err := request.SendTCPRequest(port, in, timeout) ret, err := req.Do()
ExpectNoError(err, explain...)
ExpectEqualValues(expectResp, ret, explain...)
}
func ExpectResponseError(req *request.Request, explain ...interface{}) {
_, err := req.Do()
ExpectError(err, explain...) ExpectError(err, explain...)
} }
func ExpectUDPRequest(port int, in, out []byte, timeout time.Duration, explain ...interface{}) { type RequestExpect struct {
res, err := request.SendUDPRequest(port, in, timeout) req *request.Request
ExpectNoError(err, explain...)
ExpectEqual(string(out), res, explain...) f *Framework
expectResp []byte
expectError bool
explain []interface{}
} }
func ExpectUDPRequestError(port int, in []byte, timeout time.Duration, explain ...interface{}) { func NewRequestExpect(f *Framework) *RequestExpect {
_, err := request.SendUDPRequest(port, in, timeout) return &RequestExpect{
ExpectError(err, explain...) req: NewRequest(),
f: f,
expectResp: []byte(consts.TestString),
expectError: false,
explain: make([]interface{}, 0),
}
}
func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect {
f(e.req)
return e
}
func (e *RequestExpect) PortName(name string) *RequestExpect {
if e.f != nil {
e.req.Port(e.f.PortByName(name))
}
return e
}
func (e *RequestExpect) ExpectResp(resp []byte) *RequestExpect {
e.expectResp = resp
return e
}
func (e *RequestExpect) ExpectError(expectErr bool) *RequestExpect {
e.expectError = expectErr
return e
}
func (e *RequestExpect) Explain(explain ...interface{}) *RequestExpect {
e.explain = explain
return e
}
func (e *RequestExpect) Ensure() {
if e.expectError {
ExpectResponseError(e.req, e.explain...)
} else {
ExpectResponse(e.req, e.expectResp, e.explain...)
}
} }

View File

@@ -12,7 +12,3 @@ func init() {
uuid, _ := uuid.NewUUID() uuid, _ := uuid.NewUUID()
RunID = uuid.String() RunID = uuid.String()
} }
func GenPortName(name string) string {
return "Port" + name
}

View File

@@ -1,111 +0,0 @@
package echoserver
import (
"fmt"
"net"
"strings"
fnet "github.com/fatedier/frp/pkg/util/net"
)
type ServerType string
const (
TCP ServerType = "tcp"
UDP ServerType = "udp"
Unix ServerType = "unix"
)
type Options struct {
Type ServerType
BindAddr string
BindPort int32
RepeatNum int
SpecifiedResponse string
}
type Server struct {
opt Options
l net.Listener
}
func New(opt Options) *Server {
if opt.Type == "" {
opt.Type = TCP
}
if opt.BindAddr == "" {
opt.BindAddr = "127.0.0.1"
}
if opt.RepeatNum <= 0 {
opt.RepeatNum = 1
}
return &Server{
opt: opt,
}
}
func (s *Server) GetOptions() Options {
return s.opt
}
func (s *Server) Run() error {
if err := s.initListener(); err != nil {
return err
}
go func() {
for {
c, err := s.l.Accept()
if err != nil {
return
}
go s.handle(c)
}
}()
return nil
}
func (s *Server) Close() error {
if s.l != nil {
return s.l.Close()
}
return nil
}
func (s *Server) initListener() (err error) {
switch s.opt.Type {
case TCP:
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.opt.BindAddr, s.opt.BindPort))
case UDP:
s.l, err = fnet.ListenUDP(s.opt.BindAddr, int(s.opt.BindPort))
case Unix:
s.l, err = net.Listen("unix", s.opt.BindAddr)
default:
return fmt.Errorf("unknown server type: %s", s.opt.Type)
}
if err != nil {
return
}
return nil
}
func (s *Server) handle(c net.Conn) {
defer c.Close()
buf := make([]byte, 2048)
for {
n, err := c.Read(buf)
if err != nil {
return
}
var response string
if len(s.opt.SpecifiedResponse) > 0 {
response = s.opt.SpecifiedResponse
} else {
response = strings.Repeat(string(buf[:n]), s.opt.RepeatNum)
}
c.Write([]byte(response))
}
}

View File

@@ -0,0 +1,142 @@
package server
import (
"fmt"
"net"
libnet "github.com/fatedier/frp/pkg/util/net"
)
type ServerType string
const (
TCP ServerType = "tcp"
UDP ServerType = "udp"
Unix ServerType = "unix"
)
type Server struct {
netType ServerType
bindAddr string
bindPort int
respContent []byte
bufSize int64
echoMode bool
l net.Listener
}
type Option func(*Server) *Server
func New(netType ServerType, options ...Option) *Server {
s := &Server{
netType: netType,
bindAddr: "127.0.0.1",
bufSize: 2048,
}
for _, option := range options {
s = option(s)
}
return s
}
func WithBindAddr(addr string) Option {
return func(s *Server) *Server {
s.bindAddr = addr
return s
}
}
func WithBindPort(port int) Option {
return func(s *Server) *Server {
s.bindPort = port
return s
}
}
func WithRespContent(content []byte) Option {
return func(s *Server) *Server {
s.respContent = content
return s
}
}
func WithBufSize(bufSize int64) Option {
return func(s *Server) *Server {
s.bufSize = bufSize
return s
}
}
func WithEchoMode(echoMode bool) Option {
return func(s *Server) *Server {
s.echoMode = echoMode
return s
}
}
func (s *Server) Run() error {
if err := s.initListener(); err != nil {
return err
}
go func() {
for {
c, err := s.l.Accept()
if err != nil {
return
}
go s.handle(c)
}
}()
return nil
}
func (s *Server) Close() error {
if s.l != nil {
return s.l.Close()
}
return nil
}
func (s *Server) initListener() (err error) {
switch s.netType {
case TCP:
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
case UDP:
s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
case Unix:
s.l, err = net.Listen("unix", s.bindAddr)
default:
return fmt.Errorf("unknown server type: %s", s.netType)
}
return err
}
func (s *Server) handle(c net.Conn) {
defer c.Close()
buf := make([]byte, s.bufSize)
for {
n, err := c.Read(buf)
if err != nil {
return
}
if s.echoMode {
c.Write(buf[:n])
} else {
c.Write(s.respContent)
}
}
}
func (s *Server) BindAddr() string {
return s.bindAddr
}
func (s *Server) BindPort() int {
return s.bindPort
}

View File

@@ -3,6 +3,7 @@ package port
import ( import (
"fmt" "fmt"
"net" "net"
"sync"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
) )
@@ -10,6 +11,7 @@ import (
type Allocator struct { type Allocator struct {
reserved sets.Int reserved sets.Int
used sets.Int used sets.Int
mu sync.Mutex
} }
// NewAllocator return a port allocator for testing. // NewAllocator return a port allocator for testing.
@@ -29,13 +31,31 @@ func NewAllocator(from int, to int, mod int, index int) *Allocator {
} }
func (pa *Allocator) Get() int { func (pa *Allocator) Get() int {
return pa.GetByName("")
}
func (pa *Allocator) GetByName(portName string) int {
var builder *nameBuilder
if portName == "" {
builder = &nameBuilder{}
} else {
var err error
builder, err = unmarshalFromName(portName)
if err != nil {
fmt.Println(err, portName)
return 0
}
}
pa.mu.Lock()
defer pa.mu.Unlock()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
port, _ := pa.reserved.PopAny() port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo)
if port == 0 { if port == 0 {
return 0 return 0
} }
// TODO: Distinguish between TCP and UDP
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil { if err != nil {
// Maybe not controlled by us, mark it used. // Maybe not controlled by us, mark it used.
@@ -43,13 +63,49 @@ func (pa *Allocator) Get() int {
continue continue
} }
l.Close() l.Close()
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
continue
}
udpConn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
// Maybe not controlled by us, mark it used.
pa.used.Insert(port)
continue
}
udpConn.Close()
pa.used.Insert(port) pa.used.Insert(port)
return port return port
} }
return 0 return 0
} }
func (pa *Allocator) getByRange(from, to int) int {
if from <= 0 {
port, _ := pa.reserved.PopAny()
return port
}
// choose a random port between from - to
ports := pa.reserved.UnsortedList()
for _, port := range ports {
if port >= from && port <= to {
return port
}
}
return 0
}
func (pa *Allocator) Release(port int) { func (pa *Allocator) Release(port int) {
if port <= 0 {
return
}
pa.mu.Lock()
defer pa.mu.Unlock()
if pa.used.Has(port) { if pa.used.Has(port) {
pa.used.Delete(port) pa.used.Delete(port)
pa.reserved.Insert(port) pa.reserved.Insert(port)

69
test/e2e/pkg/port/util.go Normal file
View File

@@ -0,0 +1,69 @@
package port
import (
"fmt"
"strconv"
"strings"
)
const (
NameDelimiter = "_"
)
type NameOption func(*nameBuilder) *nameBuilder
type nameBuilder struct {
name string
rangePortFrom int
rangePortTo int
}
func unmarshalFromName(name string) (*nameBuilder, error) {
var builder nameBuilder
arrs := strings.Split(name, NameDelimiter)
switch len(arrs) {
case 2:
builder.name = arrs[1]
case 4:
builder.name = arrs[1]
if fromPort, err := strconv.Atoi(arrs[2]); err != nil {
return nil, fmt.Errorf("error range port from")
} else {
builder.rangePortFrom = fromPort
}
if toPort, err := strconv.Atoi(arrs[3]); err != nil {
return nil, fmt.Errorf("error range port to")
} else {
builder.rangePortTo = toPort
}
default:
return nil, fmt.Errorf("error port name format")
}
return &builder, nil
}
func (builder *nameBuilder) String() string {
name := fmt.Sprintf("Port%s%s", NameDelimiter, builder.name)
if builder.rangePortFrom > 0 && builder.rangePortTo > 0 && builder.rangePortTo > builder.rangePortFrom {
name += fmt.Sprintf("%s%d%s%d", NameDelimiter, builder.rangePortFrom, NameDelimiter, builder.rangePortTo)
}
return name
}
func WithRangePorts(from, to int) NameOption {
return func(builder *nameBuilder) *nameBuilder {
builder.rangePortFrom = from
builder.rangePortTo = to
return builder
}
}
func GenName(name string, options ...NameOption) string {
name = strings.ReplaceAll(name, "-", "")
name = strings.ReplaceAll(name, "_", "")
builder := &nameBuilder{name: name}
for _, option := range options {
builder = option(builder)
}
return builder.String()
}

View File

@@ -4,12 +4,108 @@ import (
"fmt" "fmt"
"net" "net"
"time" "time"
libnet "github.com/fatedier/golib/net"
) )
func SendTCPRequest(port int, content []byte, timeout time.Duration) (string, error) { type Request struct {
protocol string
addr string
port int
body []byte
timeout time.Duration
proxyURL string
proxyHost string
}
func New() *Request {
return &Request{
protocol: "tcp",
}
}
func (r *Request) Protocol(protocol string) *Request {
r.protocol = protocol
return r
}
func (r *Request) TCP() *Request {
r.protocol = "tcp"
return r
}
func (r *Request) UDP() *Request {
r.protocol = "udp"
return r
}
func (r *Request) Proxy(url, host string) *Request {
r.proxyURL = url
r.proxyHost = host
return r
}
func (r *Request) Addr(addr string) *Request {
r.addr = addr
return r
}
func (r *Request) Port(port int) *Request {
r.port = port
return r
}
func (r *Request) Timeout(timeout time.Duration) *Request {
r.timeout = timeout
return r
}
func (r *Request) Body(content []byte) *Request {
r.body = content
return r
}
func (r *Request) Do() ([]byte, error) {
var (
conn net.Conn
err error
)
if len(r.proxyURL) > 0 {
if r.protocol != "tcp" {
return nil, fmt.Errorf("only tcp protocol is allowed for proxy")
}
conn, err = libnet.DialTcpByProxy(r.proxyURL, r.proxyHost)
if err != nil {
return nil, err
}
} else {
if r.addr == "" {
r.addr = fmt.Sprintf("127.0.0.1:%d", r.port)
}
switch r.protocol {
case "tcp":
conn, err = net.Dial("tcp", r.addr)
case "udp":
conn, err = net.Dial("udp", r.addr)
default:
return nil, fmt.Errorf("invalid protocol")
}
if err != nil {
return nil, err
}
}
defer conn.Close()
if r.timeout > 0 {
conn.SetDeadline(time.Now().Add(r.timeout))
}
return sendRequestByConn(conn, r.body)
}
func SendTCPRequest(port int, content []byte, timeout time.Duration) ([]byte, error) {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil { if err != nil {
return "", fmt.Errorf("connect to tcp server error: %v", err) return nil, fmt.Errorf("connect to tcp server error: %v", err)
} }
defer c.Close() defer c.Close()
@@ -17,10 +113,10 @@ func SendTCPRequest(port int, content []byte, timeout time.Duration) (string, er
return sendRequestByConn(c, content) return sendRequestByConn(c, content)
} }
func SendUDPRequest(port int, content []byte, timeout time.Duration) (string, error) { func SendUDPRequest(port int, content []byte, timeout time.Duration) ([]byte, error) {
c, err := net.Dial("udp", fmt.Sprintf("127.0.0.1:%d", port)) c, err := net.Dial("udp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil { if err != nil {
return "", fmt.Errorf("connect to udp server error: %v", err) return nil, fmt.Errorf("connect to udp server error: %v", err)
} }
defer c.Close() defer c.Close()
@@ -28,13 +124,16 @@ func SendUDPRequest(port int, content []byte, timeout time.Duration) (string, er
return sendRequestByConn(c, content) return sendRequestByConn(c, content)
} }
func sendRequestByConn(c net.Conn, content []byte) (string, error) { func sendRequestByConn(c net.Conn, content []byte) ([]byte, error) {
c.Write(content) _, err := c.Write(content)
if err != nil {
return nil, fmt.Errorf("write error: %v", err)
}
buf := make([]byte, 2048) buf := make([]byte, 2048)
n, err := c.Read(buf) n, err := c.Read(buf)
if err != nil { if err != nil {
return "", fmt.Errorf("read error: %v", err) return nil, fmt.Errorf("read error: %v", err)
} }
return string(buf[:n]), nil return buf[:n], nil
} }

View File

@@ -2,16 +2,14 @@ package plugin
import ( import (
"fmt" "fmt"
"time"
"github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )
var connTimeout = 2 * time.Second
var _ = Describe("[Feature: Client-Plugins]", func() { var _ = Describe("[Feature: Client-Plugins]", func() {
f := framework.NewDefaultFramework() f := framework.NewDefaultFramework()
@@ -37,21 +35,21 @@ var _ = Describe("[Feature: Client-Plugins]", func() {
}{ }{
{ {
proxyName: "normal", proxyName: "normal",
portName: framework.GenPortName("Normal"), portName: port.GenName("Normal"),
}, },
{ {
proxyName: "with-encryption", proxyName: "with-encryption",
portName: framework.GenPortName("WithEncryption"), portName: port.GenName("WithEncryption"),
extraConfig: "use_encryption = true", extraConfig: "use_encryption = true",
}, },
{ {
proxyName: "with-compression", proxyName: "with-compression",
portName: framework.GenPortName("WithCompression"), portName: port.GenName("WithCompression"),
extraConfig: "use_compression = true", extraConfig: "use_compression = true",
}, },
{ {
proxyName: "with-encryption-and-compression", proxyName: "with-encryption-and-compression",
portName: framework.GenPortName("WithEncryptionAndCompression"), portName: port.GenName("WithEncryptionAndCompression"),
extraConfig: ` extraConfig: `
use_encryption = true use_encryption = true
use_compression = true use_compression = true
@@ -67,9 +65,11 @@ var _ = Describe("[Feature: Client-Plugins]", func() {
f.RunProcesses([]string{serverConf}, []string{clientConf}) f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests { for _, test := range tests {
framework.ExpectTCPRequest(f.UsedPorts[test.portName], framework.ExpectResponse(
[]byte(consts.TestString), []byte(consts.TestString), framework.NewRequest().Port(f.PortByName(test.portName)),
connTimeout, test.proxyName) []byte(consts.TestString),
test.proxyName,
)
} }
}) })
}) })

View File

@@ -6,11 +6,9 @@ package e2e
// and then the function that only runs on the first Ginkgo node. // and then the function that only runs on the first Ginkgo node.
func CleanupSuite() { func CleanupSuite() {
// Run on all Ginkgo nodes // Run on all Ginkgo nodes
// TODO
} }
// AfterSuiteActions are actions that are run on ginkgo's SynchronizedAfterSuite // AfterSuiteActions are actions that are run on ginkgo's SynchronizedAfterSuite
func AfterSuiteActions() { func AfterSuiteActions() {
// Run only Ginkgo on node 1 // Run only Ginkgo on node 1
// TODO
} }

View File

@@ -132,13 +132,6 @@ custom_domains = test6.frp.com
host_header_rewrite = test6.frp.com host_header_rewrite = test6.frp.com
header_X-From-Where = frp header_X-From-Where = frp
[tcpmuxhttpconnect]
type = tcpmux
multiplexer = httpconnect
local_ip = 127.0.0.1
local_port = 10701
custom_domains = tunnel1
[wildcard_http] [wildcard_http]
type = http type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1

View File

@@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings"
"testing" "testing"
"time" "time"
@@ -13,7 +12,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/server/ports"
"github.com/fatedier/frp/tests/consts" "github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/mock" "github.com/fatedier/frp/tests/mock"
"github.com/fatedier/frp/tests/util" "github.com/fatedier/frp/tests/util"
@@ -155,17 +153,6 @@ func TestHTTP(t *testing.T) {
} }
} }
func TestTCPMux(t *testing.T) {
assert := assert.New(t)
conn, err := gnet.DialTcpByProxy(fmt.Sprintf("http://%s:%d", "127.0.0.1", consts.TEST_TCP_MUX_FRP_PORT), "tunnel1")
if assert.NoError(err) {
res, err := util.SendTCPMsgByConn(conn, consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
}
func TestWebSocket(t *testing.T) { func TestWebSocket(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
@@ -182,39 +169,6 @@ func TestWebSocket(t *testing.T) {
assert.Equal(consts.TEST_HTTP_NORMAL_STR, string(msg)) assert.Equal(consts.TEST_HTTP_NORMAL_STR, string(msg))
} }
func TestAllowPorts(t *testing.T) {
assert := assert.New(t)
// Port not allowed
status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTCPPortNotAllowed)
if assert.NoError(err) {
assert.Equal(proxy.ProxyPhaseStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortNotAllowed.Error()))
}
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyUDPPortNotAllowed)
if assert.NoError(err) {
assert.Equal(proxy.ProxyPhaseStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortNotAllowed.Error()))
}
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTCPPortUnavailable)
if assert.NoError(err) {
assert.Equal(proxy.ProxyPhaseStartErr, status.Status)
assert.True(strings.Contains(status.Err, ports.ErrPortUnAvailable.Error()))
}
// Port normal
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTCPPortNormal)
if assert.NoError(err) {
assert.Equal(proxy.ProxyPhaseRunning, status.Status)
}
status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyUDPPortNormal)
if assert.NoError(err) {
assert.Equal(proxy.ProxyPhaseRunning, status.Status)
}
}
func TestRandomPort(t *testing.T) { func TestRandomPort(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// tcp // tcp

View File

@@ -53,6 +53,9 @@
for (let s of json.stcp) { for (let s of json.stcp) {
this.status.push(s) this.status.push(s)
} }
for (let s of json.sudp) {
this.status.push(s)
}
for (let s of json.xtcp) { for (let s of json.xtcp) {
this.status.push(s) this.status.push(s)
} }

View File

@@ -17,6 +17,7 @@
<el-menu-item index="/proxies/http">HTTP</el-menu-item> <el-menu-item index="/proxies/http">HTTP</el-menu-item>
<el-menu-item index="/proxies/https">HTTPS</el-menu-item> <el-menu-item index="/proxies/https">HTTPS</el-menu-item>
<el-menu-item index="/proxies/stcp">STCP</el-menu-item> <el-menu-item index="/proxies/stcp">STCP</el-menu-item>
<el-menu-item index="/proxies/sudp">SUDP</el-menu-item>
</el-submenu> </el-submenu>
<el-menu-item index="">Help</el-menu-item> <el-menu-item index="">Help</el-menu-item>
</el-menu> </el-menu>

View File

@@ -78,7 +78,7 @@
}, },
methods: { methods: {
fetchData() { fetchData() {
fetch('/api/serverinfo', {credentials: 'include'}) fetch('../api/serverinfo', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {
@@ -122,6 +122,9 @@
if (json.proxy_type_count.stcp != null) { if (json.proxy_type_count.stcp != null) {
this.proxy_counts += json.proxy_type_count.stcp this.proxy_counts += json.proxy_type_count.stcp
} }
if (json.proxy_type_count.sudp != null) {
this.proxy_counts += json.proxy_type_count.sudp
}
if (json.proxy_type_count.xtcp != null) { if (json.proxy_type_count.xtcp != null) {
this.proxy_counts += json.proxy_type_count.xtcp this.proxy_counts += json.proxy_type_count.xtcp
} }

View File

@@ -116,7 +116,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/serverinfo', {credentials: 'include'}) fetch('../api/serverinfo', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {
@@ -125,7 +125,7 @@
if (this.vhost_http_port == null || this.vhost_http_port == 0) { if (this.vhost_http_port == null || this.vhost_http_port == 0) {
return return
} else { } else {
fetch('/api/proxy/http', {credentials: 'include'}) fetch('../api/proxy/http', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@@ -111,7 +111,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/serverinfo', {credentials: 'include'}) fetch('../api/serverinfo', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {
@@ -120,7 +120,7 @@
if (this.vhost_https_port == null || this.vhost_https_port == 0) { if (this.vhost_https_port == null || this.vhost_https_port == 0) {
return return
} else { } else {
fetch('/api/proxy/https', {credentials: 'include'}) fetch('../api/proxy/https', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@@ -95,7 +95,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/proxy/stcp', {credentials: 'include'}) fetch('../api/proxy/stcp', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@@ -0,0 +1,116 @@
<template>
<div>
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%">
<el-table-column type="expand">
<template slot-scope="props">
<el-popover
ref="popover4"
placement="right"
width="600"
style="margin-left:0px"
trigger="click">
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
</el-popover>
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom:10px" @click="fetchData2">Traffic Statistics</el-button>
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="Name">
<span>{{ props.row.name }}</span>
</el-form-item>
<el-form-item label="Type">
<span>{{ props.row.type }}</span>
</el-form-item>
<el-form-item label="Encryption">
<span>{{ props.row.encryption }}</span>
</el-form-item>
<el-form-item label="Compression">
<span>{{ props.row.compression }}</span>
</el-form-item>
<el-form-item label="Last Start">
<span>{{ props.row.last_start_time }}</span>
</el-form-item>
<el-form-item label="Last Close">
<span>{{ props.row.last_close_time }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column
label="Name"
prop="name"
sortable>
</el-table-column>
<el-table-column
label="Connections"
prop="conns"
sortable>
</el-table-column>
<el-table-column
label="Traffic In"
prop="traffic_in"
:formatter="formatTrafficIn"
sortable>
</el-table-column>
<el-table-column
label="Traffic Out"
prop="traffic_out"
:formatter="formatTrafficOut"
sortable>
</el-table-column>
<el-table-column
label="status"
prop="status"
sortable>
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { SudpProxy } from '../utils/proxy.js'
export default {
data() {
return {
proxies: null
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
fetchData() {
fetch('../api/proxy/sudp', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.proxies = new Array()
for (let proxyStats of json.proxies) {
this.proxies.push(new SudpProxy(proxyStats))
}
})
}
},
components: {
'my-traffic-chart': Traffic
}
}
</script>
<style>
</style>

View File

@@ -103,7 +103,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/proxy/tcp', {credentials: 'include'}) fetch('../api/proxy/tcp', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@@ -105,7 +105,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/proxy/udp', {credentials: 'include'}) fetch('../api/proxy/udp', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@@ -14,7 +14,7 @@ export default {
//}, //},
methods: { methods: {
fetchData() { fetchData() {
let url = '/api/traffic/' + this.proxy_name let url = '../api/traffic/' + this.proxy_name
fetch(url, {credentials: 'include'}) fetch(url, {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()

View File

@@ -6,6 +6,7 @@ import ProxiesUdp from '../components/ProxiesUdp.vue'
import ProxiesHttp from '../components/ProxiesHttp.vue' import ProxiesHttp from '../components/ProxiesHttp.vue'
import ProxiesHttps from '../components/ProxiesHttps.vue' import ProxiesHttps from '../components/ProxiesHttps.vue'
import ProxiesStcp from '../components/ProxiesStcp.vue' import ProxiesStcp from '../components/ProxiesStcp.vue'
import ProxiesSudp from '../components/ProxiesSudp.vue'
Vue.use(Router) Vue.use(Router)
@@ -34,5 +35,9 @@ export default new Router({
path: '/proxies/stcp', path: '/proxies/stcp',
name: 'ProxiesStcp', name: 'ProxiesStcp',
component: ProxiesStcp component: ProxiesStcp
}, {
path: '/proxies/sudp',
name: 'ProxiesSudp',
component: ProxiesSudp
}] }]
}) })

View File

@@ -48,24 +48,6 @@ function DrawTrafficChart(elementId, trafficIn, trafficOut) {
} }
function DrawProxyChart(elementId, serverInfo) { function DrawProxyChart(elementId, serverInfo) {
if (serverInfo.proxy_type_count.tcp == null) {
serverInfo.proxy_type_count.tcp = 0
}
if (serverInfo.proxy_type_count.udp == null) {
serverInfo.proxy_type_count.udp = 0
}
if (serverInfo.proxy_type_count.http == null) {
serverInfo.proxy_type_count.http = 0
}
if (serverInfo.proxy_type_count.https == null) {
serverInfo.proxy_type_count.https = 0
}
if (serverInfo.proxy_type_count.stcp == null) {
serverInfo.proxy_type_count.stcp = 0
}
if (serverInfo.proxy_type_count.xtcp == null) {
serverInfo.proxy_type_count.xtcp = 0
}
let myChart = echarts.init(document.getElementById(elementId), 'macarons') let myChart = echarts.init(document.getElementById(elementId), 'macarons')
myChart.showLoading() myChart.showLoading()
@@ -85,25 +67,7 @@ function DrawProxyChart(elementId, serverInfo) {
type: 'pie', type: 'pie',
radius: '55%', radius: '55%',
center: ['50%', '60%'], center: ['50%', '60%'],
data: [{ data: [],
value: serverInfo.proxy_type_count.tcp,
name: 'TCP'
}, {
value: serverInfo.proxy_type_count.udp,
name: 'UDP'
}, {
value: serverInfo.proxy_type_count.http,
name: 'HTTP'
}, {
value: serverInfo.proxy_type_count.https,
name: 'HTTPS'
}, {
value: serverInfo.proxy_type_count.stcp,
name: 'STCP'
}, {
value: serverInfo.proxy_type_count.xtcp,
name: 'XTCP'
}],
itemStyle: { itemStyle: {
emphasis: { emphasis: {
shadowBlur: 10, shadowBlur: 10,
@@ -113,6 +77,29 @@ function DrawProxyChart(elementId, serverInfo) {
} }
}] }]
}; };
if (serverInfo.proxy_type_count.tcp != null && serverInfo.proxy_type_count.tcp != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.tcp, name: 'TCP'})
}
if (serverInfo.proxy_type_count.udp != null && serverInfo.proxy_type_count.udp != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.udp, name: 'UDP'})
}
if (serverInfo.proxy_type_count.http != null && serverInfo.proxy_type_count.http != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.http, name: 'HTTP'})
}
if (serverInfo.proxy_type_count.https != null && serverInfo.proxy_type_count.https != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.https, name: 'HTTPS'})
}
if (serverInfo.proxy_type_count.stcp != null && serverInfo.proxy_type_count.stcp != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.stcp, name: 'STCP'})
}
if (serverInfo.proxy_type_count.sudp != null && serverInfo.proxy_type_count.sudp != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.sudp, name: 'SUDP'})
}
if (serverInfo.proxy_type_count.xtcp != null && serverInfo.proxy_type_count.xtcp != 0) {
option.series[0].data.push({value: serverInfo.proxy_type_count.xtcp, name: 'XTCP'})
}
myChart.setOption(option); myChart.setOption(option);
myChart.hideLoading() myChart.hideLoading()
} }

View File

@@ -54,8 +54,8 @@ class HttpProxy extends BaseProxy {
this.custom_domains = proxyStats.conf.custom_domains this.custom_domains = proxyStats.conf.custom_domains
this.host_header_rewrite = proxyStats.conf.host_header_rewrite this.host_header_rewrite = proxyStats.conf.host_header_rewrite
this.locations = proxyStats.conf.locations this.locations = proxyStats.conf.locations
if (proxyStats.conf.sub_domain != "") { if (proxyStats.conf.subdomain != "") {
this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host this.subdomain = proxyStats.conf.subdomain + "." + subdomain_host
} else { } else {
this.subdomain = "" this.subdomain = ""
} }
@@ -75,8 +75,8 @@ class HttpsProxy extends BaseProxy {
this.port = port this.port = port
if (proxyStats.conf != null) { if (proxyStats.conf != null) {
this.custom_domains = proxyStats.conf.custom_domains this.custom_domains = proxyStats.conf.custom_domains
if (proxyStats.conf.sub_domain != "") { if (proxyStats.conf.subdomain != "") {
this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host this.subdomain = proxyStats.conf.subdomain + "." + subdomain_host
} else { } else {
this.subdomain = "" this.subdomain = ""
} }
@@ -94,4 +94,11 @@ class StcpProxy extends BaseProxy {
} }
} }
export {BaseProxy, TcpProxy, UdpProxy, HttpProxy, HttpsProxy, StcpProxy} class SudpProxy extends BaseProxy {
constructor(proxyStats) {
super(proxyStats)
this.type = "sudp"
}
}
export {BaseProxy, TcpProxy, UdpProxy, HttpProxy, HttpsProxy, StcpProxy, SudpProxy}