Compare commits

...

58 Commits

Author SHA1 Message Date
fatedier
143750901e Merge pull request #2638 from fatedier/dev
bump version to v0.38.0
2021-10-25 20:31:13 +08:00
fatedier
71489d194c e2e: add delay duration for monitor case 2021-10-25 20:27:22 +08:00
fatedier
85aa3df256 bump version 2021-10-25 20:14:21 +08:00
fatedier
f1a51eba18 client: lint 2021-10-19 15:02:45 +08:00
Blizard
1d26ea440b fix: kcp protocol cause delay release resource (#2621)
Co-authored-by: blizard863 <760076784@qq.com>
2021-10-19 14:57:26 +08:00
Blizard
998e678a7f fix: typo (#2614)
Co-authored-by: blizard863 <760076784@qq.com>
2021-10-11 12:11:59 +08:00
kekeimiku
0cee1877e3 refactor: move from io/ioutil to io and os package (#2592) 2021-09-29 10:33:57 +08:00
fatedier
72a7fd948e Update bug_report.yaml 2021-09-03 11:52:04 +08:00
fatedier
357c9b0dcb use github issue form (#2563) 2021-09-03 11:47:55 +08:00
fatedier
14bd0716d0 Create FUNDING.yml (#2558) 2021-08-31 20:28:13 +08:00
bobo liu
2f74f54f18 Let's get rid of ugly statik (#2255)
* Get rid of ugly statik

go1.16 introduced the embed package, it's the more graceful solution for embedding file into binary.
https://golang.org/pkg/embed/

* remove statik totally

* split go and static files in assets
2021-08-17 20:20:04 +08:00
fatedier
a62a9431b1 support go1.17 and remove go1.15 (#2532) 2021-08-17 15:26:43 +08:00
fatedier
42745a3da2 frpc: add disable_custom_tls_first_byte to not send first custom tls to frps (#2520) 2021-08-11 23:10:35 +08:00
fatedier
82f80a22be add healthz api (#2511) 2021-08-04 14:33:53 +08:00
fatedier
f570dcb307 update stale action 2021-08-04 10:51:18 +08:00
fatedier
997d406ec2 Merge pull request #2508 from fatedier/dev
bump version
2021-08-03 23:13:31 +08:00
fatedier
87e60683ed add release note for v0.37.1 (#2507) 2021-08-03 23:10:27 +08:00
fatedier
86b2e686a5 vhost: use new readClientHello function (#2504) 2021-08-03 22:58:03 +08:00
fatedier
09f39de74e add more e2e test (#2505) 2021-08-02 13:07:28 +08:00
fatedier
2a68c1152f dep: update github.com/hashicorp/yamux to latest (#2472) 2021-07-08 10:42:28 +08:00
Fishbone
df5859b5f7 Fix server-side proxy inappropriate quit when met accept: too many open files error (#2467) 2021-07-05 10:27:15 +08:00
Blizard
3dd888a9ea fix: stuct name typo (#2458)
Co-authored-by: tanghuafa <tanghuafa@bytedance.com>
2021-06-24 16:33:52 +08:00
fatedier
a51e221db3 add real ip test 2021-06-21 19:35:52 +08:00
fatedier
fe4e9b55f3 update github.com/pires/go-proxyproto to v0.5.0 2021-06-21 19:35:52 +08:00
fatedier
3f11b6a082 update k8s.io/apimachinery to v0.21.2 2021-06-21 19:35:52 +08:00
fatedier
8a333c2ae0 update github.com/stretchr/testify to v1.7.0 2021-06-21 19:35:52 +08:00
fatedier
1fd6ba2738 update github.com/spf13/cobra to v1.1.3 2021-06-21 19:35:52 +08:00
fatedier
a98a9616f6 update dependency github.com/rodaine/table to v1.0.1 2021-06-21 19:35:52 +08:00
fatedier
95cd9ab900 update dependency github.com/prometheus/client_golang to v1.11.0 2021-06-21 19:35:52 +08:00
fatedier
900454e58b more e2e test cases (#2450) 2021-06-18 16:48:36 +08:00
fatedier
c7d4637382 fix web js (#2444) 2021-06-09 11:53:39 +08:00
ztz
56925961df Typo: josn => json (#2429) 2021-06-04 11:21:52 +08:00
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
152 changed files with 5334 additions and 3007 deletions

View File

@@ -1,17 +1,15 @@
version: 2 version: 2
jobs: jobs:
test1: go-version-latest:
docker: docker:
- image: circleci/golang:1.16-node - image: cimg/go:1.17-node
working_directory: /go/src/github.com/fatedier/frp
steps: steps:
- checkout - checkout
- run: make - run: make
- run: make alltest - run: make alltest
test2: go-version-last:
docker: docker:
- image: circleci/golang:1.15-node - image: cimg/go:1.16-node
working_directory: /go/src/github.com/fatedier/frp
steps: steps:
- checkout - checkout
- run: make - run: make
@@ -21,5 +19,5 @@ workflows:
version: 2 version: 2
build_and_test: build_and_test:
jobs: jobs:
- test1 - go-version-latest
- test2 - go-version-last

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [fatedier]

View File

@@ -1,44 +0,0 @@
---
name: Bug Report
about: Bug Report for FRP
title: ''
labels: Requires Testing
assignees: ''
---
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
<!-- ⚠️⚠️ Incomplete reports will be marked as invalid, and closed, with few exceptions ⚠️⚠️ -->
<!-- in addition, please use search well so that the same solution can be found in the feedback, we will close it directly -->
<!-- for convenience of differentiation, use FRPS or FRPC to refer to the FRP server or client -->
**[REQUIRED] hat version of frp are you using**
<!-- Use ./frpc -v or ./frps -v -->
Version:
**[REQUIRED] What operating system and processor architecture are you using**
OS:
CPU architecture:
**[REQUIRED] description of errors**
**confile**
<!-- Please pay attention to hiding the token, server_addr and other privacy information -->
**log file**
<!-- If the file is too large, use Pastebin, for example https://pastebin.ubuntu.com/ -->
**Steps to reproduce the issue**
1.
2.
3.
**Supplementary information**
**Can you guess what caused this issue**
**Checklist**:
<!--- Make sure you've completed the following steps (put an "X" between of brackets): -->
- [] I included all information required in the sections above
- [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/fatedier/frp/issues?q=is%3Aissue)

77
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Bug report
description: Report a bug to help us improve frp
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: bug-description
attributes:
label: Bug Description
description: Tell us what issues you ran into
placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
validations:
required: true
- type: input
id: frpc-version
attributes:
label: frpc Version
description: Include the output of `frpc -v`
validations:
required: true
- type: input
id: frps-version
attributes:
label: frps Version
description: Include the output of `frps -v`
validations:
required: true
- type: input
id: system-architecture
attributes:
label: System Architecture
description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
validations:
required: true
- type: textarea
id: config
attributes:
label: Configurations
description: Include what configurrations you used and ran into this problem
placeholder: Pay attention to hiding the token and password in your output
validations:
required: true
- type: textarea
id: log
attributes:
label: Logs
description: Prefer you providing releated error logs here
placeholder: Pay attention to hiding your personal informations
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: How to reproduce it? It's important for us to find the bug
value: |
1.
2.
3.
...
- type: checkboxes
id: area
attributes:
label: Affected area
options:
- label: "Docs"
- label: "Installation"
- label: "Performance and Scalability"
- label: "Security"
- label: "User Experience"
- label: "Test and Release"
- label: "Developer Infrastructure"
- label: "Client Plugin"
- label: "Server Plugin"
- label: "Extensions"
- label: "Others"

View File

@@ -1,5 +1 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links:
- name: DOCS
url: https://github.com/fatedier/frp
about: Here you can find out how to configure frp.

View File

@@ -1,22 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: "[+] Enhancement"
assignees: ''
---
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
**The solution you want**
<!--A clear and concise description of the solution you want. -->
**Alternatives considered**
<!--A clear and concise description of any alternative solutions or features you have considered. -->
**How to implement this function**
<!--Implementation steps for the solution you want. -->
**Application scenarios of this function**
<!--Make a clear and concise description of the application scenario of the solution you want. -->

View File

@@ -0,0 +1,36 @@
name: Feature Request
description: Suggest an idea to improve frp
title: "[Feature Request] "
body:
- type: markdown
attributes:
value: |
This is only used to request new product features.
- type: textarea
id: feature-request
attributes:
label: Describe the feature request
description: Tell us what's you want and why it should be added in frp.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
- type: checkboxes
id: area
attributes:
label: Affected area
options:
- label: "Docs"
- label: "Installation"
- label: "Performance and Scalability"
- label: "Security"
- label: "User Experience"
- label: "Test and Release"
- label: "Developer Infrastructure"
- label: "Client Plugin"
- label: "Server Plugin"
- label: "Extensions"
- label: "Others"

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.17
-
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

@@ -15,7 +15,11 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16 go-version: 1.17
- 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

@@ -15,12 +15,12 @@ jobs:
- uses: actions/stale@v3 - uses: actions/stale@v3
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.' stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-pr-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.' stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-issue-label: 'lifecycle/stale' stale-issue-label: 'lifecycle/stale'
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
stale-pr-label: 'lifecycle/stale' stale-pr-label: 'lifecycle/stale'
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
days-before-stale: 45 days-before-stale: 30
days-before-close: 10 days-before-close: 7
debug-only: ${{ github.event.inputs.debug-only }} debug-only: ${{ github.event.inputs.debug-only }}

View File

@@ -12,9 +12,6 @@ file:
rm -rf ./assets/frpc/static/* rm -rf ./assets/frpc/static/*
cp -rf ./web/frps/dist/* ./assets/frps/static cp -rf ./web/frps/dist/* ./assets/frps/static
cp -rf ./web/frpc/dist/* ./assets/frpc/static cp -rf ./web/frpc/dist/* ./assets/frpc/static
rm -rf ./assets/frps/statik
rm -rf ./assets/frpc/statik
go generate ./assets/...
fmt: fmt:
go fmt ./... go fmt ./...
@@ -34,13 +31,13 @@ gotest:
go test -v --cover ./server/... go test -v --cover ./server/...
go test -v --cover ./pkg/... go test -v --cover ./pkg/...
ci:
go test -count=1 -p=1 -v ./tests/...
e2e: e2e:
./hack/run-e2e.sh ./hack/run-e2e.sh
alltest: gotest ci e2e e2e-trace:
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
alltest: gotest e2e
clean: clean:
rm -f ./bin/frpc rm -f ./bin/frpc

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 +1,8 @@
### New ### New
* New plugin `https2https`. * Add `/healthz` API.
* frpc supports `tls_server_name` to override the default value from `server_addr`. * frpc support `disable_custom_tls_first_byte` .If set true, frpc will not send custom header byte.
### Improvement ### Improve
* Increase reconnect frequency if it occurs an network error between frpc and frps. * Use go standard embed package instead of statik.
### Fix
* Fix panic issue about xtcp.

View File

@@ -14,22 +14,15 @@
package assets package assets
//go:generate statik -src=./frps/static -dest=./frps
//go:generate statik -src=./frpc/static -dest=./frpc
//go:generate go fmt ./frps/statik/statik.go
//go:generate go fmt ./frpc/statik/statik.go
import ( import (
"io/ioutil" "io/fs"
"net/http" "net/http"
"os"
"path"
"github.com/rakyll/statik/fs"
) )
var ( var (
// store static files in memory by statik // read-only filesystem created by "embed" for embedded files
content fs.FS
FileSystem http.FileSystem FileSystem http.FileSystem
// if prefix is not empty, we get file content from disk // if prefix is not empty, we get file content from disk
@@ -38,40 +31,18 @@ var (
// if path is empty, load assets in memory // if path is empty, load assets in memory
// or set FileSystem using disk files // or set FileSystem using disk files
func Load(path string) (err error) { func Load(path string) {
prefixPath = path prefixPath = path
if prefixPath != "" { if prefixPath != "" {
FileSystem = http.Dir(prefixPath) FileSystem = http.Dir(prefixPath)
return nil
} else { } else {
FileSystem, err = fs.New() FileSystem = http.FS(content)
} }
return err
} }
func ReadFile(file string) (content string, err error) { func Register(fileSystem fs.FS) {
if prefixPath == "" { subFs, err := fs.Sub(fileSystem, "static")
file, err := FileSystem.Open(path.Join("/", file)) if err == nil {
if err != nil { content = subFs
return content, err
}
defer file.Close()
buf, err := ioutil.ReadAll(file)
if err != nil {
return content, err
}
content = string(buf)
} else {
file, err := os.Open(path.Join(prefixPath, file))
if err != nil {
return content, err
}
defer file.Close()
buf, err := ioutil.ReadAll(file)
if err != nil {
return content, err
}
content = string(buf)
} }
return content, err
} }

14
assets/frpc/embed.go Normal file
View File

@@ -0,0 +1,14 @@
package frpc
import (
"embed"
"github.com/fatedier/frp/assets"
)
//go:embed static/*
var content embed.FS
func init() {
assets.Register(content)
}

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?5d5774096cf5c1b4d5af"></script><script type="text/javascript" src="vendor.js?dc42700731a508d39009"></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,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:"dc42700731a508d39009"}[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}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

14
assets/frps/embed.go Normal file
View File

@@ -0,0 +1,14 @@
package frpc
import (
"embed"
"github.com/fatedier/frp/assets"
)
//go:embed static/*
var content embed.FS
func init() {
assets.Register(content)
}

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?5d154ba4c6b342d8c0c3"></script><script type="text/javascript" src="vendor.js?ddbd1f69fb6e67be4b78"></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:"ddbd1f69fb6e67be4b78"}[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

@@ -37,7 +37,8 @@ func (svr *Service) RunAdminServer(address string) (err error) {
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware) router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go // api, see admin_api.go
router.HandleFunc("/healthz", svr.healthz)
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET") router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET") router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET") router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")

View File

@@ -17,8 +17,9 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"os"
"sort" "sort"
"strings" "strings"
@@ -32,37 +33,25 @@ type GeneralResponse struct {
Msg string Msg string
} }
// GET api/reload // /healthz
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
// 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 +59,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)
@@ -269,7 +257,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
}() }()
// get new config content // get new config content
body, err := ioutil.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
res.Code = 400 res.Code = 400
res.Msg = fmt.Sprintf("read request body error: %v", err) res.Msg = fmt.Sprintf("read request body error: %v", err)
@@ -286,7 +274,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
// get token from origin content // get token from origin content
token := "" token := ""
b, err := ioutil.ReadFile(svr.cfgFile) b, err := os.ReadFile(svr.cfgFile)
if err != nil { if err != nil {
res.Code = 400 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
@@ -325,7 +313,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
} }
content = strings.Join(newRows, "\n") content = strings.Join(newRows, "\n")
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644) err = os.WriteFile(svr.cfgFile, []byte(content), 0644)
if err != nil { if err != nil {
res.Code = 500 res.Code = 500
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err) res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)

View File

@@ -182,9 +182,16 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
} }
func (ctl *Control) Close() error { func (ctl *Control) Close() error {
return ctl.GracefulClose(0)
}
func (ctl *Control) GracefulClose(d time.Duration) error {
ctl.pm.Close() ctl.pm.Close()
ctl.conn.Close()
ctl.vm.Close() ctl.vm.Close()
time.Sleep(d)
ctl.conn.Close()
if ctl.session != nil { if ctl.session != nil {
ctl.session.Close() ctl.session.Close()
} }
@@ -228,7 +235,7 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
} }
address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort)) address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort))
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig) conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig, ctl.clientCfg.DisableCustomTLSFirstByte)
if err != nil { if err != nil {
xl.Warn("start new connection to server error: %v", err) xl.Warn("start new connection to server error: %v", err)

View File

@@ -19,7 +19,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"time" "time"
@@ -162,7 +161,7 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
io.Copy(ioutil.Discard, resp.Body) io.Copy(io.Discard, resp.Body)
if resp.StatusCode/100 != 2 { if resp.StatusCode/100 != 2 {
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode) return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)

View File

@@ -19,7 +19,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"strconv" "strconv"
"strings" "strings"
@@ -401,7 +400,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 5 * time.Second fmuxCfg.KeepAliveInterval = 5 * time.Second
fmuxCfg.LogOutput = ioutil.Discard fmuxCfg.LogOutput = io.Discard
sess, err := fmux.Server(kcpConn, fmuxCfg) sess, err := fmux.Server(kcpConn, fmuxCfg)
if err != nil { if err != nil {
xl.Error("create yamux server from kcp connection error: %v", err) xl.Error("create yamux server from kcp connection error: %v", err)
@@ -757,12 +756,12 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
if m.DstAddr == "" { if m.DstAddr == "" {
m.DstAddr = "127.0.0.1" m.DstAddr = "127.0.0.1"
} }
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
h := &pp.Header{ h := &pp.Header{
Command: pp.PROXY, Command: pp.PROXY,
SourceAddress: net.ParseIP(m.SrcAddr), SourceAddr: srcAddr,
SourcePort: m.SrcPort, DestinationAddr: dstAddr,
DestinationAddress: net.ParseIP(m.DstAddr),
DestinationPort: m.DstPort,
} }
if strings.Contains(m.SrcAddr, ".") { if strings.Contains(m.SrcAddr, ".") {

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

@@ -19,7 +19,7 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"net" "net"
"runtime" "runtime"
"strconv" "strconv"
@@ -124,13 +124,10 @@ func (svr *Service) Run() error {
if svr.cfg.AdminPort != 0 { if svr.cfg.AdminPort != 0 {
// Init admin server assets // Init admin server assets
err := assets.Load(svr.cfg.AssetsDir) assets.Load(svr.cfg.AssetsDir)
if err != nil {
return fmt.Errorf("Load assets error: %v", err)
}
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort)) address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
err = svr.RunAdminServer(address) err := svr.RunAdminServer(address)
if err != nil { if err != nil {
log.Warn("run admin server error: %v", err) log.Warn("run admin server error: %v", err)
} }
@@ -232,7 +229,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
} }
address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)) address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort))
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig) conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig, svr.cfg.DisableCustomTLSFirstByte)
if err != nil { if err != nil {
return return
} }
@@ -249,7 +246,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
if svr.cfg.TCPMux { if svr.cfg.TCPMux {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard fmuxCfg.LogOutput = io.Discard
session, err = fmux.Client(conn, fmuxCfg) session, err = fmux.Client(conn, fmuxCfg)
if err != nil { if err != nil {
return return
@@ -315,9 +312,13 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
} }
func (svr *Service) Close() { func (svr *Service) Close() {
svr.GracefulClose(time.Duration(0))
}
func (svr *Service) GracefulClose(d time.Duration) {
atomic.StoreUint32(&svr.exit, 1) atomic.StoreUint32(&svr.exit, 1)
if svr.ctl != nil { if svr.ctl != nil {
svr.ctl.Close() svr.ctl.GracefulClose(d)
} }
svr.cancel() svr.cancel()
} }

View File

@@ -19,7 +19,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"sync" "sync"
"time" "time"
@@ -308,7 +307,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 5 * time.Second fmuxCfg.KeepAliveInterval = 5 * time.Second
fmuxCfg.LogOutput = ioutil.Discard fmuxCfg.LogOutput = io.Discard
sess, err := fmux.Client(remote, fmuxCfg) sess, err := fmux.Client(remote, fmuxCfg)
if err != nil { if err != nil {
xl.Error("create yamux session error: %v", err) xl.Error("create yamux session error: %v", err)
@@ -366,7 +365,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 +445,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 +474,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

@@ -18,7 +18,7 @@ import (
"math/rand" "math/rand"
"time" "time"
_ "github.com/fatedier/frp/assets/frpc/statik" _ "github.com/fatedier/frp/assets/frpc"
"github.com/fatedier/frp/cmd/frpc/sub" "github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"

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

@@ -17,7 +17,7 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@@ -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)
@@ -82,7 +76,7 @@ func reload(clientCfg config.ClientCommonConf) error {
return nil return nil
} }
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -124,28 +124,10 @@ func handleSignal(svr *client.Service) {
ch := make(chan os.Signal) ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch <-ch
svr.Close() svr.GracefulClose(500 * time.Millisecond)
time.Sleep(250 * time.Millisecond)
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 +149,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 +156,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 +207,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

@@ -18,7 +18,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@@ -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)
} }
@@ -84,7 +77,7 @@ func status(clientCfg config.ClientCommonConf) error {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode) return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
} }
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }
@@ -96,7 +89,7 @@ func status(clientCfg config.ClientCommonConf) error {
fmt.Println("Proxy Status...") fmt.Println("Proxy Status...")
if len(res.TCP) > 0 { if len(res.TCP) > 0 {
fmt.Printf("TCP") fmt.Println("TCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.TCP { for _, ps := range res.TCP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
@@ -105,7 +98,7 @@ func status(clientCfg config.ClientCommonConf) error {
fmt.Println("") fmt.Println("")
} }
if len(res.UDP) > 0 { if len(res.UDP) > 0 {
fmt.Printf("UDP") fmt.Println("UDP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.UDP { for _, ps := range res.UDP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
@@ -114,7 +107,7 @@ func status(clientCfg config.ClientCommonConf) error {
fmt.Println("") fmt.Println("")
} }
if len(res.HTTP) > 0 { if len(res.HTTP) > 0 {
fmt.Printf("HTTP") fmt.Println("HTTP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.HTTP { for _, ps := range res.HTTP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
@@ -123,7 +116,7 @@ func status(clientCfg config.ClientCommonConf) error {
fmt.Println("") fmt.Println("")
} }
if len(res.HTTPS) > 0 { if len(res.HTTPS) > 0 {
fmt.Printf("HTTPS") fmt.Println("HTTPS")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.HTTPS { for _, ps := range res.HTTPS {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
@@ -132,7 +125,7 @@ func status(clientCfg config.ClientCommonConf) error {
fmt.Println("") fmt.Println("")
} }
if len(res.STCP) > 0 { if len(res.STCP) > 0 {
fmt.Printf("STCP") fmt.Println("STCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.STCP { for _, ps := range res.STCP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
@@ -141,7 +134,7 @@ func status(clientCfg config.ClientCommonConf) error {
fmt.Println("") fmt.Println("")
} }
if len(res.XTCP) > 0 { if len(res.XTCP) > 0 {
fmt.Printf("XTCP") fmt.Println("XTCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.XTCP { for _, ps := range res.XTCP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)

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

@@ -20,7 +20,7 @@ import (
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"
_ "github.com/fatedier/frp/assets/frps/statik" _ "github.com/fatedier/frp/assets/frps"
_ "github.com/fatedier/frp/pkg/metrics" _ "github.com/fatedier/frp/pkg/metrics"
) )

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,13 @@ 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
# By default, frpc will connect frps with first custom byte if tls is enabled.
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
disable_custom_tls_first_byte = false
# '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]
@@ -178,9 +185,9 @@ use_compression = true
# if not set, you can access this custom_domains without certification # if not set, you can access this custom_domains without certification
http_user = admin http_user = admin
http_pwd = admin http_pwd = admin
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com # if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
subdomain = web01 subdomain = web01
custom_domains = web02.yourdomain.com custom_domains = web01.yourdomain.com
# locations is only available for http type # locations is only available for http type
locations = /,/pic locations = /,/pic
host_header_rewrite = example.com host_header_rewrite = example.com

View File

@@ -36,7 +36,7 @@ vhost_https_port = 443
dashboard_addr = 0.0.0.0 dashboard_addr = 0.0.0.0
dashboard_port = 7500 dashboard_port = 7500
# dashboard user and passwd for basic auth protect, if not set, both default value is admin # dashboard user and passwd for basic auth protect
dashboard_user = admin dashboard_user = admin
dashboard_pwd = admin dashboard_pwd = admin

View File

@@ -9,6 +9,7 @@ Restart=on-failure
RestartSec=5s RestartSec=5s
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
LimitNOFILE=1048576
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -3,12 +3,13 @@ Description=Frp Client Service
After=network.target After=network.target
[Service] [Service]
Type=idle Type=simple
User=nobody User=nobody
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
LimitNOFILE=1048576
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -8,6 +8,7 @@ User=nobody
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
LimitNOFILE=1048576
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -8,6 +8,7 @@ User=nobody
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
LimitNOFILE=1048576
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

37
go.mod
View File

@@ -8,33 +8,32 @@ require (
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/google/uuid v1.1.1 github.com/go-playground/validator/v10 v10.6.1
github.com/gorilla/mux v1.7.3 github.com/google/uuid v1.2.0
github.com/gorilla/websocket v1.4.0 github.com/gorilla/mux v1.8.0
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/gorilla/websocket v1.4.2
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c
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
github.com/mattn/go-runewidth v0.0.4 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/onsi/ginkgo v1.12.3 github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.10.1 github.com/onsi/gomega v1.13.0
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc github.com/pires/go-proxyproto v0.5.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.4.1 github.com/prometheus/client_golang v1.11.0
github.com/rakyll/statik v0.1.1 github.com/rodaine/table v1.0.1
github.com/rodaine/table v1.0.0 github.com/spf13/cobra v1.1.3
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/stretchr/testify v1.7.0
github.com/spf13/cobra v0.0.3
github.com/stretchr/testify v1.4.0
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
gopkg.in/ini.v1 v1.62.0 gopkg.in/ini.v1 v1.62.0
gopkg.in/square/go-jose.v2 v2.4.1 // indirect gopkg.in/square/go-jose.v2 v2.4.1 // indirect
k8s.io/apimachinery v0.18.3 k8s.io/apimachinery v0.21.2
k8s.io/client-go v0.21.2
) )

575
go.sum
View File

@@ -1,269 +1,686 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 h1:2p4W5xYizIYwhiGQgeHOQcRD2O84j0tjD40P6gUCRrk= github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 h1:2p4W5xYizIYwhiGQgeHOQcRD2O84j0tjD40P6gUCRrk=
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185/go.mod h1:MUs+IH/MGJNz5Cj2JVJBPZBKw2exON7LzO3HrJHmGiQ= github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185/go.mod h1:MUs+IH/MGJNz5Cj2JVJBPZBKw2exON7LzO3HrJHmGiQ=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/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/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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c h1:nqkErwUGfpZZMqj29WZ9U/wz2OpJVDuiokLhE/3Y7IQ=
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c/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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
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=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8= github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8= github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFYyOi8zA+Y8= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY= github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I= github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8= github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk= github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw= github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
k8s.io/client-go v0.21.2 h1:Q1j4L/iMN4pTw6Y4DWppBoUxgKO8LbffEMVEV00MUp0=
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -5,11 +5,16 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
which ginkgo &> /dev/null which ginkgo &> /dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "ginkgo not found, try to install..." echo "ginkgo not found, try to install..."
go install github.com/onsi/ginkgo/ginkgo go install github.com/onsi/ginkgo/ginkgo@latest
fi fi
debug=false debug=false
if [ x${DEBUG} == x"true" ]; then if [ x${DEBUG} == x"true" ]; then
debug=true debug=true
fi fi
ginkgo -nodes=4 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=debug -debug=${debug} logLevel=debug
if [ x${LOG_LEVEL} != x"" ]; then
logLevel=${LOG_LEVEL}
fi
ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}

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,11 +30,11 @@ 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".
ServerAddr string `ini:"server_addr" josn:"server_addr"` ServerAddr string `ini:"server_addr" json:"server_addr"`
// ServerPort specifies the port to connect to the server on. By default, // ServerPort specifies the port to connect to the server on. By default,
// this value is 7000. // this value is 7000.
ServerPort int `ini:"server_port" json:"server_port"` ServerPort int `ini:"server_port" json:"server_port"`
@@ -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
@@ -123,6 +124,9 @@ type ClientCommonConf struct {
// TLSServerName specifices the custom server name of tls certificate. By // TLSServerName specifices the custom server name of tls certificate. By
// default, server name if same to ServerAddr. // default, server name if same to ServerAddr.
TLSServerName string `ini:"tls_server_name" json:"tls_server_name"` TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
// By default, frpc will connect frps with first custom byte if tls is enabled.
// If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
// HeartBeatInterval specifies at what interval heartbeats are sent to the // HeartBeatInterval specifies at what interval heartbeats are sent to the
// server, in seconds. It is not recommended to change this value. By // server, in seconds. It is not recommended to change this value. By
// default, this value is 30. // default, this value is 30.
@@ -136,50 +140,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 +211,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 +252,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 +305,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 +330,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 +340,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)
} }

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

@@ -0,0 +1,99 @@
// 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"
"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 := os.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

@@ -22,6 +22,7 @@ import (
plugin "github.com/fatedier/frp/pkg/plugin/server" plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/util/util" "github.com/fatedier/frp/pkg/util/util"
"github.com/go-playground/validator/v10"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@@ -29,38 +30,38 @@ 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".
BindAddr string `ini:"bind_addr" json:"bind_addr"` BindAddr string `ini:"bind_addr" json:"bind_addr"`
// BindPort specifies the port that the server listens on. By default, this // BindPort specifies the port that the server listens on. By default, this
// value is 7000. // value is 7000.
BindPort int `ini:"bind_port" json:"bind_port"` BindPort int `ini:"bind_port" json:"bind_port" validate:"gte=0,lte=65535"`
// BindUDPPort specifies the UDP port that the server listens on. If this // BindUDPPort specifies the UDP port that the server listens on. If this
// value is 0, the server will not listen for UDP connections. By default, // value is 0, the server will not listen for UDP connections. By default,
// this value is 0 // this value is 0
BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port"` BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port" validate:"gte=0,lte=65535"`
// KCPBindPort specifies the KCP port that the server listens on. If this // KCPBindPort specifies the KCP port that the server listens on. If this
// value is 0, the server will not listen for KCP connections. By default, // value is 0, the server will not listen for KCP connections. By default,
// 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" validate:"gte=0,lte=65535"`
// 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
// requests. By default, this value is 0. // requests. By default, this value is 0.
VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port"` VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port" validate:"gte=0,lte=65535"`
// VhostHTTPSPort specifies the port that the server listens for HTTPS // VhostHTTPSPort specifies the port that the server listens for HTTPS
// Vhost requests. If this value is 0, the server will not listen for HTTPS // Vhost requests. If this value is 0, the server will not listen for HTTPS
// requests. By default, this value is 0. // requests. By default, this value is 0.
VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port"` VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port" validate:"gte=0,lte=65535"`
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP // TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP // HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
// requests on one single port. If it's not - it will listen on this value for // requests on one single port. If it's not - it will listen on this value for
// HTTP CONNECT requests. By default, this value is 0. // HTTP CONNECT requests. By default, this value is 0.
TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port"` TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port" validate:"gte=0,lte=65535"`
// VhostHTTPTimeout specifies the response header timeout for the Vhost // VhostHTTPTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60. // HTTP server, in seconds. By default, this value is 60.
VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"` VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"`
@@ -70,12 +71,12 @@ type ServerCommonConf struct {
// DashboardPort specifies the port that the dashboard listens on. If this // DashboardPort specifies the port that the dashboard listens on. If this
// value is 0, the dashboard will not be started. By default, this value is // value is 0, the dashboard will not be started. By default, this value is
// 0. // 0.
DashboardPort int `ini:"dashboard_port" json:"dashboard_port"` DashboardPort int `ini:"dashboard_port" json:"dashboard_port" validate:"gte=0,lte=65535"`
// 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 +175,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 +209,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 +224,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 +238,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 +265,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 validator.New().Struct(cfg)
}
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

@@ -16,7 +16,6 @@ package config
import ( import (
"bytes" "bytes"
"io/ioutil"
"os" "os"
"strings" "strings"
"text/template" "text/template"
@@ -67,7 +66,7 @@ func RenderContent(in []byte) (out []byte, err error) {
func GetRenderedConfFromFile(path string) (out []byte, err error) { func GetRenderedConfFromFile(path string) (out []byte, err error) {
var b []byte var b []byte
b, err = ioutil.ReadFile(path) b, err = os.ReadFile(path)
if err != nil { if err != nil {
return return
} }

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

@@ -60,7 +60,7 @@ func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
listener := NewProxyListener() listener := NewProxyListener()
p := &HTTPS2HTTPPlugin{ p := &HTTP2HTTPSPlugin{
localAddr: localAddr, localAddr: localAddr,
hostHeaderRewrite: hostHeaderRewrite, hostHeaderRewrite: hostHeaderRewrite,
headers: headers, headers: headers,

View File

@@ -16,7 +16,6 @@ package plugin
import ( import (
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
@@ -43,7 +42,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
passwd := params["plugin_passwd"] passwd := params["plugin_passwd"]
cfg := &gosocks5.Config{ cfg := &gosocks5.Config{
Logger: log.New(ioutil.Discard, "", log.LstdFlags), Logger: log.New(io.Discard, "", log.LstdFlags),
} }
if user != "" || passwd != "" { if user != "" || passwd != "" {
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd}) cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})

View File

@@ -20,7 +20,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/url" "net/url"
"reflect" "reflect"
@@ -116,7 +116,7 @@ func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("do http request error code: %d", resp.StatusCode) return fmt.Errorf("do http request error code: %d", resp.StatusCode)
} }
buf, err = ioutil.ReadAll(resp.Body) buf, err = io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -179,7 +179,7 @@ func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent,
ctx := xlog.NewContext(context.Background(), xl) ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid) ctx = NewReqidContext(ctx, reqid)
for _, p := range m.pingPlugins { for _, p := range m.newWorkConnPlugins {
res, retContent, err = p.Handle(ctx, OpPing, *content) res, retContent, err = p.Handle(ctx, OpPing, *content)
if err != nil { if err != nil {
xl.Warn("send NewWorkConn request to plugin [%s] error: %v", p.Name(), err) xl.Warn("send NewWorkConn request to plugin [%s] error: %v", p.Name(), err)

View File

@@ -6,8 +6,8 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"io/ioutil"
"math/big" "math/big"
"os"
) )
func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) { func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
@@ -47,7 +47,7 @@ func newRandomTLSKeyPair() *tls.Certificate {
func newCertPool(caPath string) (*x509.CertPool, error) { func newCertPool(caPath string) (*x509.CertPool, error) {
pool := x509.NewCertPool() pool := x509.NewCertPool()
caCrt, err := ioutil.ReadFile(caPath) caCrt, err := os.ReadFile(caPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -86,7 +86,7 @@ func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
return base, nil return base, nil
} }
func NewClientTLSConfig(certPath, keyPath, caPath, servearName string) (*tls.Config, error) { func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Config, error) {
var base = &tls.Config{} var base = &tls.Config{}
if certPath == "" || keyPath == "" { if certPath == "" || keyPath == "" {
@@ -107,7 +107,7 @@ func NewClientTLSConfig(certPath, keyPath, caPath, servearName string) (*tls.Con
} }
base.RootCAs = pool base.RootCAs = pool
base.ServerName = servearName base.ServerName = serverName
base.InsecureSkipVerify = false base.InsecureSkipVerify = false
} else { } else {
base.InsecureSkipVerify = true base.InsecureSkipVerify = true

View File

@@ -228,7 +228,7 @@ func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.
} }
} }
func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config) (c net.Conn, err error) { func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (c net.Conn, err error) {
c, err = ConnectServerByProxy(proxyURL, protocol, addr) c, err = ConnectServerByProxy(proxyURL, protocol, addr)
if err != nil { if err != nil {
return return
@@ -238,6 +238,6 @@ func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string,
return return
} }
c = WrapTLSClientConn(c, tlsConfig) c = WrapTLSClientConn(c, tlsConfig, disableCustomTLSHeadByte)
return return
} }

View File

@@ -27,13 +27,18 @@ var (
FRPTLSHeadByte = 0x17 FRPTLSHeadByte = 0x17
) )
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) { func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (out net.Conn) {
c.Write([]byte{byte(FRPTLSHeadByte)}) if !disableCustomTLSHeadByte {
c.Write([]byte{byte(FRPTLSHeadByte)})
}
out = tls.Client(c, tlsConfig) out = tls.Client(c, tlsConfig)
return return
} }
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, err error) { func CheckAndEnableTLSServerConnWithTimeout(
c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
) (out net.Conn, isTLS bool, custom bool, err error) {
sc, r := gnet.NewSharedConnSize(c, 2) sc, r := gnet.NewSharedConnSize(c, 2)
buf := make([]byte, 1) buf := make([]byte, 1)
var n int var n int
@@ -46,6 +51,11 @@ func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, t
if n == 1 && int(buf[0]) == FRPTLSHeadByte { if n == 1 && int(buf[0]) == FRPTLSHeadByte {
out = tls.Server(c, tlsConfig) out = tls.Server(c, tlsConfig)
isTLS = true
custom = true
} else if n == 1 && int(buf[0]) == 0x16 {
out = tls.Server(sc, tlsConfig)
isTLS = true
} else { } else {
if tlsOnly { if tlsOnly {
err = fmt.Errorf("non-TLS connection received on a TlsOnly server") err = fmt.Errorf("non-TLS connection received on a TlsOnly server")

View File

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

View File

@@ -15,32 +15,12 @@
package vhost package vhost
import ( import (
"fmt" "crypto/tls"
"io" "io"
"net" "net"
"strings"
"time" "time"
gnet "github.com/fatedier/golib/net" gnet "github.com/fatedier/golib/net"
"github.com/fatedier/golib/pool"
)
const (
typeClientHello uint8 = 1 // Type client hello
)
// TLS extension numbers
const (
extensionServerName uint16 = 0
extensionStatusRequest uint16 = 5
extensionSupportedCurves uint16 = 10
extensionSupportedPoints uint16 = 11
extensionSignatureAlgorithms uint16 = 13
extensionALPN uint16 = 16
extensionSCT uint16 = 18
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
) )
type HTTPSMuxer struct { type HTTPSMuxer struct {
@@ -52,142 +32,49 @@ func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, e
return &HTTPSMuxer{mux}, err return &HTTPSMuxer{mux}, err
} }
func readHandshake(rd io.Reader) (host string, err error) {
data := pool.GetBuf(1024)
origin := data
defer pool.PutBuf(origin)
_, err = io.ReadFull(rd, data[:47])
if err != nil {
return
}
length, err := rd.Read(data[47:])
if err != nil {
return
}
length += 47
data = data[:length]
if uint8(data[5]) != typeClientHello {
err = fmt.Errorf("readHandshake: type[%d] is not clientHello", uint16(data[5]))
return
}
// session
sessionIDLen := int(data[43])
if sessionIDLen > 32 || len(data) < 44+sessionIDLen {
err = fmt.Errorf("readHandshake: sessionIdLen[%d] is long", sessionIDLen)
return
}
data = data[44+sessionIDLen:]
if len(data) < 2 {
err = fmt.Errorf("readHandshake: dataLen[%d] after session is short", len(data))
return
}
// cipher suite numbers
cipherSuiteLen := int(data[0])<<8 | int(data[1])
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
err = fmt.Errorf("readHandshake: dataLen[%d] after cipher suite is short", len(data))
return
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
err = fmt.Errorf("readHandshake: cipherSuiteLen[%d] is long", cipherSuiteLen)
return
}
// compression method
compressionMethodsLen := int(data[0])
if len(data) < 1+compressionMethodsLen {
err = fmt.Errorf("readHandshake: compressionMethodsLen[%d] is long", compressionMethodsLen)
return
}
data = data[1+compressionMethodsLen:]
if len(data) == 0 {
// ClientHello is optionally followed by extension data
err = fmt.Errorf("readHandshake: there is no extension data to get servername")
return
}
if len(data) < 2 {
err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short", len(data))
return
}
extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
if extensionsLength != len(data) {
err = fmt.Errorf("readHandshake: extensionsLen[%d] is not equal to dataLen[%d]", extensionsLength, len(data))
return
}
for len(data) != 0 {
if len(data) < 4 {
err = fmt.Errorf("readHandshake: extensionsDataLen[%d] is too short", len(data))
return
}
extension := uint16(data[0])<<8 | uint16(data[1])
length := int(data[2])<<8 | int(data[3])
data = data[4:]
if len(data) < length {
err = fmt.Errorf("readHandshake: extensionLen[%d] is long", length)
return
}
switch extension {
case extensionRenegotiationInfo:
if length != 1 || data[0] != 0 {
err = fmt.Errorf("readHandshake: extension reNegotiationInfoLen[%d] is short", length)
return
}
case extensionNextProtoNeg:
case extensionStatusRequest:
case extensionServerName:
d := data[:length]
if len(d) < 2 {
err = fmt.Errorf("readHandshake: remiaining dataLen[%d] is short", len(d))
return
}
namesLen := int(d[0])<<8 | int(d[1])
d = d[2:]
if len(d) != namesLen {
err = fmt.Errorf("readHandshake: nameListLen[%d] is not equal to dataLen[%d]", namesLen, len(d))
return
}
for len(d) > 0 {
if len(d) < 3 {
err = fmt.Errorf("readHandshake: extension serverNameLen[%d] is short", len(d))
return
}
nameType := d[0]
nameLen := int(d[1])<<8 | int(d[2])
d = d[3:]
if len(d) < nameLen {
err = fmt.Errorf("readHandshake: nameLen[%d] is not equal to dataLen[%d]", nameLen, len(d))
return
}
if nameType == 0 {
serverName := string(d[:nameLen])
host = strings.TrimSpace(serverName)
return host, nil
}
d = d[nameLen:]
}
}
data = data[length:]
}
err = fmt.Errorf("Unknown error")
return
}
func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) { func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0) reqInfoMap := make(map[string]string, 0)
sc, rd := gnet.NewSharedConn(c) sc, rd := gnet.NewSharedConn(c)
host, err := readHandshake(rd)
clientHello, err := readClientHello(rd)
if err != nil { if err != nil {
return nil, reqInfoMap, err return nil, reqInfoMap, err
} }
reqInfoMap["Host"] = host
reqInfoMap["Host"] = clientHello.ServerName
reqInfoMap["Scheme"] = "https" reqInfoMap["Scheme"] = "https"
return sc, reqInfoMap, nil return sc, reqInfoMap, nil
} }
func readClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) {
var hello *tls.ClientHelloInfo
// Note that Handshake always fails because the readOnlyConn is not a real connection.
// As long as the Client Hello is successfully read, the failure should only happen after GetConfigForClient is called,
// so we only care about the error if hello was never set.
err := tls.Server(readOnlyConn{reader: reader}, &tls.Config{
GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) {
hello = &tls.ClientHelloInfo{}
*hello = *argHello
return nil, nil
},
}).Handshake()
if hello == nil {
return nil, err
}
return hello, nil
}
type readOnlyConn struct {
reader io.Reader
}
func (conn readOnlyConn) Read(p []byte) (int, error) { return conn.reader.Read(p) }
func (conn readOnlyConn) Write(p []byte) (int, error) { return 0, io.ErrClosedPipe }
func (conn readOnlyConn) Close() error { return nil }
func (conn readOnlyConn) LocalAddr() net.Addr { return nil }
func (conn readOnlyConn) RemoteAddr() net.Addr { return nil }
func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil }
func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil }
func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil }

View File

@@ -0,0 +1,38 @@
package vhost
import (
"crypto/tls"
"net"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestGetHTTPSHostname(t *testing.T) {
require := require.New(t)
l, err := net.Listen("tcp", ":")
require.NoError(err)
defer l.Close()
var conn net.Conn
go func() {
conn, _ = l.Accept()
require.NotNil(conn)
}()
go func() {
time.Sleep(100 * time.Millisecond)
tls.Dial("tcp", l.Addr().String(), &tls.Config{
InsecureSkipVerify: true,
ServerName: "example.com",
})
}()
time.Sleep(200 * time.Millisecond)
_, infos, err := GetHTTPSHostname(conn)
require.NoError(err)
require.Equal("example.com", infos["Host"])
require.Equal("https", infos["Scheme"])
}

View File

@@ -16,8 +16,9 @@ package vhost
import ( import (
"bytes" "bytes"
"io/ioutil" "io"
"net/http" "net/http"
"os"
frpLog "github.com/fatedier/frp/pkg/util/log" frpLog "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/version" "github.com/fatedier/frp/pkg/util/version"
@@ -57,7 +58,7 @@ func getNotFoundPageContent() []byte {
err error err error
) )
if NotFoundPagePath != "" { if NotFoundPagePath != "" {
buf, err = ioutil.ReadFile(NotFoundPagePath) buf, err = os.ReadFile(NotFoundPagePath)
if err != nil { if err != nil {
frpLog.Warn("read custom 404 page error: %v", err) frpLog.Warn("read custom 404 page error: %v", err)
buf = []byte(NotFound) buf = []byte(NotFound)
@@ -80,7 +81,7 @@ func notFoundResponse() *http.Response {
ProtoMajor: 1, ProtoMajor: 1,
ProtoMinor: 0, ProtoMinor: 0,
Header: header, Header: header,
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())), Body: io.NopCloser(bytes.NewReader(getNotFoundPageContent())),
} }
return res return res
} }

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

@@ -48,6 +48,7 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
router.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET") router.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
router.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET") router.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
router.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET") router.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
router.HandleFunc("/healthz", svr.Healthz)
// view // view
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET") router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")

View File

@@ -51,6 +51,11 @@ type serverInfoResp struct {
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"` ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
} }
// /healthz
func (svr *Service) Healthz(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
// api/serverinfo // api/serverinfo
func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) { func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200} res := GeneralResponse{Code: 200}

View File

@@ -21,6 +21,7 @@ import (
"net" "net"
"strconv" "strconv"
"sync" "sync"
"time"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
@@ -151,12 +152,28 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
xl := xlog.FromContextSafe(pxy.ctx) xl := xlog.FromContextSafe(pxy.ctx)
for _, listener := range pxy.listeners { for _, listener := range pxy.listeners {
go func(l net.Listener) { go func(l net.Listener) {
var tempDelay time.Duration // how long to sleep on accept failure
for { for {
// block // block
// if listener is closed, err returned // if listener is closed, err returned
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
xl.Info("listener is closed") if err, ok := err.(interface{ Temporary() bool }); ok && err.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
xl.Info("met temporary error: %s, sleep for %s ...", err, tempDelay)
time.Sleep(tempDelay)
continue
}
xl.Warn("listener is closed: %s", err)
return return
} }
xl.Info("get a user connection [%s]", c.RemoteAddr().String()) xl.Info("get a user connection [%s]", c.RemoteAddr().String())

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

@@ -19,7 +19,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io"
"net" "net"
"net/http" "net/http"
"sort" "sort"
@@ -258,14 +258,15 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
} }
// frp tls listener // frp tls listener
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool { svr.tlsListener = svr.muxer.Listen(2, 1, func(data []byte) bool {
return int(data[0]) == frpNet.FRPTLSHeadByte // tls first byte can be 0x16 only when vhost https port is not same with bind port
return int(data[0]) == frpNet.FRPTLSHeadByte || int(data[0]) == 0x16
}) })
// 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)
@@ -279,11 +280,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
// Create dashboard web server. // Create dashboard web server.
if cfg.DashboardPort > 0 { if cfg.DashboardPort > 0 {
// Init dashboard assets // Init dashboard assets
err = assets.Load(cfg.AssetsDir) assets.Load(cfg.AssetsDir)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
address := net.JoinHostPort(cfg.DashboardAddr, strconv.Itoa(cfg.DashboardPort)) address := net.JoinHostPort(cfg.DashboardAddr, strconv.Itoa(cfg.DashboardPort))
err = svr.RunDashboardServer(address) err = svr.RunDashboardServer(address)
@@ -395,20 +392,21 @@ func (svr *Service) HandleListener(l net.Listener) {
log.Trace("start check TLS connection...") log.Trace("start check TLS connection...")
originConn := c originConn := c
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout) var isTLS, custom bool
c, isTLS, custom, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
if err != nil { if err != nil {
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err) log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
originConn.Close() originConn.Close()
continue continue
} }
log.Trace("success check TLS connection") log.Trace("check TLS connection success, isTLS: %v custom: %v", isTLS, custom)
// Start a new goroutine for dealing connections. // Start a new goroutine to handle connection.
go func(ctx context.Context, frpConn net.Conn) { go func(ctx context.Context, frpConn net.Conn) {
if svr.cfg.TCPMux { if svr.cfg.TCPMux {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard fmuxCfg.LogOutput = io.Discard
session, err := fmux.Server(frpConn, fmuxCfg) session, err := fmux.Server(frpConn, fmuxCfg)
if err != nil { if err != nil {
log.Warn("Failed to create mux connection: %v", err) log.Warn("Failed to create mux connection: %v", err)

View File

@@ -1,18 +1,21 @@
package basic package basic
import ( import (
"crypto/tls"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/fatedier/frp/pkg/transport"
"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/httpserver"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"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 +53,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,13 +83,198 @@ 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) Protocol(protocol).
PortName(test.portName).
Explain(test.proxyName).
Ensure()
} }
}) })
} }
}) })
Describe("HTTP", func() {
It("proxy to HTTP server", func() {
serverConf := consts.DefaultServerConfig
vhostHTTPPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[%s]
type = http
local_port = {{ .%s }}
custom_domains = %s
`+extra, proxyName, framework.HTTPSimpleServerPort, customDomains)
}
tests := []struct {
proxyName string
customDomains 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
`,
},
{
proxyName: "multiple-custom-domains",
customDomains: "a.example.com, b.example.com",
},
}
// build all client config
for i, test := range tests {
if tests[i].customDomains == "" {
tests[i].customDomains = test.proxyName + ".example.com"
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
framework.NewRequestExpect(f).
Explain(test.proxyName + "-" + domain).
Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost(domain)
}).
Ensure()
}
}
// not exist host
framework.NewRequestExpect(f).
Explain("not exist host").
Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("not-exist.example.com")
}).
Ensure(framework.ExpectResponseCode(404))
})
})
Describe("HTTPS", func() {
It("proxy to HTTPS server", func() {
serverConf := consts.DefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_https_port = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[%s]
type = https
local_port = %d
custom_domains = %s
`+extra, proxyName, localPort, customDomains)
}
tests := []struct {
proxyName string
customDomains 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
`,
},
{
proxyName: "multiple-custom-domains",
customDomains: "a.example.com, b.example.com",
},
}
// build all client config
for i, test := range tests {
if tests[i].customDomains == "" {
tests[i].customDomains = test.proxyName + ".example.com"
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithTlsConfig(tlsConfig),
httpserver.WithResponse([]byte("test")),
)
f.RunServer("", localServer)
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
framework.NewRequestExpect(f).
Explain(test.proxyName + "-" + domain).
Port(vhostHTTPSPort).
RequestModify(func(r *request.Request) {
r.HTTPS().HTTPHost(domain).TLSConfig(&tls.Config{
ServerName: domain,
InsecureSkipVerify: true,
})
}).
ExpectResp([]byte("test")).
Ensure()
}
}
// not exist host
notExistDomain := "not-exist.example.com"
framework.NewRequestExpect(f).
Explain("not exist host").
Port(vhostHTTPSPort).
RequestModify(func(r *request.Request) {
r.HTTPS().HTTPHost(notExistDomain).TLSConfig(&tls.Config{
ServerName: notExistDomain,
InsecureSkipVerify: true,
})
}).
ExpectError(true).
Ensure()
})
})
Describe("STCP && SUDP", func() { Describe("STCP && SUDP", func() {
types := []string{"stcp", "sudp"} types := []string{"stcp", "sudp"}
for _, t := range types { for _, t := range types {
@@ -139,24 +327,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 +353,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 +370,91 @@ 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 { Protocol(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 := streamserver.New(streamserver.TCP, streamserver.WithBindPort(f.AllocPort()), streamserver.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.Addr("invalid").Proxy(proxyURL)
}).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.Addr(test.proxyName).Proxy(proxyURL)
}).ExpectResp([]byte(test.proxyName)).Explain(test.proxyName).Ensure()
}
})
})
}) })

78
test/e2e/basic/client.go Normal file
View File

@@ -0,0 +1,78 @@
package basic
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: ClientManage]", func() {
f := framework.NewDefaultFramework()
It("Update && Reload API", func() {
serverConf := consts.DefaultServerConfig
adminPort := f.AllocPort()
p1Port := f.AllocPort()
p2Port := f.AllocPort()
p3Port := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
admin_port = %d
[p1]
type = tcp
local_port = {{ .%s }}
remote_port = %d
[p2]
type = tcp
local_port = {{ .%s }}
remote_port = %d
[p3]
type = tcp
local_port = {{ .%s }}
remote_port = %d
`, adminPort,
framework.TCPEchoServerPort, p1Port,
framework.TCPEchoServerPort, p2Port,
framework.TCPEchoServerPort, p3Port)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(p1Port).Ensure()
framework.NewRequestExpect(f).Port(p2Port).Ensure()
framework.NewRequestExpect(f).Port(p3Port).Ensure()
client := clientsdk.New("127.0.0.1", adminPort)
conf, err := client.GetConfig()
framework.ExpectNoError(err)
newP2Port := f.AllocPort()
// change p2 port and remove p3 proxy
newClientConf := strings.ReplaceAll(conf, strconv.Itoa(p2Port), strconv.Itoa(newP2Port))
p3Index := strings.Index(newClientConf, "[p3]")
newClientConf = newClientConf[:p3Index]
err = client.UpdateConfig(newClientConf)
framework.ExpectNoError(err)
err = client.Reload()
framework.ExpectNoError(err)
time.Sleep(time.Second)
framework.NewRequestExpect(f).Port(p1Port).Explain("p1 port").Ensure()
framework.NewRequestExpect(f).Port(p2Port).Explain("original p2 port").ExpectError(true).Ensure()
framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure()
framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure()
})
})

View File

@@ -6,6 +6,8 @@ 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/cert"
"github.com/fatedier/frp/test/e2e/pkg/port"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )
@@ -16,16 +18,17 @@ type generalTestConfigures struct {
expectError bool expectError bool
} }
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) { func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
It(desc, func() { serverConf := consts.DefaultServerConfig
serverConf := consts.DefaultServerConfig clientConf := consts.DefaultClientConfig
clientConf := consts.DefaultClientConfig
serverConf += fmt.Sprintf(` serverConf += fmt.Sprintf(`
%s %s
`, configures.server) `, configures.server)
clientConf += fmt.Sprintf(` tcpPortName := port.GenName("TCP")
udpPortName := port.GenName("UDP")
clientConf += fmt.Sprintf(`
%s %s
[tcp] [tcp]
@@ -38,23 +41,21 @@ 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).Protocol("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 { // defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
framework.ExpectTCPRequestError(f.UsedPorts[framework.GenPortName("TCP")], func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
[]byte(consts.TestString), connTimeout, "tcp proxy") It(desc, func() {
framework.ExpectUDPRequestError(f.UsedPorts[framework.GenPortName("UDP")], runClientServerTest(f, configures)
[]byte(consts.TestString), connTimeout, "udp proxy")
}
}) })
} }
@@ -112,4 +113,140 @@ var _ = Describe("[Feature: Client-Server]", func() {
expectError: true, expectError: true,
}) })
}) })
Describe("TLS with custom certificate", func() {
supportProtocols := []string{"tcp", "kcp", "websocket"}
var (
caCrtPath string
serverCrtPath, serverKeyPath string
clientCrtPath, clientKeyPath string
)
JustBeforeEach(func() {
generator := &cert.SelfSignedCertGenerator{}
artifacts, err := generator.Generate("0.0.0.0")
framework.ExpectNoError(err)
caCrtPath = f.WriteTempFile("ca.crt", string(artifacts.CACert))
serverCrtPath = f.WriteTempFile("server.crt", string(artifacts.Cert))
serverKeyPath = f.WriteTempFile("server.key", string(artifacts.Key))
generator.SetCA(artifacts.CACert, artifacts.CAKey)
generator.Generate("0.0.0.0")
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
})
for _, protocol := range supportProtocols {
tmp := protocol
It("one-way authentication: "+tmp, func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
protocol = %s
kcp_bind_port = {{ .%s }}
tls_trusted_ca_file = %s
`, tmp, consts.PortServerName, caCrtPath),
client: fmt.Sprintf(`
protocol = %s
tls_enable = true
tls_cert_file = %s
tls_key_file = %s
`, tmp, clientCrtPath, clientKeyPath),
})
})
It("mutual authentication: "+tmp, func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
protocol = %s
kcp_bind_port = {{ .%s }}
tls_cert_file = %s
tls_key_file = %s
tls_trusted_ca_file = %s
`, tmp, consts.PortServerName, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(`
protocol = %s
tls_enable = true
tls_cert_file = %s
tls_key_file = %s
tls_trusted_ca_file = %s
`, tmp, clientCrtPath, clientKeyPath, caCrtPath),
})
})
}
})
Describe("TLS with custom certificate and specified server name", func() {
var (
caCrtPath string
serverCrtPath, serverKeyPath string
clientCrtPath, clientKeyPath string
)
JustBeforeEach(func() {
generator := &cert.SelfSignedCertGenerator{}
artifacts, err := generator.Generate("example.com")
framework.ExpectNoError(err)
caCrtPath = f.WriteTempFile("ca.crt", string(artifacts.CACert))
serverCrtPath = f.WriteTempFile("server.crt", string(artifacts.Cert))
serverKeyPath = f.WriteTempFile("server.key", string(artifacts.Key))
generator.SetCA(artifacts.CACert, artifacts.CAKey)
generator.Generate("example.com")
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
})
It("mutual authentication", func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
tls_cert_file = %s
tls_key_file = %s
tls_trusted_ca_file = %s
`, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(`
tls_enable = true
tls_server_name = example.com
tls_cert_file = %s
tls_key_file = %s
tls_trusted_ca_file = %s
`, clientCrtPath, clientKeyPath, caCrtPath),
})
})
It("mutual authentication with incorrect server name", func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
tls_cert_file = %s
tls_key_file = %s
tls_trusted_ca_file = %s
`, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(`
tls_enable = true
tls_server_name = invalid.com
tls_cert_file = %s
tls_key_file = %s
tls_trusted_ca_file = %s
`, clientCrtPath, clientKeyPath, caCrtPath),
expectError: true,
})
})
})
Describe("TLS with disable_custom_tls_first_byte", func() {
supportProtocols := []string{"tcp", "kcp", "websocket"}
for _, protocol := range supportProtocols {
tmp := protocol
defineClientServerTest("TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
server: fmt.Sprintf(`
kcp_bind_port = {{ .%s }}
protocol = %s
`, consts.PortServerName, protocol),
client: fmt.Sprintf(`
tls_enable = true
protocol = %s
disable_custom_tls_first_byte = true
`, protocol),
})
}
})
}) })

113
test/e2e/basic/cmd.go Normal file
View File

@@ -0,0 +1,113 @@
package basic
import (
"fmt"
"strconv"
"strings"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo"
)
const (
ConfigValidStr = "syntax is ok"
)
var _ = Describe("[Feature: Cmd]", func() {
f := framework.NewDefaultFramework()
Describe("Verify", func() {
It("frps valid", func() {
path := f.GenerateConfigFile(`
[common]
bind_addr = 0.0.0.0
bind_port = 7000
`)
_, output, err := f.RunFrps("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
})
It("frps invalid", func() {
path := f.GenerateConfigFile(`
[common]
bind_addr = 0.0.0.0
bind_port = 70000
`)
_, output, err := f.RunFrps("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
})
It("frpc valid", func() {
path := f.GenerateConfigFile(`
[common]
server_addr = 0.0.0.0
server_port = 7000
`)
_, output, err := f.RunFrpc("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
})
It("frpc invalid", func() {
path := f.GenerateConfigFile(`
[common]
server_addr = 0.0.0.0
server_port = 7000
protocol = invalid
`)
_, output, err := f.RunFrpc("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
})
})
Describe("Single proxy", func() {
It("TCP", func() {
serverPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort))
framework.ExpectNoError(err)
localPort := f.PortByName(framework.TCPEchoServerPort)
remotePort := f.AllocPort()
_, _, err = f.RunFrpc("tcp", "-s", fmt.Sprintf("127.0.0.1:%d", serverPort), "-t", "123", "-u", "test",
"-l", strconv.Itoa(localPort), "-r", strconv.Itoa(remotePort), "-n", "tcp_test")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
It("UDP", func() {
serverPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort))
framework.ExpectNoError(err)
localPort := f.PortByName(framework.UDPEchoServerPort)
remotePort := f.AllocPort()
_, _, err = f.RunFrpc("udp", "-s", fmt.Sprintf("127.0.0.1:%d", serverPort), "-t", "123", "-u", "test",
"-l", strconv.Itoa(localPort), "-r", strconv.Itoa(remotePort), "-n", "udp_test")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Protocol("udp").
Port(remotePort).Ensure()
})
It("HTTP", func() {
serverPort := f.AllocPort()
vhostHTTPPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort), "--vhost_http_port", strconv.Itoa(vhostHTTPPort))
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("http", "-s", "127.0.0.1:"+strconv.Itoa(serverPort), "-t", "123", "-u", "test",
"-n", "udp_test", "-l", strconv.Itoa(f.PortByName(framework.HTTPSimpleServerPort)),
"--custom_domain", "test.example.com")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("test.example.com")
}).
Ensure()
})
})
})

83
test/e2e/basic/config.go Normal file
View File

@@ -0,0 +1,83 @@
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/onsi/ginkgo"
)
var _ = Describe("[Feature: Config]", func() {
f := framework.NewDefaultFramework()
Describe("Template", func() {
It("render by env", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
portName := port.GenName("TCP")
serverConf += fmt.Sprintf(`
token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
`, "`", "`")
clientConf += fmt.Sprintf(`
token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, "`", "`", framework.TCPEchoServerPort, portName)
f.SetEnvs([]string{"FRP_TOKEN=123"})
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).PortName(portName).Ensure()
})
})
Describe("Includes", func() {
It("split tcp proxies into different files", func() {
serverPort := f.AllocPort()
serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
bind_addr = 0.0.0.0
bind_port = %d
`, serverPort))
remotePort := f.AllocPort()
proxyConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[tcp]
type = tcp
local_port = %d
remote_port = %d
`, f.PortByName(framework.TCPEchoServerPort), remotePort))
remotePort2 := f.AllocPort()
proxyConfigPath2 := f.GenerateConfigFile(fmt.Sprintf(`
[tcp2]
type = tcp
local_port = %d
remote_port = %d
`, f.PortByName(framework.TCPEchoServerPort), remotePort2))
clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
server_port = %d
includes = %s,%s
`, serverPort, proxyConfigPath, proxyConfigPath2))
_, _, err := f.RunFrps("-c", serverConfigPath)
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("-c", clientConfigPath)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
framework.NewRequestExpect(f).Port(remotePort2).Ensure()
})
})
})

326
test/e2e/basic/http.go Normal file
View File

@@ -0,0 +1,326 @@
package basic
import (
"fmt"
"net/http"
"net/url"
"strconv"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
"github.com/fatedier/frp/test/e2e/pkg/utils"
"github.com/gorilla/websocket"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: HTTP]", func() {
f := framework.NewDefaultFramework()
getDefaultServerConf := func(vhostHTTPPort int) string {
conf := consts.DefaultServerConfig + `
vhost_http_port = %d
`
return fmt.Sprintf(conf, vhostHTTPPort)
}
newHTTPServer := func(port int, respContent string) *httpserver.Server {
return httpserver.New(
httpserver.WithBindPort(port),
httpserver.WithHandler(framework.SpecifiedHTTPBodyHandler([]byte(respContent))),
)
}
It("HTTP route by locations", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
fooPort := f.AllocPort()
f.RunServer("", newHTTPServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = http
local_port = %d
custom_domains = normal.example.com
locations = /,/foo
[bar]
type = http
local_port = %d
custom_domains = normal.example.com
locations = /bar
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// foo path
framework.NewRequestExpect(f).Explain("foo path").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath("/foo")
}).
ExpectResp([]byte("foo")).
Ensure()
// bar path
framework.NewRequestExpect(f).Explain("bar path").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath("/bar")
}).
ExpectResp([]byte("bar")).
Ensure()
// other path
framework.NewRequestExpect(f).Explain("other path").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath("/other")
}).
ExpectResp([]byte("foo")).
Ensure()
})
It("HTTP Basic Auth", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = {{ .%s }}
custom_domains = normal.example.com
http_user = test
http_pwd = test
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not set auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
Ensure(framework.ExpectResponseCode(401))
// set incorrect auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPHeaders(map[string]string{
"Authorization": utils.BasicAuth("test", "invalid"),
})
}).
Ensure(framework.ExpectResponseCode(401))
// set correct auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPHeaders(map[string]string{
"Authorization": utils.BasicAuth("test", "test"),
})
}).
Ensure()
})
It("Wildcard domain", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = {{ .%s }}
custom_domains = *.example.com
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not match host
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("not-match.test.com")
}).
Ensure(framework.ExpectResponseCode(404))
// test.example.com match *.example.com
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("test.example.com")
}).
Ensure()
// sub.test.example.com match *.example.com
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("sub.test.example.com")
}).
Ensure()
})
It("Subdomain", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
serverConf += `
subdomain_host = example.com
`
fooPort := f.AllocPort()
f.RunServer("", newHTTPServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = http
local_port = %d
subdomain = foo
[bar]
type = http
local_port = %d
subdomain = bar
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// foo
framework.NewRequestExpect(f).Explain("foo subdomain").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("foo.example.com")
}).
ExpectResp([]byte("foo")).
Ensure()
// bar
framework.NewRequestExpect(f).Explain("bar subdomain").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("bar.example.com")
}).
ExpectResp([]byte("bar")).
Ensure()
})
It("Modify headers", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(req.Header.Get("X-From-Where")))
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = %d
custom_domains = normal.example.com
header_X-From-Where = frp
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not set auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
ExpectResp([]byte("frp")). // local http server will write this X-From-Where header to response body
Ensure()
})
It("Host Header Rewrite", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(req.Host))
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = %d
custom_domains = normal.example.com
host_header_rewrite = rewrite.example.com
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
ExpectResp([]byte("rewrite.example.com")). // local http server will write host header to response body
Ensure()
})
It("Websocket protocol", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
upgrader := websocket.Upgrader{}
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
c, err := upgrader.Upgrade(w, req, nil)
if err != nil {
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
break
}
err = c.WriteMessage(mt, message)
if err != nil {
break
}
}
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = %d
custom_domains = 127.0.0.1
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
u := url.URL{Scheme: "ws", Host: "127.0.0.1:" + strconv.Itoa(vhostHTTPPort)}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
framework.ExpectNoError(err)
err = c.WriteMessage(websocket.TextMessage, []byte(consts.TestString))
framework.ExpectNoError(err)
_, msg, err := c.ReadMessage()
framework.ExpectNoError(err)
framework.ExpectEqualValues(consts.TestString, string(msg))
})
})

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

@@ -0,0 +1,147 @@
package basic
import (
"fmt"
"net"
"strconv"
"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"
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
. "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 = 20000-25000,25002,30000-50000
`
tcpPortName := port.GenName("TCP", port.WithRangePorts(20000, 25000))
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).Port(25003).ExpectError(true).Ensure()
// Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
// UDP
// Allowed in range
framework.NewRequestExpect(f).Protocol("udp").PortName(udpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.UDP().Port(25003)
}).ExpectError(true).Ensure()
})
It("Alloc Random Port", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
adminPort := f.AllocPort()
clientConf += fmt.Sprintf(`
admin_port = %d
[tcp]
type = tcp
local_port = {{ .%s }}
[udp]
type = udp
local_port = {{ .%s }}
`, adminPort, framework.TCPEchoServerPort, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
client := clientsdk.New("127.0.0.1", adminPort)
// tcp random port
status, err := client.GetProxyStatus("tcp")
framework.ExpectNoError(err)
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
framework.ExpectNoError(err)
port, err := strconv.Atoi(portStr)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(port).Ensure()
// udp random port
status, err = client.GetProxyStatus("udp")
framework.ExpectNoError(err)
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
framework.ExpectNoError(err)
port, err = strconv.Atoi(portStr)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Protocol("udp").Port(port).Ensure()
})
It("Port Reuse", func() {
serverConf := consts.DefaultServerConfig
// Use same port as PortServer
serverConf += fmt.Sprintf(`
vhost_http_port = {{ .%s }}
`, consts.PortServerName)
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[http]
type = http
local_port = {{ .%s }}
custom_domains = example.com
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).PortName(consts.PortServerName).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

@@ -11,6 +11,7 @@ import (
// test source // test source
_ "github.com/fatedier/frp/test/e2e/basic" _ "github.com/fatedier/frp/test/e2e/basic"
_ "github.com/fatedier/frp/test/e2e/features"
_ "github.com/fatedier/frp/test/e2e/plugin" _ "github.com/fatedier/frp/test/e2e/plugin"
_ "github.com/onsi/ginkgo" _ "github.com/onsi/ginkgo"

View File

@@ -2,7 +2,6 @@ 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"
@@ -10,8 +9,6 @@ import (
. "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 +17,17 @@ var _ = Describe("[Feature: Example]", func() {
serverConf := consts.DefaultServerConfig serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(` clientConf += fmt.Sprintf(`
[tcp] [tcp]
type = tcp type = tcp
local_port = {{ .%s }} local_port = {{ .%s }}
remote_port = {{ .%s }} remote_port = %d
`, framework.TCPEchoServerPort, framework.GenPortName("TCP")) `, framework.TCPEchoServerPort, remotePort)
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).Port(remotePort).Ensure()
}) })
}) })
}) })

View File

@@ -0,0 +1,47 @@
package features
import (
"fmt"
"strings"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: Bandwidth Limit]", func() {
f := framework.NewDefaultFramework()
It("Proxy Bandwidth Limit", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))
f.RunServer("", localServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = %d
remote_port = %d
bandwidth_limit = 10KB
`, localPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
content := strings.Repeat("a", 50*1024) // 5KB
start := time.Now()
framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
r.Body([]byte(content)).Timeout(30 * time.Second)
}).ExpectResp([]byte(content)).Ensure()
duration := time.Now().Sub(start)
framework.ExpectTrue(duration.Seconds() > 7, "100Kb with 10KB limit, want > 7 seconds, but got %d seconds", duration.Seconds())
})
})

View File

@@ -0,0 +1,65 @@
package features
import (
"fmt"
"time"
"github.com/fatedier/frp/test/e2e/framework"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: Chaos]", func() {
f := framework.NewDefaultFramework()
It("reconnect after frps restart", func() {
serverPort := f.AllocPort()
serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
bind_addr = 0.0.0.0
bind_port = %d
`, serverPort))
remotePort := f.AllocPort()
clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
server_port = %d
log_level = trace
[tcp]
type = tcp
local_port = %d
remote_port = %d
`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort))
// 1. start frps and frpc, expect request success
ps, _, err := f.RunFrps("-c", serverConfigPath)
framework.ExpectNoError(err)
pc, _, err := f.RunFrpc("-c", clientConfigPath)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
// 2. stop frps, expect request failed
ps.Stop()
time.Sleep(200 * time.Millisecond)
framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
// 3. restart frps, expect request success
_, _, err = f.RunFrps("-c", serverConfigPath)
framework.ExpectNoError(err)
time.Sleep(2 * time.Second)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
// 4. stop frpc, expect request failed
pc.Stop()
time.Sleep(200 * time.Millisecond)
framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
// 5. restart frpc, expect request success
_, _, err = f.RunFrpc("-c", clientConfigPath)
framework.ExpectNoError(err)
time.Sleep(time.Second)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
})

238
test/e2e/features/group.go Normal file
View File

@@ -0,0 +1,238 @@
package features
import (
"fmt"
"strconv"
"sync"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: Group]", func() {
f := framework.NewDefaultFramework()
newHTTPServer := func(port int, respContent string) *httpserver.Server {
return httpserver.New(
httpserver.WithBindPort(port),
httpserver.WithHandler(framework.SpecifiedHTTPBodyHandler([]byte(respContent))),
)
}
validateFooBarResponse := func(resp *request.Response) bool {
if string(resp.Content) == "foo" || string(resp.Content) == "bar" {
return true
}
return false
}
doFooBarHTTPRequest := func(vhostPort int, host string) []string {
results := []string{}
var wait sync.WaitGroup
var mu sync.Mutex
expectFn := func() {
framework.NewRequestExpect(f).Port(vhostPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost(host)
}).
Ensure(validateFooBarResponse, func(resp *request.Response) bool {
mu.Lock()
defer mu.Unlock()
results = append(results, string(resp.Content))
return true
})
}
for i := 0; i < 10; i++ {
wait.Add(1)
go func() {
defer wait.Done()
expectFn()
}()
}
wait.Wait()
return results
}
Describe("Load Balancing", func() {
It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
fooPort := f.AllocPort()
fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
f.RunServer("", fooServer)
barPort := f.AllocPort()
barServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(barPort), streamserver.WithRespContent([]byte("bar")))
f.RunServer("", barServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[foo]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
[bar]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
`, fooPort, remotePort, barPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
fooCount := 0
barCount := 0
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Explain("times " + strconv.Itoa(i)).Port(remotePort).Ensure(func(resp *request.Response) bool {
switch string(resp.Content) {
case "foo":
fooCount++
case "bar":
barCount++
default:
return false
}
return true
})
}
framework.ExpectTrue(fooCount > 1 && barCount > 1, "fooCount: %d, barCount: %d", fooCount, barCount)
})
})
Describe("Health Check", func() {
It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
fooPort := f.AllocPort()
fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
f.RunServer("", fooServer)
barPort := f.AllocPort()
barServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(barPort), streamserver.WithRespContent([]byte("bar")))
f.RunServer("", barServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[foo]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
health_check_type = tcp
health_check_interval_s = 1
[bar]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
health_check_type = tcp
health_check_interval_s = 1
`, fooPort, remotePort, barPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// check foo and bar is ok
results := []string{}
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true
})
}
framework.ExpectContainElements(results, []string{"foo", "bar"})
// close bar server, check foo is ok
barServer.Close()
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Port(remotePort).ExpectResp([]byte("foo")).Ensure()
}
// resume bar server, check foo and bar is ok
f.RunServer("", barServer)
time.Sleep(2 * time.Second)
results = []string{}
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true
})
}
framework.ExpectContainElements(results, []string{"foo", "bar"})
})
It("HTTP", func() {
vhostPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostPort)
clientConf := consts.DefaultClientConfig
fooPort := f.AllocPort()
fooServer := newHTTPServer(fooPort, "foo")
f.RunServer("", fooServer)
barPort := f.AllocPort()
barServer := newHTTPServer(barPort, "bar")
f.RunServer("", barServer)
clientConf += fmt.Sprintf(`
[foo]
type = http
local_port = %d
custom_domains = example.com
group = test
group_key = 123
health_check_type = http
health_check_interval_s = 1
health_check_url = /healthz
[bar]
type = http
local_port = %d
custom_domains = example.com
group = test
group_key = 123
health_check_type = http
health_check_interval_s = 1
health_check_url = /healthz
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// check foo and bar is ok
results := doFooBarHTTPRequest(vhostPort, "example.com")
framework.ExpectContainElements(results, []string{"foo", "bar"})
// close bar server, check foo is ok
barServer.Close()
time.Sleep(2 * time.Second)
results = doFooBarHTTPRequest(vhostPort, "example.com")
framework.ExpectContainElements(results, []string{"foo"})
framework.ExpectNotContainElements(results, []string{"bar"})
// resume bar server, check foo and bar is ok
f.RunServer("", barServer)
time.Sleep(2 * time.Second)
results = doFooBarHTTPRequest(vhostPort, "example.com")
framework.ExpectContainElements(results, []string{"foo", "bar"})
})
})
})

View File

@@ -0,0 +1,54 @@
package features
import (
"fmt"
"strings"
"time"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: Monitor]", func() {
f := framework.NewDefaultFramework()
It("Prometheus metrics", func() {
dashboardPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
enable_prometheus = true
dashboard_addr = 0.0.0.0
dashboard_port = %d
`, dashboardPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
time.Sleep(500 * time.Millisecond)
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().Port(dashboardPort).HTTPPath("/metrics")
}).Ensure(func(resp *request.Response) bool {
log.Trace("prometheus metrics response: \n%s", resp.Content)
if resp.Code != 200 {
return false
}
if !strings.Contains(string(resp.Content), "traffic_in") {
return false
}
return true
})
})
})

View File

@@ -0,0 +1,152 @@
package features
import (
"bufio"
"fmt"
"net"
"net/http"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
"github.com/fatedier/frp/test/e2e/pkg/rpc"
. "github.com/onsi/ginkgo"
pp "github.com/pires/go-proxyproto"
)
var _ = Describe("[Feature: Real IP]", func() {
f := framework.NewDefaultFramework()
It("HTTP X-Forwarded-For", func() {
vhostHTTPPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(req.Header.Get("X-Forwarded-For")))
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = %d
custom_domains = normal.example.com
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
ExpectResp([]byte("127.0.0.1")).
Ensure()
})
Describe("Proxy Protocol", func() {
It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort),
streamserver.WithCustomHandler(func(c net.Conn) {
defer c.Close()
rd := bufio.NewReader(c)
ppHeader, err := pp.Read(rd)
if err != nil {
log.Error("read proxy protocol error: %v", err)
return
}
for {
if _, err := rpc.ReadBytes(rd); err != nil {
return
}
buf := []byte(ppHeader.SourceAddr.String())
rpc.WriteBytes(c, buf)
}
}))
f.RunServer("", localServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = %d
remote_port = %d
proxy_protocol_version = v2
`, localPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
log.Trace("ProxyProtocol get SourceAddr: %s", string(resp.Content))
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
if err != nil {
return false
}
if addr.IP.String() != "127.0.0.1" {
return false
}
return true
})
})
It("HTTP", func() {
vhostHTTPPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
var srcAddrRecord string
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort),
streamserver.WithCustomHandler(func(c net.Conn) {
defer c.Close()
rd := bufio.NewReader(c)
ppHeader, err := pp.Read(rd)
if err != nil {
log.Error("read proxy protocol error: %v", err)
return
}
srcAddrRecord = ppHeader.SourceAddr.String()
}))
f.RunServer("", localServer)
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = %d
custom_domains = normal.example.com
proxy_protocol_version = v2
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).Ensure(framework.ExpectResponseCode(404))
log.Trace("ProxyProtocol get SourceAddr: %s", srcAddrRecord)
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
framework.ExpectNoError(err, srcAddrRecord)
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
})
})
})

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

@@ -14,6 +14,10 @@ func ExpectEqualValues(actual interface{}, extra interface{}, explain ...interfa
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...) gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
} }
func ExpectEqualValuesWithOffset(offset int, actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
}
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises // ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) { func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...) gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
@@ -24,6 +28,10 @@ func ExpectError(err error, explain ...interface{}) {
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...) gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
} }
func ExpectErrorWithOffset(offset int, err error, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
}
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error. // ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
func ExpectNoError(err error, explain ...interface{}) { func ExpectNoError(err error, explain ...interface{}) {
ExpectNoErrorWithOffset(1, err, explain...) ExpectNoErrorWithOffset(1, err, explain...)
@@ -40,6 +48,14 @@ func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...) gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
} }
func ExpectContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
}
func ExpectNotContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
}
// ExpectHaveKey expects the actual map has the key in the keyset // ExpectHaveKey expects the actual map has the key in the keyset
func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) { func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...) gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
@@ -53,3 +69,7 @@ func ExpectEmpty(actual interface{}, explain ...interface{}) {
func ExpectTrue(actual interface{}, explain ...interface{}) { func ExpectTrue(actual interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...) gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
} }
func ExpectTrueWithOffset(offset int, actual interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
}

Some files were not shown because too many files have changed in this diff Show More