Merge branch 'dev' into dev
This commit is contained in:
commit
cf90df6ffa
@ -2,16 +2,14 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.16-node
|
- image: cimg/go:1.18-node
|
||||||
working_directory: /go/src/github.com/fatedier/frp
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: make
|
- run: make
|
||||||
- run: make alltest
|
- run: make alltest
|
||||||
go-version-last:
|
go-version-last:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.15-node
|
- image: cimg/go:1.17-node
|
||||||
working_directory: /go/src/github.com/fatedier/frp
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: make
|
- run: make
|
||||||
|
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [fatedier]
|
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -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
77
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal 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"
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -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.
|
|
||||||
|
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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. -->
|
|
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal 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"
|
2
.github/workflows/build-and-push-image.yml
vendored
2
.github/workflows/build-and-push-image.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- 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.16
|
go-version: 1.18
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
# https://github.com/actions/setup-go/issues/107
|
# https://github.com/actions/setup-go/issues/107
|
||||||
|
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@ -15,7 +15,7 @@ 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.18
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
# https://github.com/actions/setup-go/issues/107
|
# https://github.com/actions/setup-go/issues/107
|
||||||
|
12
.github/workflows/stale.yml
vendored
12
.github/workflows/stale.yml
vendored
@ -12,15 +12,17 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v5
|
||||||
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: "PRs go stale after 30d of inactivity. Stale PRs 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 }}
|
||||||
|
exempt-all-pr-milestones: true
|
||||||
|
exempt-all-pr-assignees: true
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
builds:
|
builds:
|
||||||
- skip: true
|
- skip: true
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: '{{ .ProjectName }}_sha256_checksums.txt'
|
||||||
|
algorithm: sha256
|
||||||
|
extra_files:
|
||||||
|
- glob: ./release/packages/*
|
||||||
release:
|
release:
|
||||||
# Same as for github
|
# Same as for github
|
||||||
# Note: it can only be one: either github, gitlab or gitea
|
# Note: it can only be one: either github, gitlab or gitea
|
||||||
|
8
Makefile
8
Makefile
@ -12,13 +12,13 @@ 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 ./...
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
frps:
|
frps:
|
||||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ e2e:
|
|||||||
e2e-trace:
|
e2e-trace:
|
||||||
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
||||||
|
|
||||||
alltest: gotest e2e
|
alltest: vet gotest e2e
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ./bin/frpc
|
rm -f ./bin/frpc
|
||||||
|
47
README.md
47
README.md
@ -6,6 +6,32 @@
|
|||||||
|
|
||||||
[README](README.md) | [中文文档](README_zh.md)
|
[README](README.md) | [中文文档](README_zh.md)
|
||||||
|
|
||||||
|
<h3 align="center">Platinum Sponsors</h3>
|
||||||
|
<!--platinum sponsors start-->
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
|
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_doppler.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--platinum sponsors end-->
|
||||||
|
|
||||||
|
<h3 align="center">Gold Sponsors</h3>
|
||||||
|
<!--gold sponsors start-->
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
|
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
|
<h3 align="center">Silver Sponsors</h3>
|
||||||
|
|
||||||
|
* Sakura Frp - 欢迎点击 "加入我们"
|
||||||
|
|
||||||
## What is frp?
|
## What is frp?
|
||||||
|
|
||||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
||||||
@ -67,8 +93,7 @@ frp also has a P2P connect mode.
|
|||||||
* [Development Plan](#development-plan)
|
* [Development Plan](#development-plan)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
* [AliPay](#alipay)
|
* [GitHub Sponsors](#github-sponsors)
|
||||||
* [Wechat Pay](#wechat-pay)
|
|
||||||
* [PayPal](#paypal)
|
* [PayPal](#paypal)
|
||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
@ -77,7 +102,9 @@ frp also has a P2P connect mode.
|
|||||||
|
|
||||||
frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
|
frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
|
||||||
|
|
||||||
**The protocol might change at a release and we don't promise backwards compatibility. Please check the release log when upgrading the client and the server.**
|
We are working on v2 version and trying to do some code refactor and improvements. It won't be compatible with v1.
|
||||||
|
|
||||||
|
We will switch v0 to v1 at the right time and only accept bug fixes and improvements instead of big feature requirements.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@ -867,7 +894,7 @@ In this example, it will set header `X-From-Where: frp` in the HTTP request.
|
|||||||
|
|
||||||
This feature is for http proxy only.
|
This feature is for http proxy only.
|
||||||
|
|
||||||
You can get user's real IP from HTTP request headers `X-Forwarded-For` and `X-Real-IP`.
|
You can get user's real IP from HTTP request headers `X-Forwarded-For`.
|
||||||
|
|
||||||
#### Proxy Protocol
|
#### Proxy Protocol
|
||||||
|
|
||||||
@ -995,11 +1022,13 @@ server_port = 7000
|
|||||||
type = tcpmux
|
type = tcpmux
|
||||||
multiplexer = httpconnect
|
multiplexer = httpconnect
|
||||||
custom_domains = test1
|
custom_domains = test1
|
||||||
|
local_port = 80
|
||||||
|
|
||||||
[proxy2]
|
[proxy2]
|
||||||
type = tcpmux
|
type = tcpmux
|
||||||
multiplexer = httpconnect
|
multiplexer = httpconnect
|
||||||
custom_domains = test2
|
custom_domains = test2
|
||||||
|
local_port = 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:
|
In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:
|
||||||
@ -1085,15 +1114,11 @@ Interested in getting involved? We would like to help you!
|
|||||||
|
|
||||||
If frp helps you a lot, you can support us by:
|
If frp helps you a lot, you can support us by:
|
||||||
|
|
||||||
frp QQ group: 606194980
|
### GitHub Sponsors
|
||||||
|
|
||||||
### AliPay
|
Support us by [Github Sponsors](https://github.com/sponsors/fatedier).
|
||||||
|
|
||||||

|
You can have your company's logo placed on README file of this project.
|
||||||
|
|
||||||
### Wechat Pay
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### PayPal
|
### PayPal
|
||||||
|
|
||||||
|
40
README_zh.md
40
README_zh.md
@ -7,6 +7,32 @@
|
|||||||
|
|
||||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||||
|
|
||||||
|
<h3 align="center">Platinum Sponsors</h3>
|
||||||
|
<!--platinum sponsors start-->
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
|
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_doppler.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--platinum sponsors end-->
|
||||||
|
|
||||||
|
<h3 align="center">Gold Sponsors</h3>
|
||||||
|
<!--gold sponsors start-->
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
|
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
|
<h3 align="center">Silver Sponsors</h3>
|
||||||
|
|
||||||
|
* Sakura Frp - 欢迎点击 "加入我们"
|
||||||
|
|
||||||
## 为什么使用 frp ?
|
## 为什么使用 frp ?
|
||||||
|
|
||||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
||||||
@ -25,6 +51,10 @@ frp 目前已被很多公司广泛用于测试、生产环境。
|
|||||||
|
|
||||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||||
|
|
||||||
|
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
|
||||||
|
|
||||||
|
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
||||||
@ -46,6 +76,12 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
|
|
||||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||||
|
|
||||||
|
### GitHub Sponsors
|
||||||
|
|
||||||
|
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||||
|
|
||||||
|
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||||
|
|
||||||
### 知识星球
|
### 知识星球
|
||||||
|
|
||||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||||
@ -59,7 +95,3 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
### 微信支付捐赠
|
### 微信支付捐赠
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Paypal 捐赠
|
|
||||||
|
|
||||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
### New
|
||||||
|
|
||||||
|
* Support go http pprof.
|
||||||
|
|
||||||
|
### Improve
|
||||||
|
|
||||||
|
* Change underlying TCP connection keepalive interval to 2 hours.
|
||||||
|
* Create new connection to server for `sudp` visitor when needed, to avoid frequent reconnections.
|
@ -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
14
assets/frpc/embed.go
Normal 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)
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
14
assets/frps/embed.go
Normal file
14
assets/frps/embed.go
Normal 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)
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -17,6 +17,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
@ -26,27 +27,39 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpServerReadTimeout = 10 * time.Second
|
httpServerReadTimeout = 60 * time.Second
|
||||||
httpServerWriteTimeout = 10 * time.Second
|
httpServerWriteTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
func (svr *Service) RunAdminServer(address string) (err error) {
|
||||||
// url router
|
// url router
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
router.HandleFunc("/healthz", svr.healthz)
|
||||||
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// debug
|
||||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
if svr.cfg.PprofEnable {
|
||||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
|
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
subRouter := router.NewRoute().Subrouter()
|
||||||
|
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||||
|
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
|
// api, see admin_api.go
|
||||||
|
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||||
|
|
||||||
// view
|
// view
|
||||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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,6 +33,11 @@ type GeneralResponse struct {
|
|||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /healthz
|
||||||
|
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}
|
||||||
|
|
||||||
// GET api/reload
|
// GET api/reload
|
||||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
@ -251,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)
|
||||||
@ -268,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()
|
||||||
@ -307,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)
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/golib/control/shutdown"
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -182,9 +183,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()
|
||||||
}
|
}
|
||||||
@ -227,12 +235,38 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort))
|
proxyType, addr, auth, err := libdial.ParseProxyURL(ctl.clientCfg.HTTPProxy)
|
||||||
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig)
|
if err != nil {
|
||||||
|
xl.Error("fail to parse proxy url")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dialOptions := []libdial.DialOption{}
|
||||||
|
protocol := ctl.clientCfg.Protocol
|
||||||
|
if protocol == "websocket" {
|
||||||
|
protocol = "tcp"
|
||||||
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
||||||
|
}
|
||||||
|
if ctl.clientCfg.ConnectServerLocalIP != "" {
|
||||||
|
dialOptions = append(dialOptions, libdial.WithLocalAddr(ctl.clientCfg.ConnectServerLocalIP))
|
||||||
|
}
|
||||||
|
dialOptions = append(dialOptions,
|
||||||
|
libdial.WithProtocol(protocol),
|
||||||
|
libdial.WithTimeout(time.Duration(ctl.clientCfg.DialServerTimeout)*time.Second),
|
||||||
|
libdial.WithKeepAlive(time.Duration(ctl.clientCfg.DialServerKeepAlive)*time.Second),
|
||||||
|
libdial.WithProxy(proxyType, addr),
|
||||||
|
libdial.WithProxyAuth(auth),
|
||||||
|
libdial.WithTLSConfig(tlsConfig),
|
||||||
|
libdial.WithAfterHook(libdial.AfterHook{
|
||||||
|
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, ctl.clientCfg.DisableCustomTLSFirstByte),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
conn, err = libdial.Dial(
|
||||||
|
net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort)),
|
||||||
|
dialOptions...,
|
||||||
|
)
|
||||||
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)
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -301,16 +335,27 @@ func (ctl *Control) msgHandler() {
|
|||||||
}()
|
}()
|
||||||
defer ctl.msgHandlerShutdown.Done()
|
defer ctl.msgHandlerShutdown.Done()
|
||||||
|
|
||||||
|
var hbSendCh <-chan time.Time
|
||||||
|
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
||||||
|
// Just keep it here to keep compatible with old version frps.
|
||||||
|
if ctl.clientCfg.HeartbeatInterval > 0 {
|
||||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
||||||
defer hbSend.Stop()
|
defer hbSend.Stop()
|
||||||
|
hbSendCh = hbSend.C
|
||||||
|
}
|
||||||
|
|
||||||
|
var hbCheckCh <-chan time.Time
|
||||||
|
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||||
|
if ctl.clientCfg.HeartbeatInterval > 0 && ctl.clientCfg.HeartbeatTimeout > 0 && !ctl.clientCfg.TCPMux {
|
||||||
hbCheck := time.NewTicker(time.Second)
|
hbCheck := time.NewTicker(time.Second)
|
||||||
defer hbCheck.Stop()
|
defer hbCheck.Stop()
|
||||||
|
hbCheckCh = hbCheck.C
|
||||||
|
}
|
||||||
|
|
||||||
ctl.lastPong = time.Now()
|
ctl.lastPong = time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-hbSend.C:
|
case <-hbSendCh:
|
||||||
// send heartbeat to server
|
// send heartbeat to server
|
||||||
xl.Debug("send heartbeat to server")
|
xl.Debug("send heartbeat to server")
|
||||||
pingMsg := &msg.Ping{}
|
pingMsg := &msg.Ping{}
|
||||||
@ -319,7 +364,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctl.sendCh <- pingMsg
|
ctl.sendCh <- pingMsg
|
||||||
case <-hbCheck.C:
|
case <-hbCheckCh:
|
||||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
// let reader() stop
|
// let reader() stop
|
||||||
|
@ -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)
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -36,6 +35,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
pp "github.com/pires/go-proxyproto"
|
pp "github.com/pires/go-proxyproto"
|
||||||
@ -347,22 +347,18 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|||||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Send detect message
|
// Send detect message
|
||||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
|
||||||
if len(array) <= 1 {
|
if err != nil {
|
||||||
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
|
||||||
}
|
}
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
/*
|
|
||||||
for i := 1000; i < 65000; i++ {
|
port, err := strconv.ParseInt(portStr, 10, 64)
|
||||||
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
xl.Trace("send all detect msg done")
|
xl.Trace("send all detect msg done")
|
||||||
|
|
||||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||||
@ -370,7 +366,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|||||||
// Listen for clientConn's address and wait for visitor connection
|
// Listen for clientConn's address and wait for visitor connection
|
||||||
lConn, err := net.ListenUDP("udp", laddr)
|
lConn, err := net.ListenUDP("udp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("listen on visitorConn's local adress error: %v", err)
|
xl.Error("listen on visitorConn's local address error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer lConn.Close()
|
defer lConn.Close()
|
||||||
@ -401,7 +397,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)
|
||||||
@ -791,7 +787,10 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIP, localInfo.LocalPort))
|
localConn, err := libdial.Dial(
|
||||||
|
net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort)),
|
||||||
|
libdial.WithTimeout(10*time.Second),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
||||||
|
@ -17,12 +17,13 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -34,12 +35,20 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/golib/crypto"
|
||||||
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
crypto.DefaultSalt = "frp"
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
// Service is a client service.
|
// Service is a client service.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// uniq id got from frps, attach it in loginMsg
|
// uniq id got from frps, attach it in loginMsg
|
||||||
@ -97,6 +106,21 @@ func (svr *Service) GetController() *Control {
|
|||||||
func (svr *Service) Run() error {
|
func (svr *Service) Run() error {
|
||||||
xl := xlog.FromContextSafe(svr.ctx)
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
|
|
||||||
|
// set custom DNSServer
|
||||||
|
if svr.cfg.DNSServer != "" {
|
||||||
|
dnsAddr := svr.cfg.DNSServer
|
||||||
|
if !strings.Contains(dnsAddr, ":") {
|
||||||
|
dnsAddr += ":53"
|
||||||
|
}
|
||||||
|
// Change default dns server for frpc
|
||||||
|
net.DefaultResolver = &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return net.Dial("udp", dnsAddr)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// login to frps
|
// login to frps
|
||||||
for {
|
for {
|
||||||
conn, session, err := svr.login()
|
conn, session, err := svr.login()
|
||||||
@ -108,7 +132,7 @@ func (svr *Service) Run() error {
|
|||||||
if svr.cfg.LoginFailExit {
|
if svr.cfg.LoginFailExit {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
time.Sleep(10 * time.Second)
|
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
||||||
} else {
|
} else {
|
||||||
// login success
|
// login success
|
||||||
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||||
@ -124,13 +148,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)
|
||||||
}
|
}
|
||||||
@ -160,8 +181,11 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
|
|
||||||
// the first three retry with no delay
|
// the first three retry with no delay
|
||||||
if reconnectCounts > 3 {
|
if reconnectCounts > 3 {
|
||||||
time.Sleep(reconnectDelay)
|
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
||||||
|
xl.Info("wait %v to reconnect", reconnectDelay)
|
||||||
reconnectDelay *= 2
|
reconnectDelay *= 2
|
||||||
|
} else {
|
||||||
|
util.RandomSleep(time.Second, 0, 0.5)
|
||||||
}
|
}
|
||||||
reconnectCounts++
|
reconnectCounts++
|
||||||
|
|
||||||
@ -177,19 +201,13 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
xl.Info("try to reconnect to server...")
|
xl.Info("try to reconnect to server...")
|
||||||
conn, session, err := svr.login()
|
conn, session, err := svr.login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("reconnect to server error: %v", err)
|
xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
|
||||||
time.Sleep(delayTime)
|
util.RandomSleep(delayTime, 0.9, 1.1)
|
||||||
|
|
||||||
opErr := &net.OpError{}
|
|
||||||
// quick retry for dial error
|
|
||||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
|
||||||
delayTime = 2 * time.Second
|
|
||||||
} else {
|
|
||||||
delayTime = delayTime * 2
|
delayTime = delayTime * 2
|
||||||
if delayTime > maxDelayTime {
|
if delayTime > maxDelayTime {
|
||||||
delayTime = maxDelayTime
|
delayTime = maxDelayTime
|
||||||
}
|
}
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// reconnect success, init delayTime
|
// reconnect success, init delayTime
|
||||||
@ -231,8 +249,35 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort))
|
proxyType, addr, auth, err := libdial.ParseProxyURL(svr.cfg.HTTPProxy)
|
||||||
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig)
|
if err != nil {
|
||||||
|
xl.Error("fail to parse proxy url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dialOptions := []libdial.DialOption{}
|
||||||
|
protocol := svr.cfg.Protocol
|
||||||
|
if protocol == "websocket" {
|
||||||
|
protocol = "tcp"
|
||||||
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
||||||
|
}
|
||||||
|
if svr.cfg.ConnectServerLocalIP != "" {
|
||||||
|
dialOptions = append(dialOptions, libdial.WithLocalAddr(svr.cfg.ConnectServerLocalIP))
|
||||||
|
}
|
||||||
|
dialOptions = append(dialOptions,
|
||||||
|
libdial.WithProtocol(protocol),
|
||||||
|
libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
|
||||||
|
libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
|
||||||
|
libdial.WithProxy(proxyType, addr),
|
||||||
|
libdial.WithProxyAuth(auth),
|
||||||
|
libdial.WithTLSConfig(tlsConfig),
|
||||||
|
libdial.WithAfterHook(libdial.AfterHook{
|
||||||
|
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, svr.cfg.DisableCustomTLSFirstByte),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
conn, err = libdial.Dial(
|
||||||
|
net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)),
|
||||||
|
dialOptions...,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -248,8 +293,8 @@ 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 = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * 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
|
||||||
@ -311,13 +356,28 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
|
|||||||
svr.visitorCfgs = visitorCfgs
|
svr.visitorCfgs = visitorCfgs
|
||||||
svr.cfgMu.Unlock()
|
svr.cfgMu.Unlock()
|
||||||
|
|
||||||
|
svr.ctlMu.RLock()
|
||||||
|
ctl := svr.ctl
|
||||||
|
svr.ctlMu.RUnlock()
|
||||||
|
|
||||||
|
if ctl != nil {
|
||||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
svr.ctlMu.RLock()
|
||||||
if svr.ctl != nil {
|
if svr.ctl != nil {
|
||||||
svr.ctl.Close()
|
svr.ctl.GracefulClose(d)
|
||||||
}
|
}
|
||||||
|
svr.ctlMu.RUnlock()
|
||||||
|
|
||||||
svr.cancel()
|
svr.cancel()
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ type STCPVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *STCPVisitor) Run() (err error) {
|
func (sv *STCPVisitor) Run() (err error) {
|
||||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ type XTCPVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XTCPVisitor) Run() (err error) {
|
func (sv *XTCPVisitor) Run() (err error) {
|
||||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -308,7 +308,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)
|
||||||
@ -353,7 +353,7 @@ type SUDPVisitor struct {
|
|||||||
func (sv *SUDPVisitor) Run() (err error) {
|
func (sv *SUDPVisitor) Run() (err error) {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||||
}
|
}
|
||||||
@ -377,29 +377,33 @@ func (sv *SUDPVisitor) Run() (err error) {
|
|||||||
func (sv *SUDPVisitor) dispatcher() {
|
func (sv *SUDPVisitor) dispatcher() {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
visitorConn net.Conn
|
||||||
|
err error
|
||||||
|
|
||||||
|
firstPacket *msg.UDPPacket
|
||||||
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// loop for get frpc to frps tcp conn
|
|
||||||
// setup worker
|
|
||||||
// wait worker to finished
|
|
||||||
// retry or exit
|
|
||||||
visitorConn, err := sv.getNewVisitorConn()
|
|
||||||
if err != nil {
|
|
||||||
// check if proxy is closed
|
|
||||||
// if checkCloseCh is close, we will return, other case we will continue to reconnect
|
|
||||||
select {
|
select {
|
||||||
|
case firstPacket = <-sv.sendCh:
|
||||||
|
if firstPacket == nil {
|
||||||
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
case <-sv.checkCloseCh:
|
case <-sv.checkCloseCh:
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
return
|
return
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
visitorConn, err = sv.getNewVisitorConn()
|
||||||
|
if err != nil {
|
||||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sv.worker(visitorConn)
|
// visitorConn always be closed when worker done.
|
||||||
|
sv.worker(visitorConn, firstPacket)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-sv.checkCloseCh:
|
case <-sv.checkCloseCh:
|
||||||
@ -407,9 +411,10 @@ func (sv *SUDPVisitor) dispatcher() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
xl.Debug("starting sudp proxy worker")
|
xl.Debug("starting sudp proxy worker")
|
||||||
|
|
||||||
@ -463,6 +468,14 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var errRet error
|
var errRet error
|
||||||
|
if firstPacket != nil {
|
||||||
|
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
||||||
|
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case udpMsg, ok := <-sv.sendCh:
|
case udpMsg, ok := <-sv.sendCh:
|
||||||
|
@ -15,18 +15,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
_ "github.com/fatedier/frp/assets/frpc"
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
|
||||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
sub.Execute()
|
sub.Execute()
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
@ -76,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
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,14 @@
|
|||||||
package sub
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
|
cfgDir string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
|
||||||
serverAddr string
|
serverAddr string
|
||||||
@ -72,15 +74,12 @@ var (
|
|||||||
bindPort int
|
bindPort int
|
||||||
|
|
||||||
tlsEnable bool
|
tlsEnable bool
|
||||||
|
|
||||||
kcpDoneCh chan struct{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
kcpDoneCh = make(chan struct{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
func RegisterCommonFlags(cmd *cobra.Command) {
|
||||||
@ -104,6 +103,32 @@ var rootCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||||
|
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||||
|
if cfgDir != "" {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
err := runClient(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frpc service error for config file [%s]\n", path)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Do not show command usage here.
|
// Do not show command usage here.
|
||||||
err := runClient(cfgFile)
|
err := runClient(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -120,13 +145,12 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSignal(svr *client.Service) {
|
func handleSignal(svr *client.Service, doneCh chan struct{}) {
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal, 1)
|
||||||
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(doneCh)
|
||||||
close(kcpDoneCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||||
@ -183,18 +207,9 @@ func startService(
|
|||||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||||
|
|
||||||
if cfg.DNSServer != "" {
|
if cfgFile != "" {
|
||||||
s := cfg.DNSServer
|
log.Trace("start frpc service for config file [%s]", cfgFile)
|
||||||
if !strings.Contains(s, ":") {
|
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
|
||||||
s += ":53"
|
|
||||||
}
|
|
||||||
// Change default dns server for frpc
|
|
||||||
net.DefaultResolver = &net.Resolver{
|
|
||||||
PreferGo: true,
|
|
||||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
return net.Dial("udp", s)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
@ -202,9 +217,10 @@ func startService(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kcpDoneCh := make(chan struct{})
|
||||||
// Capture the exit signal if we use kcp.
|
// Capture the exit signal if we use kcp.
|
||||||
if cfg.Protocol == "kcp" {
|
if cfg.Protocol == "kcp" {
|
||||||
go handleSignal(svr)
|
go handleSignal(svr, kcpDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svr.Run()
|
err = svr.Run()
|
||||||
|
@ -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"
|
||||||
@ -77,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
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,6 +6,13 @@
|
|||||||
server_addr = 0.0.0.0
|
server_addr = 0.0.0.0
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
|
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||||
|
# dial_server_timeout = 10
|
||||||
|
|
||||||
|
# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
# If negative, keep-alive probes are disabled.
|
||||||
|
# dial_server_keepalive = 7200
|
||||||
|
|
||||||
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
|
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
|
||||||
# it only works when protocol is tcp
|
# it only works when protocol is tcp
|
||||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||||
@ -48,6 +55,12 @@ oidc_audience =
|
|||||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
oidc_token_endpoint_url =
|
oidc_token_endpoint_url =
|
||||||
|
|
||||||
|
# oidc_additional_xxx specifies additional parameters to be sent to the OIDC Token Endpoint.
|
||||||
|
# For example, if you want to specify the "audience" parameter, you can set as follow.
|
||||||
|
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
|
||||||
|
# oidc_additional_audience = https://dev.auth.com/api/v2/
|
||||||
|
# oidc_additional_var1 = foobar
|
||||||
|
|
||||||
# set admin address for control frpc's action by http api such as reload
|
# set admin address for control frpc's action by http api such as reload
|
||||||
admin_addr = 127.0.0.1
|
admin_addr = 127.0.0.1
|
||||||
admin_port = 7400
|
admin_port = 7400
|
||||||
@ -60,7 +73,11 @@ admin_pwd = admin
|
|||||||
pool_count = 5
|
pool_count = 5
|
||||||
|
|
||||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||||
tcp_mux = true
|
# tcp_mux = true
|
||||||
|
|
||||||
|
# specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcp_mux is true.
|
||||||
|
# tcp_mux_keepalive_interval = 60
|
||||||
|
|
||||||
# your proxy name will be changed to {user}.{proxy}
|
# your proxy name will be changed to {user}.{proxy}
|
||||||
user = your_name
|
user = your_name
|
||||||
@ -73,6 +90,10 @@ login_fail_exit = true
|
|||||||
# now it supports tcp, kcp and websocket, default is tcp
|
# now it supports tcp, kcp and websocket, default is tcp
|
||||||
protocol = tcp
|
protocol = tcp
|
||||||
|
|
||||||
|
# set client binding ip when connect server, default is empty.
|
||||||
|
# only when protocol = tcp or websocket, the value will be used.
|
||||||
|
connect_server_local_ip = 0.0.0.0
|
||||||
|
|
||||||
# if tls_enable is true, frpc will connect frps by tls
|
# if tls_enable is true, frpc will connect frps by tls
|
||||||
tls_enable = true
|
tls_enable = true
|
||||||
|
|
||||||
@ -84,12 +105,13 @@ tls_enable = true
|
|||||||
# specify a dns server, so frpc will use this instead of default one
|
# specify a dns server, so frpc will use this instead of default one
|
||||||
# dns_server = 8.8.8.8
|
# dns_server = 8.8.8.8
|
||||||
|
|
||||||
# proxy names you want to start seperated by ','
|
# proxy names you want to start separated by ','
|
||||||
# default is empty, means all proxies
|
# default is empty, means all proxies
|
||||||
# start = ssh,dns
|
# start = ssh,dns
|
||||||
|
|
||||||
# heartbeat configure, it's not recommended to modify the default value
|
# heartbeat configure, it's not recommended to modify the default value
|
||||||
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 90
|
# The default value of heartbeat_interval is 10 and heartbeat_timeout is 90. Set negative value
|
||||||
|
# to disable it.
|
||||||
# heartbeat_interval = 30
|
# heartbeat_interval = 30
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
|
|
||||||
@ -105,6 +127,14 @@ udp_packet_size = 1500
|
|||||||
# include other config files for proxies.
|
# include other config files for proxies.
|
||||||
# includes = ./confd/*.ini
|
# 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
|
||||||
|
|
||||||
|
# Enable golang pprof handlers in admin listener.
|
||||||
|
# Admin port must be set first.
|
||||||
|
pprof_enable = 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]
|
||||||
@ -181,9 +211,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
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -86,13 +86,12 @@ oidc_audience =
|
|||||||
# By default, this value is false.
|
# By default, this value is false.
|
||||||
oidc_skip_expiry_check = false
|
oidc_skip_expiry_check = false
|
||||||
|
|
||||||
|
|
||||||
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||||
# By default, this value is false.
|
# By default, this value is false.
|
||||||
oidc_skip_issuer_check = false
|
oidc_skip_issuer_check = false
|
||||||
|
|
||||||
# heartbeat configure, it's not recommended to modify the default value
|
# heartbeat configure, it's not recommended to modify the default value
|
||||||
# the default value of heartbeat_timeout is 90
|
# the default value of heartbeat_timeout is 90. Set negative value to disable it.
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
|
|
||||||
# user_conn_timeout configure, it's not recommended to modify the default value
|
# user_conn_timeout configure, it's not recommended to modify the default value
|
||||||
@ -120,7 +119,15 @@ tls_only = false
|
|||||||
subdomain_host = frps.com
|
subdomain_host = frps.com
|
||||||
|
|
||||||
# if tcp stream multiplexing is used, default is true
|
# if tcp stream multiplexing is used, default is true
|
||||||
tcp_mux = true
|
# tcp_mux = true
|
||||||
|
|
||||||
|
# specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcp_mux is true.
|
||||||
|
# tcp_mux_keepalive_interval = 60
|
||||||
|
|
||||||
|
# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
# If negative, keep-alive probes are disabled.
|
||||||
|
# tcp_keepalive = 7200
|
||||||
|
|
||||||
# custom 404 page for HTTP requests
|
# custom 404 page for HTTP requests
|
||||||
# custom_404_page = /path/to/404.html
|
# custom_404_page = /path/to/404.html
|
||||||
@ -130,6 +137,10 @@ tcp_mux = true
|
|||||||
# It affects the udp and sudp proxy.
|
# It affects the udp and sudp proxy.
|
||||||
udp_packet_size = 1500
|
udp_packet_size = 1500
|
||||||
|
|
||||||
|
# Enable golang pprof handlers in dashboard listener.
|
||||||
|
# Dashboard port must be set first
|
||||||
|
pprof_enable = false
|
||||||
|
|
||||||
[plugin.user-manager]
|
[plugin.user-manager]
|
||||||
addr = 127.0.0.1:9000
|
addr = 127.0.0.1:9000
|
||||||
path = /handler
|
path = /handler
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
BIN
doc/pic/sponsor_doppler.png
Normal file
BIN
doc/pic/sponsor_doppler.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_workos.png
Normal file
BIN
doc/pic/sponsor_workos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
@ -70,7 +70,7 @@ The response can look like any of the following:
|
|||||||
|
|
||||||
### Operation
|
### Operation
|
||||||
|
|
||||||
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
Currently `Login`, `NewProxy`, `CloseProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||||
|
|
||||||
#### Login
|
#### Login
|
||||||
|
|
||||||
@ -88,7 +88,8 @@ Client login operation
|
|||||||
"privilege_key": <string>,
|
"privilege_key": <string>,
|
||||||
"run_id": <string>,
|
"run_id": <string>,
|
||||||
"pool_count": <int>,
|
"pool_count": <int>,
|
||||||
"metas": map<string>string
|
"metas": map<string>string,
|
||||||
|
"client_address": <string>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -135,6 +136,26 @@ Create new proxy
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### CloseProxy
|
||||||
|
|
||||||
|
A previously created proxy is closed.
|
||||||
|
|
||||||
|
Please note that one request will be sent for every proxy that is closed, do **NOT** use this
|
||||||
|
if you have too many proxies bound to a single client, as this may exhaust the server's resources.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
"run_id": <string>
|
||||||
|
},
|
||||||
|
"proxy_name": <string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Ping
|
#### Ping
|
||||||
|
|
||||||
Heartbeat from frpc
|
Heartbeat from frpc
|
||||||
|
@ -1,228 +0,0 @@
|
|||||||
### 服务端管理插件
|
|
||||||
|
|
||||||
frp 管理插件的作用是在不侵入自身代码的前提下,扩展 frp 服务端的能力。
|
|
||||||
|
|
||||||
frp 管理插件会以单独进程的形式运行,并且监听在一个端口上,对外提供 RPC 接口,响应 frps 的请求。
|
|
||||||
|
|
||||||
frps 在执行某些操作前,会根据配置向管理插件发送 RPC 请求,根据管理插件的响应来执行相应的操作。
|
|
||||||
|
|
||||||
### RPC 请求
|
|
||||||
|
|
||||||
管理插件接收到操作请求后,可以给出三种回应。
|
|
||||||
|
|
||||||
* 拒绝操作,需要返回拒绝操作的原因。
|
|
||||||
* 允许操作,不需要修改操作内容。
|
|
||||||
* 允许操作,对操作请求进行修改后,返回修改后的内容。
|
|
||||||
|
|
||||||
### 接口
|
|
||||||
|
|
||||||
接口路径可以在 frps 配置中为每个插件单独配置,这里以 `/handler` 为例。
|
|
||||||
|
|
||||||
Request
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /handler
|
|
||||||
{
|
|
||||||
"version": "0.1.0",
|
|
||||||
"op": "Login",
|
|
||||||
"content": {
|
|
||||||
... // 具体的操作信息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
请求 Header
|
|
||||||
X-Frp-Reqid: 用于追踪请求
|
|
||||||
```
|
|
||||||
|
|
||||||
Response
|
|
||||||
|
|
||||||
非 200 的返回都认为是请求异常。
|
|
||||||
|
|
||||||
拒绝执行操作
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"reject": true,
|
|
||||||
"reject_reason": "invalid user"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
允许且内容不需要变动
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"reject": false,
|
|
||||||
"unchange": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
允许且需要替换操作内容
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"unchange": "false",
|
|
||||||
"content": {
|
|
||||||
... // 替换后的操作信息,格式必须和请求时的一致
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作类型
|
|
||||||
|
|
||||||
目前插件支持管理的操作类型有 `Login`、`NewProxy`、`Ping`、`NewWorkConn` 和 `NewUserConn`。
|
|
||||||
|
|
||||||
#### Login
|
|
||||||
|
|
||||||
用户登录操作信息
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"version": <string>,
|
|
||||||
"hostname": <string>,
|
|
||||||
"os": <string>,
|
|
||||||
"arch": <string>,
|
|
||||||
"user": <string>,
|
|
||||||
"timestamp": <int64>,
|
|
||||||
"privilege_key": <string>,
|
|
||||||
"run_id": <string>,
|
|
||||||
"pool_count": <int>,
|
|
||||||
"metas": map<string>string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### NewProxy
|
|
||||||
|
|
||||||
创建代理的相关信息
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"user": {
|
|
||||||
"user": <string>,
|
|
||||||
"metas": map<string>string
|
|
||||||
},
|
|
||||||
"proxy_name": <string>,
|
|
||||||
"proxy_type": <string>,
|
|
||||||
"use_encryption": <bool>,
|
|
||||||
"use_compression": <bool>,
|
|
||||||
"group": <string>,
|
|
||||||
"group_key": <string>,
|
|
||||||
|
|
||||||
// tcp and udp only
|
|
||||||
"remote_port": <int>,
|
|
||||||
|
|
||||||
// http and https only
|
|
||||||
"custom_domains": []<string>,
|
|
||||||
"subdomain": <string>,
|
|
||||||
"locations": <string>,
|
|
||||||
"http_user": <string>,
|
|
||||||
"http_pwd": <string>,
|
|
||||||
"host_header_rewrite": <string>,
|
|
||||||
"headers": map<string>string,
|
|
||||||
|
|
||||||
"metas": map<string>string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Ping
|
|
||||||
|
|
||||||
心跳相关信息
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"user": {
|
|
||||||
"user": <string>,
|
|
||||||
"metas": map<string>string
|
|
||||||
"run_id": <string>
|
|
||||||
},
|
|
||||||
"timestamp": <int64>,
|
|
||||||
"privilege_key": <string>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### NewWorkConn
|
|
||||||
|
|
||||||
新增 `frpc` 连接相关信息
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"user": {
|
|
||||||
"user": <string>,
|
|
||||||
"metas": map<string>string
|
|
||||||
"run_id": <string>
|
|
||||||
},
|
|
||||||
"run_id": <string>
|
|
||||||
"timestamp": <int64>,
|
|
||||||
"privilege_key": <string>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### NewUserConn
|
|
||||||
|
|
||||||
新增 `proxy` 连接相关信息 (支持 `tcp`、`stcp`、`https` 和 `tcpmux` 协议)。
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"user": {
|
|
||||||
"user": <string>,
|
|
||||||
"metas": map<string>string
|
|
||||||
"run_id": <string>
|
|
||||||
},
|
|
||||||
"proxy_name": <string>,
|
|
||||||
"proxy_type": <string>,
|
|
||||||
"remote_addr": <string>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### frps 中插件配置
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[common]
|
|
||||||
bind_port = 7000
|
|
||||||
|
|
||||||
[plugin.user-manager]
|
|
||||||
addr = 127.0.0.1:9000
|
|
||||||
path = /handler
|
|
||||||
ops = Login
|
|
||||||
|
|
||||||
[plugin.port-manager]
|
|
||||||
addr = 127.0.0.1:9001
|
|
||||||
path = /handler
|
|
||||||
ops = NewProxy
|
|
||||||
```
|
|
||||||
|
|
||||||
addr: 插件监听的网络地址。
|
|
||||||
path: 插件监听的 HTTP 请求路径。
|
|
||||||
ops: 插件需要处理的操作列表,多个 op 以英文逗号分隔。
|
|
||||||
|
|
||||||
### 元数据
|
|
||||||
|
|
||||||
为了减少 frps 的代码修改,同时提高管理插件的扩展能力,在 frpc 的配置文件中引入自定义元数据的概念。元数据会在调用 RPC 请求时发送给插件。
|
|
||||||
|
|
||||||
元数据以 `meta_` 开头,可以配置多个,元数据分为两种,一种配置在 `common` 下,一种配置在各个 proxy 中。
|
|
||||||
|
|
||||||
```
|
|
||||||
# frpc.ini
|
|
||||||
[common]
|
|
||||||
server_addr = 127.0.0.1
|
|
||||||
server_port = 7000
|
|
||||||
user = fake
|
|
||||||
meta_token = fake
|
|
||||||
meta_version = 1.0.0
|
|
||||||
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
local_port = 22
|
|
||||||
remote_port = 6000
|
|
||||||
meta_id = 123
|
|
||||||
```
|
|
@ -1,11 +1,11 @@
|
|||||||
FROM alpine:3.12.0 AS temp
|
FROM alpine:3 AS temp
|
||||||
|
|
||||||
COPY bin/frpc /tmp
|
COPY bin/frpc /tmp
|
||||||
|
|
||||||
RUN chmod -R 777 /tmp/frpc
|
RUN chmod -R 777 /tmp/frpc
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.12.0
|
FROM alpine:3
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
FROM alpine:3.12.0 AS temp
|
FROM alpine:3 AS temp
|
||||||
|
|
||||||
COPY bin/frps /tmp
|
COPY bin/frps /tmp
|
||||||
|
|
||||||
RUN chmod -R 777 /tmp/frps
|
RUN chmod -R 777 /tmp/frps
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.12.0
|
FROM alpine:3
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
10
go.mod
10
go.mod
@ -6,22 +6,19 @@ require (
|
|||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
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.20220321042308-c306138b83ac
|
||||||
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/go-playground/validator/v10 v10.6.1
|
github.com/go-playground/validator/v10 v10.6.1
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864
|
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c
|
||||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
|
||||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.4
|
github.com/onsi/ginkgo v1.16.4
|
||||||
github.com/onsi/gomega v1.13.0
|
github.com/onsi/gomega v1.13.0
|
||||||
github.com/pires/go-proxyproto v0.5.0
|
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.11.0
|
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.1
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
@ -33,8 +30,9 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
|
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-20210616094352-59db8d763f22 // 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.21.2
|
k8s.io/apimachinery v0.21.2
|
||||||
|
k8s.io/client-go v0.21.2
|
||||||
)
|
)
|
||||||
|
161
go.sum
161
go.sum
@ -5,12 +5,30 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
|||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
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.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.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/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.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.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=
|
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/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
@ -41,7 +59,11 @@ 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 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
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/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=
|
||||||
@ -59,21 +81,26 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
|
|||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
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/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.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
|
||||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185/go.mod h1:MUs+IH/MGJNz5Cj2JVJBPZBKw2exON7LzO3HrJHmGiQ=
|
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
|
||||||
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/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 v1.0.0/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 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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
@ -104,14 +131,19 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
|||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
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-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/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.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.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.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.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=
|
||||||
@ -140,6 +172,9 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
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-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-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/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/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@ -154,6 +189,7 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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-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/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/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
@ -177,9 +213,11 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
|
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c h1:nqkErwUGfpZZMqj29WZ9U/wz2OpJVDuiokLhE/3Y7IQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
@ -188,6 +226,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
|||||||
github.com/json-iterator/go v1.1.10/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.11/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.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=
|
||||||
@ -195,10 +234,10 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
|||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
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/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/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
|
||||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
|
||||||
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/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=
|
||||||
@ -260,6 +299,7 @@ github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
|||||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
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/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/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 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
|
||||||
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
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=
|
||||||
@ -295,8 +335,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
|
||||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
|
||||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
||||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
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/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
@ -315,6 +353,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
|
|||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
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.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/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 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||||
@ -335,39 +374,48 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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/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-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/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-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
||||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
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/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-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/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.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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
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/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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
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-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-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/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-20201012173705-84dcc777aaee/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-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-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-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-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-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-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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -377,10 +425,15 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
|||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/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-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-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-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/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.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.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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.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=
|
||||||
@ -392,7 +445,6 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-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-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-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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@ -400,11 +452,18 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
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-20190620200207-3b0461eec859/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-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-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-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-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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
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-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 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||||
@ -412,6 +471,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
|
|||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-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-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=
|
||||||
@ -438,10 +498,21 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/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-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-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-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-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-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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -455,7 +526,10 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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=
|
||||||
@ -465,8 +539,9 @@ 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/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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
|
||||||
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-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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -486,7 +561,21 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/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-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-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-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-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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
@ -501,11 +590,17 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
|||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
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.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
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.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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
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-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-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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
@ -515,12 +610,25 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/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-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-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/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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
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.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
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.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
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=
|
||||||
@ -566,13 +674,20 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
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 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
|
||||||
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
|
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/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.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||||
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
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/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/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.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/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=
|
||||||
|
@ -5,7 +5,7 @@ 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 get -u github.com/onsi/ginkgo/ginkgo
|
go install github.com/onsi/ginkgo/ginkgo@latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug=false
|
debug=false
|
||||||
@ -17,4 +17,4 @@ if [ x${LOG_LEVEL} != x"" ]; then
|
|||||||
logLevel=${LOG_LEVEL}
|
logLevel=${LOG_LEVEL}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ginkgo -nodes=5 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
||||||
|
@ -40,6 +40,11 @@ type OidcClientConfig struct {
|
|||||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||||
// By default, this value is "".
|
// By default, this value is "".
|
||||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||||
|
|
||||||
|
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
||||||
|
// this field will be transfer to map[string][]string in OIDC token generator
|
||||||
|
// The field will be set by prefix "oidc_additional_"
|
||||||
|
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultOidcClientConf() OidcClientConfig {
|
func getDefaultOidcClientConf() OidcClientConfig {
|
||||||
@ -48,6 +53,7 @@ func getDefaultOidcClientConf() OidcClientConfig {
|
|||||||
OidcClientSecret: "",
|
OidcClientSecret: "",
|
||||||
OidcAudience: "",
|
OidcAudience: "",
|
||||||
OidcTokenEndpointURL: "",
|
OidcTokenEndpointURL: "",
|
||||||
|
OidcAdditionalEndpointParams: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,11 +94,17 @@ type OidcAuthProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
||||||
|
eps := make(map[string][]string)
|
||||||
|
for k, v := range cfg.OidcAdditionalEndpointParams {
|
||||||
|
eps[k] = []string{v}
|
||||||
|
}
|
||||||
|
|
||||||
tokenGenerator := &clientcredentials.Config{
|
tokenGenerator := &clientcredentials.Config{
|
||||||
ClientID: cfg.OidcClientID,
|
ClientID: cfg.OidcClientID,
|
||||||
ClientSecret: cfg.OidcClientSecret,
|
ClientSecret: cfg.OidcClientSecret,
|
||||||
Scopes: []string{cfg.OidcAudience},
|
Scopes: []string{cfg.OidcAudience},
|
||||||
TokenURL: cfg.OidcTokenEndpointURL,
|
TokenURL: cfg.OidcTokenEndpointURL,
|
||||||
|
EndpointParams: eps,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &OidcAuthProvider{
|
return &OidcAuthProvider{
|
||||||
|
@ -38,6 +38,15 @@ type ClientCommonConf struct {
|
|||||||
// 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"`
|
||||||
|
// The maximum amount of time a dial to server will wait for a connect to complete.
|
||||||
|
DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"`
|
||||||
|
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
// If negative, keep-alive probes are disabled.
|
||||||
|
DialServerKeepAlive int64 `ini:"dial_server_keepalive" json:"dial_server_keepalive"`
|
||||||
|
// ConnectServerLocalIP specifies the address of the client bind when it connect to server.
|
||||||
|
// By default, this value is empty.
|
||||||
|
// this value only use in TCP/Websocket protocol. Not support in KCP protocol.
|
||||||
|
ConnectServerLocalIP string `ini:"connect_server_local_ip" json:"connect_server_local_ip"`
|
||||||
// HTTPProxy specifies a proxy address to connect to the server through. If
|
// HTTPProxy specifies a proxy address to connect to the server through. If
|
||||||
// this value is "", the server will be connected to directly. By default,
|
// this value is "", the server will be connected to directly. By default,
|
||||||
// this value is read from the "http_proxy" environment variable.
|
// this value is read from the "http_proxy" environment variable.
|
||||||
@ -86,6 +95,9 @@ type ClientCommonConf struct {
|
|||||||
// the server must have TCP multiplexing enabled as well. By default, this
|
// the server must have TCP multiplexing enabled as well. By default, this
|
||||||
// value is true.
|
// value is true.
|
||||||
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
||||||
|
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||||
|
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||||
|
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||||
// User specifies a prefix for proxy names to distinguish them from other
|
// User specifies a prefix for proxy names to distinguish them from other
|
||||||
// clients. If this value is not "", proxy names will automatically be
|
// clients. If this value is not "", proxy names will automatically be
|
||||||
// changed to "{user}.{proxy_name}". By default, this value is "".
|
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||||
@ -121,16 +133,19 @@ type ClientCommonConf struct {
|
|||||||
// It only works when "tls_enable" is valid and tls configuration of server
|
// It only works when "tls_enable" is valid and tls configuration of server
|
||||||
// has been specified.
|
// has been specified.
|
||||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||||
// TLSServerName specifices the custom server name of tls certificate. By
|
// TLSServerName specifies 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. Set negative value to disable it.
|
||||||
HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"`
|
HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"`
|
||||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||||
// before the connection is terminated, in seconds. It is not recommended
|
// before the connection is terminated, in seconds. It is not recommended
|
||||||
// to change this value. By default, this value is 90.
|
// to change this value. By default, this value is 90. Set negative value to disable it.
|
||||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||||
// Client meta info
|
// Client meta info
|
||||||
Metas map[string]string `ini:"-" json:"metas"`
|
Metas map[string]string `ini:"-" json:"metas"`
|
||||||
@ -139,6 +154,9 @@ type ClientCommonConf struct {
|
|||||||
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.
|
// Include other config files for proxies.
|
||||||
IncludeConfigFiles []string `ini:"includes" json:"includes"`
|
IncludeConfigFiles []string `ini:"includes" json:"includes"`
|
||||||
|
// Enable golang pprof handlers in admin listener.
|
||||||
|
// Admin port must be set first.
|
||||||
|
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultClientConf returns a client configuration with default values.
|
// GetDefaultClientConf returns a client configuration with default values.
|
||||||
@ -147,6 +165,8 @@ func GetDefaultClientConf() ClientCommonConf {
|
|||||||
ClientConfig: auth.GetDefaultClientConf(),
|
ClientConfig: auth.GetDefaultClientConf(),
|
||||||
ServerAddr: "0.0.0.0",
|
ServerAddr: "0.0.0.0",
|
||||||
ServerPort: 7000,
|
ServerPort: 7000,
|
||||||
|
DialServerTimeout: 10,
|
||||||
|
DialServerKeepAlive: 7200,
|
||||||
HTTPProxy: os.Getenv("http_proxy"),
|
HTTPProxy: os.Getenv("http_proxy"),
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
@ -160,6 +180,7 @@ func GetDefaultClientConf() ClientCommonConf {
|
|||||||
AssetsDir: "",
|
AssetsDir: "",
|
||||||
PoolCount: 1,
|
PoolCount: 1,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
User: "",
|
User: "",
|
||||||
DNSServer: "",
|
DNSServer: "",
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
@ -174,6 +195,7 @@ func GetDefaultClientConf() ClientCommonConf {
|
|||||||
Metas: make(map[string]string),
|
Metas: make(map[string]string),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
IncludeConfigFiles: make([]string, 0),
|
IncludeConfigFiles: make([]string, 0),
|
||||||
|
PprofEnable: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,13 +208,11 @@ func (cfg *ClientCommonConf) Complete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *ClientCommonConf) Validate() error {
|
func (cfg *ClientCommonConf) Validate() error {
|
||||||
if cfg.HeartbeatInterval <= 0 {
|
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||||
return fmt.Errorf("invalid heartbeat_interval")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||||
return fmt.Errorf("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 {
|
||||||
if cfg.TLSCertFile != "" {
|
if cfg.TLSCertFile != "" {
|
||||||
@ -249,6 +269,8 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||||
|
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||||
|
|
||||||
return common, nil
|
return common, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,6 +261,8 @@ func Test_LoadClientCommonConf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ServerAddr: "0.0.0.9",
|
ServerAddr: "0.0.0.9",
|
||||||
ServerPort: 7009,
|
ServerPort: 7009,
|
||||||
|
DialServerTimeout: 10,
|
||||||
|
DialServerKeepAlive: 7200,
|
||||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||||
LogFile: "./frpc.log9",
|
LogFile: "./frpc.log9",
|
||||||
LogWay: "file",
|
LogWay: "file",
|
||||||
@ -274,6 +276,7 @@ func Test_LoadClientCommonConf(t *testing.T) {
|
|||||||
AssetsDir: "./static9",
|
AssetsDir: "./static9",
|
||||||
PoolCount: 59,
|
PoolCount: 59,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
User: "your_name",
|
User: "your_name",
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
@ -17,7 +17,6 @@ package config
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
@ -77,7 +76,7 @@ func getIncludeContents(paths []string) ([]byte, error) {
|
|||||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
files, err := ioutil.ReadDir(absDir)
|
files, err := os.ReadDir(absDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,12 @@ type ServerCommonConf struct {
|
|||||||
// from a client to share a single TCP connection. By default, this value
|
// from a client to share a single TCP connection. By default, this value
|
||||||
// is true.
|
// is true.
|
||||||
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
||||||
|
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||||
|
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||||
|
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||||
|
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
// If negative, keep-alive probes are disabled.
|
||||||
|
TCPKeepAlive int64 `ini:"tcp_keepalive" json:"tcp_keepalive"`
|
||||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||||
// value is "", a default page will be displayed. By default, this value is
|
// value is "", a default page will be displayed. By default, this value is
|
||||||
// "".
|
// "".
|
||||||
@ -154,7 +160,7 @@ type ServerCommonConf struct {
|
|||||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||||
// before terminating the connection. It is not recommended to change this
|
// before terminating the connection. It is not recommended to change this
|
||||||
// value. By default, this value is 90.
|
// value. By default, this value is 90. Set negative value to disable it.
|
||||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||||
// UserConnTimeout specifies the maximum time to wait for a work
|
// UserConnTimeout specifies the maximum time to wait for a work
|
||||||
// connection. By default, this value is 10.
|
// connection. By default, this value is 10.
|
||||||
@ -164,6 +170,9 @@ type ServerCommonConf 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"`
|
||||||
|
// Enable golang pprof handlers in dashboard listener.
|
||||||
|
// Dashboard port must be set first.
|
||||||
|
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultServerConf returns a server configuration with reasonable
|
// GetDefaultServerConf returns a server configuration with reasonable
|
||||||
@ -194,6 +203,8 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
SubDomainHost: "",
|
SubDomainHost: "",
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
|
TCPKeepAlive: 7200,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
@ -206,6 +217,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
Custom404Page: "",
|
Custom404Page: "",
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
|
PprofEnable: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +139,8 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
TLSTrustedCaFile: "ca.crt",
|
TLSTrustedCaFile: "ca.crt",
|
||||||
SubDomainHost: "frps.com",
|
SubDomainHost: "frps.com",
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
|
TCPKeepAlive: 7200,
|
||||||
UDPPacketSize: 1509,
|
UDPPacketSize: 1509,
|
||||||
|
|
||||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||||
@ -189,6 +191,8 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
|
TCPKeepAlive: 7200,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
HeartbeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
|
@ -16,7 +16,6 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@ -30,11 +29,11 @@ func init() {
|
|||||||
glbEnvs = make(map[string]string)
|
glbEnvs = make(map[string]string)
|
||||||
envs := os.Environ()
|
envs := os.Environ()
|
||||||
for _, env := range envs {
|
for _, env := range envs {
|
||||||
kv := strings.Split(env, "=")
|
pair := strings.SplitN(env, "=", 2)
|
||||||
if len(kv) != 2 {
|
if len(pair) != 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
glbEnvs[kv[0]] = kv[1]
|
glbEnvs[pair[0]] = pair[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
@ -26,6 +27,7 @@ import (
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
loginPlugins []Plugin
|
loginPlugins []Plugin
|
||||||
newProxyPlugins []Plugin
|
newProxyPlugins []Plugin
|
||||||
|
closeProxyPlugins []Plugin
|
||||||
pingPlugins []Plugin
|
pingPlugins []Plugin
|
||||||
newWorkConnPlugins []Plugin
|
newWorkConnPlugins []Plugin
|
||||||
newUserConnPlugins []Plugin
|
newUserConnPlugins []Plugin
|
||||||
@ -35,6 +37,7 @@ func NewManager() *Manager {
|
|||||||
return &Manager{
|
return &Manager{
|
||||||
loginPlugins: make([]Plugin, 0),
|
loginPlugins: make([]Plugin, 0),
|
||||||
newProxyPlugins: make([]Plugin, 0),
|
newProxyPlugins: make([]Plugin, 0),
|
||||||
|
closeProxyPlugins: make([]Plugin, 0),
|
||||||
pingPlugins: make([]Plugin, 0),
|
pingPlugins: make([]Plugin, 0),
|
||||||
newWorkConnPlugins: make([]Plugin, 0),
|
newWorkConnPlugins: make([]Plugin, 0),
|
||||||
newUserConnPlugins: make([]Plugin, 0),
|
newUserConnPlugins: make([]Plugin, 0),
|
||||||
@ -48,6 +51,9 @@ func (m *Manager) Register(p Plugin) {
|
|||||||
if p.IsSupport(OpNewProxy) {
|
if p.IsSupport(OpNewProxy) {
|
||||||
m.newProxyPlugins = append(m.newProxyPlugins, p)
|
m.newProxyPlugins = append(m.newProxyPlugins, p)
|
||||||
}
|
}
|
||||||
|
if p.IsSupport(OpCloseProxy) {
|
||||||
|
m.closeProxyPlugins = append(m.closeProxyPlugins, p)
|
||||||
|
}
|
||||||
if p.IsSupport(OpPing) {
|
if p.IsSupport(OpPing) {
|
||||||
m.pingPlugins = append(m.pingPlugins, p)
|
m.pingPlugins = append(m.pingPlugins, p)
|
||||||
}
|
}
|
||||||
@ -127,6 +133,32 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
|
|||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) CloseProxy(content *CloseProxyContent) error {
|
||||||
|
if len(m.closeProxyPlugins) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]string, 0)
|
||||||
|
reqid, _ := util.RandID()
|
||||||
|
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||||
|
ctx := xlog.NewContext(context.Background(), xl)
|
||||||
|
ctx = NewReqidContext(ctx, reqid)
|
||||||
|
|
||||||
|
for _, p := range m.closeProxyPlugins {
|
||||||
|
_, _, err := p.Handle(ctx, OpCloseProxy, *content)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("send CloseProxy request to plugin [%s] error: %v", p.Name(), err)
|
||||||
|
errs = append(errs, fmt.Sprintf("[%s]: %v", p.Name(), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("send CloseProxy request to plugin errors: %s", strings.Join(errs, "; "))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
|
func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
|
||||||
if len(m.pingPlugins) == 0 {
|
if len(m.pingPlugins) == 0 {
|
||||||
return content, nil
|
return content, nil
|
||||||
@ -179,7 +211,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)
|
||||||
|
@ -23,6 +23,7 @@ const (
|
|||||||
|
|
||||||
OpLogin = "Login"
|
OpLogin = "Login"
|
||||||
OpNewProxy = "NewProxy"
|
OpNewProxy = "NewProxy"
|
||||||
|
OpCloseProxy = "CloseProxy"
|
||||||
OpPing = "Ping"
|
OpPing = "Ping"
|
||||||
OpNewWorkConn = "NewWorkConn"
|
OpNewWorkConn = "NewWorkConn"
|
||||||
OpNewUserConn = "NewUserConn"
|
OpNewUserConn = "NewUserConn"
|
||||||
|
@ -33,6 +33,8 @@ type Response struct {
|
|||||||
|
|
||||||
type LoginContent struct {
|
type LoginContent struct {
|
||||||
msg.Login
|
msg.Login
|
||||||
|
|
||||||
|
ClientAddress string `json:"client_address,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
@ -46,6 +48,11 @@ type NewProxyContent struct {
|
|||||||
msg.NewProxy
|
msg.NewProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CloseProxyContent struct {
|
||||||
|
User UserInfo `json:"user"`
|
||||||
|
msg.CloseProxy
|
||||||
|
}
|
||||||
|
|
||||||
type PingContent struct {
|
type PingContent struct {
|
||||||
User UserInfo `json:"user"`
|
User UserInfo `json:"user"`
|
||||||
msg.Ping
|
msg.Ping
|
||||||
|
@ -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 == "" {
|
||||||
@ -100,6 +100,8 @@ func NewClientTLSConfig(certPath, keyPath, caPath, servearName string) (*tls.Con
|
|||||||
base.Certificates = []tls.Certificate{*cert}
|
base.Certificates = []tls.Certificate{*cert}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.ServerName = serverName
|
||||||
|
|
||||||
if caPath != "" {
|
if caPath != "" {
|
||||||
pool, err := newCertPool(caPath)
|
pool, err := newCertPool(caPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,7 +109,6 @@ func NewClientTLSConfig(certPath, keyPath, caPath, servearName string) (*tls.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
base.RootCAs = pool
|
base.RootCAs = pool
|
||||||
base.ServerName = servearName
|
|
||||||
base.InsecureSkipVerify = false
|
base.InsecureSkipVerify = false
|
||||||
} else {
|
} else {
|
||||||
base.InsecureSkipVerify = true
|
base.InsecureSkipVerify = true
|
||||||
|
@ -16,18 +16,13 @@ package net
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
|
||||||
kcp "github.com/fatedier/kcp-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContextGetter interface {
|
type ContextGetter interface {
|
||||||
@ -188,56 +183,3 @@ func (statsConn *StatsConn) Close() (err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectServer(protocol string, addr string) (c net.Conn, err error) {
|
|
||||||
switch protocol {
|
|
||||||
case "tcp":
|
|
||||||
return net.Dial("tcp", addr)
|
|
||||||
case "kcp":
|
|
||||||
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
|
|
||||||
if errRet != nil {
|
|
||||||
err = errRet
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kcpConn.SetStreamMode(true)
|
|
||||||
kcpConn.SetWriteDelay(true)
|
|
||||||
kcpConn.SetNoDelay(1, 20, 2, 1)
|
|
||||||
kcpConn.SetWindowSize(128, 512)
|
|
||||||
kcpConn.SetMtu(1350)
|
|
||||||
kcpConn.SetACKNoDelay(false)
|
|
||||||
kcpConn.SetReadBuffer(4194304)
|
|
||||||
kcpConn.SetWriteBuffer(4194304)
|
|
||||||
c = kcpConn
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.Conn, err error) {
|
|
||||||
switch protocol {
|
|
||||||
case "tcp":
|
|
||||||
return gnet.DialTcpByProxy(proxyURL, addr)
|
|
||||||
case "kcp":
|
|
||||||
// http proxy is not supported for kcp
|
|
||||||
return ConnectServer(protocol, addr)
|
|
||||||
case "websocket":
|
|
||||||
return ConnectWebsocketServer(addr)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config) (c net.Conn, err error) {
|
|
||||||
c, err = ConnectServerByProxy(proxyURL, protocol, addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsConfig == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c = WrapTLSClientConn(c, tlsConfig)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
44
pkg/util/net/dial.go
Normal file
44
pkg/util/net/dial.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DialHookCustomTLSHeadByte(enableTLS bool, disableCustomTLSHeadByte bool) libdial.AfterHookFunc {
|
||||||
|
return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) {
|
||||||
|
if enableTLS && !disableCustomTLSHeadByte {
|
||||||
|
_, err := c.Write([]byte{byte(FRPTLSHeadByte)})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx, c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialHookWebsocket() libdial.AfterHookFunc {
|
||||||
|
return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) {
|
||||||
|
addr = "ws://" + addr + FrpWebsocketPath
|
||||||
|
uri, err := url.Parse(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin := "http://" + uri.Host
|
||||||
|
cfg, err := websocket.NewConfig(addr, origin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := websocket.NewClient(cfg, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return ctx, conn, nil
|
||||||
|
}
|
||||||
|
}
|
@ -27,13 +27,10 @@ var (
|
|||||||
FRPTLSHeadByte = 0x17
|
FRPTLSHeadByte = 0x17
|
||||||
)
|
)
|
||||||
|
|
||||||
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
|
func CheckAndEnableTLSServerConnWithTimeout(
|
||||||
c.Write([]byte{byte(FRPTLSHeadByte)})
|
c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
|
||||||
out = tls.Client(c, tlsConfig)
|
) (out net.Conn, isTLS bool, custom bool, err error) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, 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 +43,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")
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -163,7 +164,7 @@ type UDPListener struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
|
func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return l, err
|
return l, err
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,9 @@ package net
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
@ -54,7 +52,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
|
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
|
||||||
tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
tcpLn, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -77,27 +75,3 @@ func (p *WebsocketListener) Close() error {
|
|||||||
func (p *WebsocketListener) Addr() net.Addr {
|
func (p *WebsocketListener) Addr() net.Addr {
|
||||||
return p.ln.Addr()
|
return p.ln.Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// addr: domain:port
|
|
||||||
func ConnectWebsocketServer(addr string) (net.Conn, error) {
|
|
||||||
addr = "ws://" + addr + FrpWebsocketPath
|
|
||||||
uri, err := url.Parse(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin := "http://" + uri.Host
|
|
||||||
cfg, err := websocket.NewConfig(addr, origin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cfg.Dialer = &net.Dialer{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := websocket.DialConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
@ -48,7 +48,7 @@ func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host = util.GetHostFromAddr(req.Host)
|
host, _ = util.CanonicalHost(req.Host)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,17 +34,6 @@ func OkResponse() *http.Response {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
|
|
||||||
func GetHostFromAddr(addr string) (host string) {
|
|
||||||
strs := strings.Split(addr, ":")
|
|
||||||
if len(strs) > 1 {
|
|
||||||
host = strs[0]
|
|
||||||
} else {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// canonicalHost strips port from host if present and returns the canonicalized
|
// canonicalHost strips port from host if present and returns the canonicalized
|
||||||
// host name.
|
// host name.
|
||||||
func CanonicalHost(host string) (string, error) {
|
func CanonicalHost(host string) (string, error) {
|
||||||
|
@ -19,8 +19,11 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
mathrand "math/rand"
|
||||||
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RandID return a rand string used in frp.
|
// RandID return a rand string used in frp.
|
||||||
@ -52,7 +55,7 @@ func CanonicalAddr(host string, port int) (addr string) {
|
|||||||
if port == 80 || port == 443 {
|
if port == 80 || port == 443 {
|
||||||
addr = host
|
addr = host
|
||||||
} else {
|
} else {
|
||||||
addr = fmt.Sprintf("%s:%d", host, port)
|
addr = net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -108,3 +111,17 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
|
|||||||
}
|
}
|
||||||
return summary
|
return summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
|
||||||
|
min := int64(minRatio * 1000.0)
|
||||||
|
max := int64(maxRatio * 1000.0)
|
||||||
|
var n int64
|
||||||
|
if max <= min {
|
||||||
|
n = min
|
||||||
|
} else {
|
||||||
|
n = mathrand.Int63n(max-min) + min
|
||||||
|
}
|
||||||
|
d := duration * time.Duration(n) / time.Duration(1000)
|
||||||
|
time.Sleep(d)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.37.0"
|
var version string = "0.41.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -59,7 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
Director: func(req *http.Request) {
|
Director: func(req *http.Request) {
|
||||||
req.URL.Scheme = "http"
|
req.URL.Scheme = "http"
|
||||||
url := req.Context().Value(RouteInfoURL).(string)
|
url := req.Context().Value(RouteInfoURL).(string)
|
||||||
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
|
oldHost, _ := util.CanonicalHost(req.Context().Value(RouteInfoHost).(string))
|
||||||
rc := rp.GetRouteConfig(oldHost, url)
|
rc := rp.GetRouteConfig(oldHost, url)
|
||||||
if rc != nil {
|
if rc != nil {
|
||||||
if rc.RewriteHost != "" {
|
if rc.RewriteHost != "" {
|
||||||
@ -81,7 +81,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
IdleConnTimeout: 60 * time.Second,
|
IdleConnTimeout: 60 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
url := ctx.Value(RouteInfoURL).(string)
|
url := ctx.Value(RouteInfoURL).(string)
|
||||||
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string))
|
host, _ := util.CanonicalHost(ctx.Value(RouteInfoHost).(string))
|
||||||
remote := ctx.Value(RouteInfoRemote).(string)
|
remote := ctx.Value(RouteInfoRemote).(string)
|
||||||
return rp.CreateConnection(host, url, remote)
|
return rp.CreateConnection(host, url, remote)
|
||||||
},
|
},
|
||||||
@ -89,7 +89,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
BufferPool: newWrapPool(),
|
BufferPool: newWrapPool(),
|
||||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
frpLog.Warn("do http proxy request error: %v", err)
|
frpLog.Warn("do http proxy request [host: %s] error: %v", req.Host, err)
|
||||||
rw.WriteHeader(http.StatusNotFound)
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
rw.Write(getNotFoundPageContent())
|
rw.Write(getNotFoundPageContent())
|
||||||
},
|
},
|
||||||
@ -191,7 +191,7 @@ func (rp *HTTPReverseProxy) getVhost(domain string, location string) (vr *Router
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
domain := util.GetHostFromAddr(req.Host)
|
domain, _ := util.CanonicalHost(req.Host)
|
||||||
location := req.URL.Path
|
location := req.URL.Path
|
||||||
user, passwd, _ := req.BasicAuth()
|
user, passwd, _ := req.BasicAuth()
|
||||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||||
|
@ -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 }
|
||||||
|
38
pkg/util/vhost/https_test.go
Normal file
38
pkg/util/vhost/https_test.go
Normal 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"])
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
case workConn, ok = <-ctl.workConnCh:
|
case workConn, ok = <-ctl.workConnCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
err = frpErr.ErrCtlClosed
|
err = frpErr.ErrCtlClosed
|
||||||
xl.Warn("no work connections avaiable, %v", err)
|
xl.Warn("no work connections available, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,6 +376,20 @@ func (ctl *Control) stoper() {
|
|||||||
pxy.Close()
|
pxy.Close()
|
||||||
ctl.pxyManager.Del(pxy.GetName())
|
ctl.pxyManager.Del(pxy.GetName())
|
||||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
|
|
||||||
|
notifyContent := &plugin.CloseProxyContent{
|
||||||
|
User: plugin.UserInfo{
|
||||||
|
User: ctl.loginMsg.User,
|
||||||
|
Metas: ctl.loginMsg.Metas,
|
||||||
|
RunID: ctl.loginMsg.RunID,
|
||||||
|
},
|
||||||
|
CloseProxy: msg.CloseProxy{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
ctl.pluginManager.CloseProxy(notifyContent)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctl.allShutdown.Done()
|
ctl.allShutdown.Done()
|
||||||
@ -400,12 +414,19 @@ func (ctl *Control) manager() {
|
|||||||
defer ctl.allShutdown.Start()
|
defer ctl.allShutdown.Start()
|
||||||
defer ctl.managerShutdown.Done()
|
defer ctl.managerShutdown.Done()
|
||||||
|
|
||||||
|
var heartbeatCh <-chan time.Time
|
||||||
|
if ctl.serverCfg.TCPMux || ctl.serverCfg.HeartbeatTimeout <= 0 {
|
||||||
|
// Don't need application heartbeat here.
|
||||||
|
// yamux will do same thing.
|
||||||
|
} else {
|
||||||
heartbeat := time.NewTicker(time.Second)
|
heartbeat := time.NewTicker(time.Second)
|
||||||
defer heartbeat.Stop()
|
defer heartbeat.Stop()
|
||||||
|
heartbeatCh = heartbeat.C
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-heartbeat.C:
|
case <-heartbeatCh:
|
||||||
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
return
|
return
|
||||||
@ -557,5 +578,20 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
|||||||
ctl.mu.Unlock()
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
|
|
||||||
|
notifyContent := &plugin.CloseProxyContent{
|
||||||
|
User: plugin.UserInfo{
|
||||||
|
User: ctl.loginMsg.User,
|
||||||
|
Metas: ctl.loginMsg.Metas,
|
||||||
|
RunID: ctl.loginMsg.RunID,
|
||||||
|
},
|
||||||
|
CloseProxy: msg.CloseProxy{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
ctl.pluginManager.CloseProxy(notifyContent)
|
||||||
|
}()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
@ -27,33 +28,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpServerReadTimeout = 10 * time.Second
|
httpServerReadTimeout = 60 * time.Second
|
||||||
httpServerWriteTimeout = 10 * time.Second
|
httpServerWriteTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func (svr *Service) RunDashboardServer(address string) (err error) {
|
func (svr *Service) RunDashboardServer(address string) (err error) {
|
||||||
// url router
|
// url router
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/healthz", svr.Healthz)
|
||||||
|
|
||||||
|
// debug
|
||||||
|
if svr.cfg.PprofEnable {
|
||||||
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
|
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
|
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
|
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
subRouter := router.NewRoute().Subrouter()
|
||||||
|
|
||||||
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
||||||
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
// metrics
|
// metrics
|
||||||
if svr.cfg.EnablePrometheus {
|
if svr.cfg.EnablePrometheus {
|
||||||
router.Handle("/metrics", promhttp.Handler())
|
subRouter.Handle("/metrics", promhttp.Handler())
|
||||||
}
|
}
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
router.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET")
|
subRouter.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET")
|
||||||
router.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
|
subRouter.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
|
||||||
router.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
|
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
|
||||||
router.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
|
subRouter.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
|
||||||
|
|
||||||
// view
|
// view
|
||||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
|
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
package group
|
package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/server/ports"
|
"github.com/fatedier/frp/server/ports"
|
||||||
@ -101,7 +101,7 @@ func (tg *TCPGroup) Listen(proxyName string, group string, groupKey string, addr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
tcpLn, errRet := net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(port)))
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
|
@ -2,8 +2,8 @@ package ports
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -134,7 +134,7 @@ func (pm *Manager) Acquire(name string, port int) (realPort int, err error) {
|
|||||||
|
|
||||||
func (pm *Manager) isPortAvailable(port int) bool {
|
func (pm *Manager) isPortAvailable(port int) bool {
|
||||||
if pm.netType == "udp" {
|
if pm.netType == "udp" {
|
||||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ func (pm *Manager) isPortAvailable(port int) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
l, err := net.Listen(pm.netType, net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
@ -17,6 +17,7 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
)
|
)
|
||||||
@ -54,7 +55,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.rc.TCPPortManager.Release(pxy.realPort)
|
pxy.rc.TCPPortManager.Release(pxy.realPort)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
@ -70,7 +71,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
|||||||
|
|
||||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||||
pxy.cfg.RemotePort = pxy.realPort
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
addr, errRet := net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@ -124,7 +124,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
// Create tcpmux httpconnect multiplexer.
|
// Create tcpmux httpconnect multiplexer.
|
||||||
if cfg.TCPMuxHTTPConnectPort > 0 {
|
if cfg.TCPMuxHTTPConnectPort > 0 {
|
||||||
var l net.Listener
|
var l net.Listener
|
||||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort))
|
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.TCPMuxHTTPConnectPort))
|
||||||
|
l, err = net.Listen("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create server listener error, %v", err)
|
err = fmt.Errorf("Create server listener error, %v", err)
|
||||||
return
|
return
|
||||||
@ -135,7 +136,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort)
|
log.Info("tcpmux httpconnect multiplexer listen on %s", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init all plugins
|
// Init all plugins
|
||||||
@ -185,6 +186,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
svr.muxer = mux.NewMux(ln)
|
svr.muxer = mux.NewMux(ln)
|
||||||
|
svr.muxer.SetKeepAlive(time.Duration(cfg.TCPKeepAlive) * time.Second)
|
||||||
go svr.muxer.Serve()
|
go svr.muxer.Serve()
|
||||||
ln = svr.muxer.DefaultListener()
|
ln = svr.muxer.DefaultListener()
|
||||||
|
|
||||||
@ -199,7 +201,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err)
|
err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KCPBindPort)
|
log.Info("frps kcp listen on udp %s", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for accepting connections from client using websocket protocol.
|
// Listen for accepting connections from client using websocket protocol.
|
||||||
@ -232,7 +234,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
go server.Serve(l)
|
go server.Serve(l)
|
||||||
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort)
|
log.Info("http service listen on %s", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create https vhost muxer.
|
// Create https vhost muxer.
|
||||||
@ -258,8 +260,9 @@ 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.
|
||||||
@ -279,11 +282,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)
|
||||||
@ -291,7 +290,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
log.Info("Dashboard listen on %s", address)
|
||||||
statsEnable = true
|
statsEnable = true
|
||||||
}
|
}
|
||||||
if statsEnable {
|
if statsEnable {
|
||||||
@ -338,6 +337,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
|||||||
// server plugin hook
|
// server plugin hook
|
||||||
content := &plugin.LoginContent{
|
content := &plugin.LoginContent{
|
||||||
Login: *m,
|
Login: *m,
|
||||||
|
ClientAddress: conn.RemoteAddr().String(),
|
||||||
}
|
}
|
||||||
retContent, err := svr.pluginManager.Login(content)
|
retContent, err := svr.pluginManager.Login(content)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -395,20 +395,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 = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * 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)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"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/mock/server/streamserver"
|
||||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||||
@ -172,6 +175,106 @@ var _ = Describe("[Feature: Basic]", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/frp/test/e2e/framework"
|
"github.com/fatedier/frp/test/e2e/framework"
|
||||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||||
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
|
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
@ -75,4 +76,28 @@ var _ = Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure()
|
framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure()
|
||||||
framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure()
|
framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("healthz", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
|
||||||
|
dashboardPort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
admin_addr = 0.0.0.0
|
||||||
|
admin_port = %d
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
`, dashboardPort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPPath("/healthz")
|
||||||
|
}).Port(dashboardPort).ExpectResp([]byte("")).Ensure()
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPPath("/")
|
||||||
|
}).Port(dashboardPort).
|
||||||
|
Ensure(framework.ExpectResponseCode(401))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/frp/test/e2e/framework"
|
"github.com/fatedier/frp/test/e2e/framework"
|
||||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/cert"
|
||||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
@ -17,9 +18,7 @@ type generalTestConfigures struct {
|
|||||||
expectError bool
|
expectError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
|
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
||||||
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
|
|
||||||
It(desc, func() {
|
|
||||||
serverConf := consts.DefaultServerConfig
|
serverConf := consts.DefaultServerConfig
|
||||||
clientConf := consts.DefaultClientConfig
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
@ -51,6 +50,12 @@ func defineClientServerTest(desc string, f *framework.Framework, configures *gen
|
|||||||
framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
|
framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
|
||||||
framework.NewRequestExpect(f).Protocol("udp").
|
framework.NewRequestExpect(f).Protocol("udp").
|
||||||
PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
|
PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
|
||||||
|
}
|
||||||
|
|
||||||
|
// defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
|
||||||
|
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
|
||||||
|
It(desc, func() {
|
||||||
|
runClientServerTest(f, configures)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,4 +113,159 @@ 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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("IPv6 bind address", func() {
|
||||||
|
supportProtocols := []string{"tcp", "kcp", "websocket"}
|
||||||
|
for _, protocol := range supportProtocols {
|
||||||
|
tmp := protocol
|
||||||
|
defineClientServerTest("IPv6 bind address: "+strings.ToUpper(tmp), f, &generalTestConfigures{
|
||||||
|
server: fmt.Sprintf(`
|
||||||
|
bind_addr = ::
|
||||||
|
kcp_bind_port = {{ .%s }}
|
||||||
|
protocol = %s
|
||||||
|
`, consts.PortServerName, protocol),
|
||||||
|
client: fmt.Sprintf(`
|
||||||
|
tls_enable = true
|
||||||
|
protocol = %s
|
||||||
|
disable_custom_tls_first_byte = true
|
||||||
|
`, protocol),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -123,4 +123,57 @@ var _ = Describe("[Feature: Server Manager]", func() {
|
|||||||
|
|
||||||
framework.NewRequestExpect(f).Protocol("udp").Port(port).Ensure()
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("healthz", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
dashboardPort := f.AllocPort()
|
||||||
|
|
||||||
|
// Use same port as PortServer
|
||||||
|
serverConf += fmt.Sprintf(`
|
||||||
|
vhost_http_port = {{ .%s }}
|
||||||
|
dashboard_addr = 0.0.0.0
|
||||||
|
dashboard_port = %d
|
||||||
|
dashboard_user = admin
|
||||||
|
dashboard_pwd = admin
|
||||||
|
`, consts.PortServerName, dashboardPort)
|
||||||
|
|
||||||
|
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().HTTPPath("/healthz")
|
||||||
|
}).Port(dashboardPort).ExpectResp([]byte("")).Ensure()
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPPath("/")
|
||||||
|
}).Port(dashboardPort).
|
||||||
|
Ensure(framework.ExpectResponseCode(401))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
48
test/e2e/features/heartbeat.go
Normal file
48
test/e2e/features/heartbeat.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("[Feature: Heartbeat]", func() {
|
||||||
|
f := framework.NewDefaultFramework()
|
||||||
|
|
||||||
|
It("disable application layer heartbeat", func() {
|
||||||
|
serverPort := f.AllocPort()
|
||||||
|
serverConf := fmt.Sprintf(`
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = %d
|
||||||
|
heartbeat_timeout = -1
|
||||||
|
tcp_mux_keepalive_interval = 2
|
||||||
|
`, serverPort)
|
||||||
|
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf := fmt.Sprintf(`
|
||||||
|
[common]
|
||||||
|
server_port = %d
|
||||||
|
log_level = trace
|
||||||
|
heartbeat_interval = -1
|
||||||
|
heartbeat_timeout = -1
|
||||||
|
tcp_mux_keepalive_interval = 2
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = %d
|
||||||
|
remote_port = %d
|
||||||
|
`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||||
|
|
||||||
|
// run frps and frpc
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
|
||||||
|
})
|
||||||
|
})
|
54
test/e2e/features/monitor.go
Normal file
54
test/e2e/features/monitor.go
Normal 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
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -3,8 +3,8 @@ package framework
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@ -89,7 +89,7 @@ func (f *Framework) BeforeEach() {
|
|||||||
|
|
||||||
f.cleanupHandle = AddCleanupAction(f.AfterEach)
|
f.cleanupHandle = AddCleanupAction(f.AfterEach)
|
||||||
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "frp-e2e-test-*")
|
dir, err := os.MkdirTemp(os.TempDir(), "frp-e2e-test-*")
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
f.TempDirectory = dir
|
f.TempDirectory = dir
|
||||||
|
|
||||||
@ -256,3 +256,10 @@ func (f *Framework) RunServer(portName string, s server.Server) {
|
|||||||
func (f *Framework) SetEnvs(envs []string) {
|
func (f *Framework) SetEnvs(envs []string) {
|
||||||
f.osEnvs = envs
|
f.osEnvs = envs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Framework) WriteTempFile(name string, content string) string {
|
||||||
|
filePath := filepath.Join(f.TempDirectory, name)
|
||||||
|
err := os.WriteFile(filePath, []byte(content), 0766)
|
||||||
|
ExpectNoError(err)
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ package framework
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// RunProcesses run multiple processes from templates.
|
// RunProcesses run multiple processes from templates.
|
||||||
// The first template should always be frps.
|
// The first template should always be frps.
|
||||||
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
|
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) ([]*process.Process, []*process.Process) {
|
||||||
templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
|
templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
|
||||||
for _, t := range serverTemplates {
|
for _, t := range serverTemplates {
|
||||||
templates = append(templates, t)
|
templates = append(templates, t)
|
||||||
@ -28,35 +28,41 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
|||||||
f.usedPorts[name] = port
|
f.usedPorts[name] = port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentServerProcesses := make([]*process.Process, 0, len(serverTemplates))
|
||||||
for i := range serverTemplates {
|
for i := range serverTemplates {
|
||||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
|
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
|
||||||
err = ioutil.WriteFile(path, []byte(outs[i]), 0666)
|
err = os.WriteFile(path, []byte(outs[i]), 0666)
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
flog.Trace("[%s] %s", path, outs[i])
|
flog.Trace("[%s] %s", path, outs[i])
|
||||||
|
|
||||||
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
|
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
|
||||||
f.serverConfPaths = append(f.serverConfPaths, path)
|
f.serverConfPaths = append(f.serverConfPaths, path)
|
||||||
f.serverProcesses = append(f.serverProcesses, p)
|
f.serverProcesses = append(f.serverProcesses, p)
|
||||||
|
currentServerProcesses = append(currentServerProcesses, p)
|
||||||
err = p.Start()
|
err = p.Start()
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
|
||||||
for i := range clientTemplates {
|
for i := range clientTemplates {
|
||||||
index := i + len(serverTemplates)
|
index := i + len(serverTemplates)
|
||||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
|
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
|
||||||
err = ioutil.WriteFile(path, []byte(outs[index]), 0666)
|
err = os.WriteFile(path, []byte(outs[index]), 0666)
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
flog.Trace("[%s] %s", path, outs[index])
|
flog.Trace("[%s] %s", path, outs[index])
|
||||||
|
|
||||||
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
|
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
|
||||||
f.clientConfPaths = append(f.clientConfPaths, path)
|
f.clientConfPaths = append(f.clientConfPaths, path)
|
||||||
f.clientProcesses = append(f.clientProcesses, p)
|
f.clientProcesses = append(f.clientProcesses, p)
|
||||||
|
currentClientProcesses = append(currentClientProcesses, p)
|
||||||
err = p.Start()
|
err = p.Start()
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
return currentServerProcesses, currentClientProcesses
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
|
func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
|
||||||
@ -85,7 +91,7 @@ func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
|
|||||||
func (f *Framework) GenerateConfigFile(content string) string {
|
func (f *Framework) GenerateConfigFile(content string) string {
|
||||||
f.configFileIndex++
|
f.configFileIndex++
|
||||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
|
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
|
||||||
err := ioutil.WriteFile(path, []byte(content), 0666)
|
err := os.WriteFile(path, []byte(content), 0666)
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,11 @@ func NewRequestExpect(f *Framework) *RequestExpect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *RequestExpect) Request(req *request.Request) *RequestExpect {
|
||||||
|
e.req = req
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect {
|
func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect {
|
||||||
f(e.req)
|
f(e.req)
|
||||||
return e
|
return e
|
||||||
@ -108,7 +113,7 @@ func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
|
|||||||
if !bytes.Equal(e.expectResp, ret.Content) {
|
if !bytes.Equal(e.expectResp, ret.Content) {
|
||||||
flog.Trace("Response info: %+v", ret)
|
flog.Trace("Response info: %+v", ret)
|
||||||
}
|
}
|
||||||
ExpectEqualValuesWithOffset(1, e.expectResp, ret.Content, e.explain...)
|
ExpectEqualValuesWithOffset(1, ret.Content, e.expectResp, e.explain...)
|
||||||
} else {
|
} else {
|
||||||
for _, fn := range fns {
|
for _, fn := range fns {
|
||||||
ok := fn(ret)
|
ok := fn(ret)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package httpserver
|
package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -10,9 +10,10 @@ import (
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
bindAddr string
|
bindAddr string
|
||||||
bindPort int
|
bindPort int
|
||||||
hanlder http.Handler
|
handler http.Handler
|
||||||
|
|
||||||
l net.Listener
|
l net.Listener
|
||||||
|
tlsConfig *tls.Config
|
||||||
hs *http.Server
|
hs *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,9 +44,25 @@ func WithBindPort(port int) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithTlsConfig(tlsConfig *tls.Config) Option {
|
||||||
|
return func(s *Server) *Server {
|
||||||
|
s.tlsConfig = tlsConfig
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithHandler(h http.Handler) Option {
|
func WithHandler(h http.Handler) Option {
|
||||||
return func(s *Server) *Server {
|
return func(s *Server) *Server {
|
||||||
s.hanlder = h
|
s.handler = h
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithResponse(resp []byte) Option {
|
||||||
|
return func(s *Server) *Server {
|
||||||
|
s.handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(resp)
|
||||||
|
})
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,10 +75,16 @@ func (s *Server) Run() error {
|
|||||||
addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))
|
addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))
|
||||||
hs := &http.Server{
|
hs := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.hanlder,
|
Handler: s.handler,
|
||||||
|
TLSConfig: s.tlsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.hs = hs
|
s.hs = hs
|
||||||
|
if s.tlsConfig == nil {
|
||||||
go hs.Serve(s.l)
|
go hs.Serve(s.l)
|
||||||
|
} else {
|
||||||
|
go hs.ServeTLS(s.l, "", "")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +96,7 @@ func (s *Server) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) initListener() (err error) {
|
func (s *Server) initListener() (err error) {
|
||||||
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
|
s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
libnet "github.com/fatedier/frp/pkg/util/net"
|
libnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
||||||
@ -99,7 +100,7 @@ func (s *Server) Close() error {
|
|||||||
func (s *Server) initListener() (err error) {
|
func (s *Server) initListener() (err error) {
|
||||||
switch s.netType {
|
switch s.netType {
|
||||||
case TCP:
|
case TCP:
|
||||||
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
|
s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
|
||||||
case UDP:
|
case UDP:
|
||||||
s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
|
s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
|
||||||
case Unix:
|
case Unix:
|
||||||
|
68
test/e2e/pkg/cert/generator.go
Normal file
68
test/e2e/pkg/cert/generator.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Artifacts hosts a private key, its corresponding serving certificate and
|
||||||
|
// the CA certificate that signs the serving certificate.
|
||||||
|
type Artifacts struct {
|
||||||
|
// PEM encoded private key
|
||||||
|
Key []byte
|
||||||
|
// PEM encoded serving certificate
|
||||||
|
Cert []byte
|
||||||
|
// PEM encoded CA private key
|
||||||
|
CAKey []byte
|
||||||
|
// PEM encoded CA certificate
|
||||||
|
CACert []byte
|
||||||
|
// Resource version of the certs
|
||||||
|
ResourceVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertGenerator is an interface to provision the serving certificate.
|
||||||
|
type CertGenerator interface {
|
||||||
|
// Generate returns a Artifacts struct.
|
||||||
|
Generate(CommonName string) (*Artifacts, error)
|
||||||
|
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
|
||||||
|
SetCA(caKey, caCert []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidCACert think cert and key are valid if they meet the following requirements:
|
||||||
|
// - key and cert are valid pair
|
||||||
|
// - caCert is the root ca of cert
|
||||||
|
// - cert is for dnsName
|
||||||
|
// - cert won't expire before time
|
||||||
|
func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool {
|
||||||
|
if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Verify key and cert are valid pair
|
||||||
|
_, err := tls.X509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify cert is valid for at least 1 year.
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
if !pool.AppendCertsFromPEM(caCert) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode(cert)
|
||||||
|
if block == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ops := x509.VerifyOptions{
|
||||||
|
DNSName: dnsName,
|
||||||
|
Roots: pool,
|
||||||
|
CurrentTime: time,
|
||||||
|
}
|
||||||
|
_, err = c.Verify(ops)
|
||||||
|
return err == nil
|
||||||
|
}
|
169
test/e2e/pkg/cert/selfsigned.go
Normal file
169
test/e2e/pkg/cert/selfsigned.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/client-go/util/cert"
|
||||||
|
"k8s.io/client-go/util/keyutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelfSignedCertGenerator struct {
|
||||||
|
caKey []byte
|
||||||
|
caCert []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ CertGenerator = &SelfSignedCertGenerator{}
|
||||||
|
|
||||||
|
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
|
||||||
|
func (cp *SelfSignedCertGenerator) SetCA(caKey, caCert []byte) {
|
||||||
|
cp.caKey = caKey
|
||||||
|
cp.caCert = caCert
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate creates and returns a CA certificate, certificate and
|
||||||
|
// key for the server or client. Key and Cert are used by the server or client
|
||||||
|
// to establish trust for others, CA certificate is used by the
|
||||||
|
// client or server to verify the other's authentication chain.
|
||||||
|
// The cert will be valid for 365 days.
|
||||||
|
func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artifacts, error) {
|
||||||
|
var signingKey *rsa.PrivateKey
|
||||||
|
var signingCert *x509.Certificate
|
||||||
|
var valid bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
valid, signingKey, signingCert = cp.validCACert()
|
||||||
|
if !valid {
|
||||||
|
signingKey, err = NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create the CA private key: %v", err)
|
||||||
|
}
|
||||||
|
signingCert, err = cert.NewSelfSignedCACert(cert.Config{CommonName: commonName}, signingKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create the CA cert: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostIP := net.ParseIP(commonName)
|
||||||
|
var altIPs []net.IP
|
||||||
|
DNSNames := []string{"localhost"}
|
||||||
|
if hostIP.To4() != nil {
|
||||||
|
altIPs = append(altIPs, hostIP.To4())
|
||||||
|
} else {
|
||||||
|
DNSNames = append(DNSNames, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create the private key: %v", err)
|
||||||
|
}
|
||||||
|
signedCert, err := NewSignedCert(
|
||||||
|
cert.Config{
|
||||||
|
CommonName: commonName,
|
||||||
|
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||||
|
AltNames: cert.AltNames{IPs: altIPs, DNSNames: DNSNames},
|
||||||
|
},
|
||||||
|
key, signingCert, signingKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create the cert: %v", err)
|
||||||
|
}
|
||||||
|
return &Artifacts{
|
||||||
|
Key: EncodePrivateKeyPEM(key),
|
||||||
|
Cert: EncodeCertPEM(signedCert),
|
||||||
|
CAKey: EncodePrivateKeyPEM(signingKey),
|
||||||
|
CACert: EncodeCertPEM(signingCert),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *SelfSignedCertGenerator) validCACert() (bool, *rsa.PrivateKey, *x509.Certificate) {
|
||||||
|
if !ValidCACert(cp.caKey, cp.caCert, cp.caCert, "",
|
||||||
|
time.Now().AddDate(1, 0, 0)) {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
key, err := keyutil.ParsePrivateKeyPEM(cp.caKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
privateKey, ok := key.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, err := cert.ParseCertsPEM(cp.caCert)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
if len(certs) != 1 {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
return true, privateKey, certs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateKey creates an RSA private key
|
||||||
|
func NewPrivateKey() (*rsa.PrivateKey, error) {
|
||||||
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSignedCert creates a signed certificate using the given CA certificate and key
|
||||||
|
func NewSignedCert(cfg cert.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
|
||||||
|
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(cfg.CommonName) == 0 {
|
||||||
|
return nil, errors.New("must specify a CommonName")
|
||||||
|
}
|
||||||
|
if len(cfg.Usages) == 0 {
|
||||||
|
return nil, errors.New("must specify at least one ExtKeyUsage")
|
||||||
|
}
|
||||||
|
|
||||||
|
certTmpl := x509.Certificate{
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: cfg.CommonName,
|
||||||
|
Organization: cfg.Organization,
|
||||||
|
},
|
||||||
|
DNSNames: cfg.AltNames.DNSNames,
|
||||||
|
IPAddresses: cfg.AltNames.IPs,
|
||||||
|
SerialNumber: serial,
|
||||||
|
NotBefore: caCert.NotBefore,
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10).UTC(),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: cfg.Usages,
|
||||||
|
}
|
||||||
|
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x509.ParseCertificate(certDERBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodePrivateKeyPEM returns PEM-encoded private key data
|
||||||
|
func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
|
||||||
|
block := pem.Block{
|
||||||
|
Type: keyutil.RSAPrivateKeyBlockType,
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(&block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeCertPEM returns PEM-encoded certificate data
|
||||||
|
func EncodeCertPEM(ct *x509.Certificate) []byte {
|
||||||
|
block := pem.Block{
|
||||||
|
Type: cert.CertificateBlockType,
|
||||||
|
Bytes: ct.Raw,
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(&block)
|
||||||
|
}
|
@ -3,6 +3,7 @@ package port
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
@ -57,7 +58,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Maybe not controlled by us, mark it used.
|
// Maybe not controlled by us, mark it used.
|
||||||
pa.used.Insert(port)
|
pa.used.Insert(port)
|
||||||
@ -65,7 +66,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
|||||||
}
|
}
|
||||||
l.Close()
|
l.Close()
|
||||||
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port))
|
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package request
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -13,7 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
||||||
libnet "github.com/fatedier/golib/net"
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
@ -25,11 +25,12 @@ type Request struct {
|
|||||||
body []byte
|
body []byte
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
|
||||||
// for http
|
// for http or https
|
||||||
method string
|
method string
|
||||||
host string
|
host string
|
||||||
path string
|
path string
|
||||||
headers map[string]string
|
headers map[string]string
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
|
||||||
proxyURL string
|
proxyURL string
|
||||||
}
|
}
|
||||||
@ -64,6 +65,11 @@ func (r *Request) HTTP() *Request {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Request) HTTPS() *Request {
|
||||||
|
r.protocol = "https"
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Request) Proxy(url string) *Request {
|
func (r *Request) Proxy(url string) *Request {
|
||||||
r.proxyURL = url
|
r.proxyURL = url
|
||||||
return r
|
return r
|
||||||
@ -102,6 +108,11 @@ func (r *Request) HTTPHeaders(headers map[string]string) *Request {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Request) TLSConfig(tlsConfig *tls.Config) *Request {
|
||||||
|
r.tlsConfig = tlsConfig
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Request) Timeout(timeout time.Duration) *Request {
|
func (r *Request) Timeout(timeout time.Duration) *Request {
|
||||||
r.timeout = timeout
|
r.timeout = timeout
|
||||||
return r
|
return r
|
||||||
@ -119,10 +130,10 @@ func (r *Request) Do() (*Response, error) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
addr := net.JoinHostPort(r.addr, strconv.Itoa(r.port))
|
addr := net.JoinHostPort(r.addr, strconv.Itoa(r.port))
|
||||||
// for protocol http
|
// for protocol http and https
|
||||||
if r.protocol == "http" {
|
if r.protocol == "http" || r.protocol == "https" {
|
||||||
return r.sendHTTPRequest(r.method, fmt.Sprintf("http://%s%s", addr, r.path),
|
return r.sendHTTPRequest(r.method, fmt.Sprintf("%s://%s%s", r.protocol, addr, r.path),
|
||||||
r.host, r.headers, r.proxyURL, r.body)
|
r.host, r.headers, r.proxyURL, r.body, r.tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// for protocol tcp and udp
|
// for protocol tcp and udp
|
||||||
@ -130,7 +141,11 @@ func (r *Request) Do() (*Response, error) {
|
|||||||
if r.protocol != "tcp" {
|
if r.protocol != "tcp" {
|
||||||
return nil, fmt.Errorf("only tcp protocol is allowed for proxy")
|
return nil, fmt.Errorf("only tcp protocol is allowed for proxy")
|
||||||
}
|
}
|
||||||
conn, err = libnet.DialTcpByProxy(r.proxyURL, addr)
|
proxyType, proxyAddress, auth, err := libdial.ParseProxyURL(r.proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse ProxyURL error: %v", err)
|
||||||
|
}
|
||||||
|
conn, err = libdial.Dial(addr, libdial.WithProxy(proxyType, proxyAddress), libdial.WithProxyAuth(auth))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -165,7 +180,10 @@ type Response struct {
|
|||||||
Content []byte
|
Content []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers map[string]string, proxy string, body []byte) (*Response, error) {
|
func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers map[string]string,
|
||||||
|
proxy string, body []byte, tlsConfig *tls.Config,
|
||||||
|
) (*Response, error) {
|
||||||
|
|
||||||
var inBody io.Reader
|
var inBody io.Reader
|
||||||
if len(body) != 0 {
|
if len(body) != 0 {
|
||||||
inBody = bytes.NewReader(body)
|
inBody = bytes.NewReader(body)
|
||||||
@ -190,6 +208,7 @@ func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers ma
|
|||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
if len(proxy) != 0 {
|
if len(proxy) != 0 {
|
||||||
tr.Proxy = func(req *http.Request) (*url.URL, error) {
|
tr.Proxy = func(req *http.Request) (*url.URL, error) {
|
||||||
@ -203,7 +222,7 @@ func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret := &Response{Code: resp.StatusCode, Header: resp.Header}
|
ret := &Response{Code: resp.StatusCode, Header: resp.Header}
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
buf, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -125,7 +125,7 @@ func (c *Client) do(req *http.Request) (string, error) {
|
|||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return "", fmt.Errorf("api status code [%d]", resp.StatusCode)
|
return "", fmt.Errorf("api status 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
|
||||||
}
|
}
|
||||||
|
316
test/e2e/plugin/client.go
Normal file
316
test/e2e/plugin/client.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
"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/cert"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/utils"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("[Feature: Client-Plugins]", func() {
|
||||||
|
f := framework.NewDefaultFramework()
|
||||||
|
|
||||||
|
Describe("UnixDomainSocket", func() {
|
||||||
|
It("Expose a unix domain socket echo server", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
|
getProxyConf := func(proxyName string, portName string, extra string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
[%s]
|
||||||
|
type = tcp
|
||||||
|
remote_port = {{ .%s }}
|
||||||
|
plugin = unix_domain_socket
|
||||||
|
plugin_unix_path = {{ .%s }}
|
||||||
|
`+extra, proxyName, portName, framework.UDSEchoServerAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
proxyName string
|
||||||
|
portName string
|
||||||
|
extraConfig string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
proxyName: "normal",
|
||||||
|
portName: port.GenName("Normal"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
proxyName: "with-encryption",
|
||||||
|
portName: port.GenName("WithEncryption"),
|
||||||
|
extraConfig: "use_encryption = true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
proxyName: "with-compression",
|
||||||
|
portName: port.GenName("WithCompression"),
|
||||||
|
extraConfig: "use_compression = true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
proxyName: "with-encryption-and-compression",
|
||||||
|
portName: port.GenName("WithEncryptionAndCompression"),
|
||||||
|
extraConfig: `
|
||||||
|
use_encryption = true
|
||||||
|
use_compression = true
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// build all client config
|
||||||
|
for _, test := range tests {
|
||||||
|
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
|
||||||
|
}
|
||||||
|
// run frps and frpc
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("http_proxy", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf += fmt.Sprintf(`
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
remote_port = %d
|
||||||
|
plugin = http_proxy
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = 123
|
||||||
|
`, remotePort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
// http proxy, no auth info
|
||||||
|
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().Proxy("http://127.0.0.1:" + strconv.Itoa(remotePort))
|
||||||
|
}).Ensure(framework.ExpectResponseCode(407))
|
||||||
|
|
||||||
|
// http proxy, correct auth
|
||||||
|
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||||
|
}).Ensure()
|
||||||
|
|
||||||
|
// connect TCP server by CONNECT method
|
||||||
|
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||||
|
r.TCP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("socks5 proxy", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf += fmt.Sprintf(`
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
remote_port = %d
|
||||||
|
plugin = socks5
|
||||||
|
plugin_user = abc
|
||||||
|
plugin_passwd = 123
|
||||||
|
`, remotePort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
// http proxy, no auth info
|
||||||
|
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||||
|
r.TCP().Proxy("socks5://127.0.0.1:" + strconv.Itoa(remotePort))
|
||||||
|
}).ExpectError(true).Ensure()
|
||||||
|
|
||||||
|
// http proxy, correct auth
|
||||||
|
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||||
|
r.TCP().Proxy("socks5://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||||
|
}).Ensure()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("static_file", func() {
|
||||||
|
vhostPort := f.AllocPort()
|
||||||
|
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||||
|
vhost_http_port = %d
|
||||||
|
`, vhostPort)
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
f.WriteTempFile("test_static_file", "foo")
|
||||||
|
clientConf += fmt.Sprintf(`
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
remote_port = %d
|
||||||
|
plugin = static_file
|
||||||
|
plugin_local_path = %s
|
||||||
|
|
||||||
|
[http]
|
||||||
|
type = http
|
||||||
|
custom_domains = example.com
|
||||||
|
plugin = static_file
|
||||||
|
plugin_local_path = %s
|
||||||
|
|
||||||
|
[http-with-auth]
|
||||||
|
type = http
|
||||||
|
custom_domains = other.example.com
|
||||||
|
plugin = static_file
|
||||||
|
plugin_local_path = %s
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = 123
|
||||||
|
`, remotePort, f.TempDirectory, f.TempDirectory, f.TempDirectory)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
// from tcp proxy
|
||||||
|
framework.NewRequestExpect(f).Request(
|
||||||
|
framework.NewHTTPRequest().HTTPPath("/test_static_file").Port(remotePort),
|
||||||
|
).ExpectResp([]byte("foo")).Ensure()
|
||||||
|
|
||||||
|
// from http proxy without auth
|
||||||
|
framework.NewRequestExpect(f).Request(
|
||||||
|
framework.NewHTTPRequest().HTTPHost("example.com").HTTPPath("/test_static_file").Port(vhostPort),
|
||||||
|
).ExpectResp([]byte("foo")).Ensure()
|
||||||
|
|
||||||
|
// from http proxy with auth
|
||||||
|
framework.NewRequestExpect(f).Request(
|
||||||
|
framework.NewHTTPRequest().HTTPHost("other.example.com").HTTPPath("/test_static_file").Port(vhostPort).HTTPHeaders(map[string]string{
|
||||||
|
"Authorization": utils.BasicAuth("abc", "123"),
|
||||||
|
}),
|
||||||
|
).ExpectResp([]byte("foo")).Ensure()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("http2https", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
vhostHTTPPort := f.AllocPort()
|
||||||
|
serverConf += fmt.Sprintf(`
|
||||||
|
vhost_http_port = %d
|
||||||
|
`, vhostHTTPPort)
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[http2https]
|
||||||
|
type = http
|
||||||
|
custom_domains = example.com
|
||||||
|
plugin = http2https
|
||||||
|
plugin_local_addr = 127.0.0.1:%d
|
||||||
|
`, localPort)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
Port(vhostHTTPPort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPHost("example.com")
|
||||||
|
}).
|
||||||
|
ExpectResp([]byte("test")).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("https2http", func() {
|
||||||
|
generator := &cert.SelfSignedCertGenerator{}
|
||||||
|
artifacts, err := generator.Generate("example.com")
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
|
||||||
|
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
|
||||||
|
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
vhostHTTPSPort := f.AllocPort()
|
||||||
|
serverConf += fmt.Sprintf(`
|
||||||
|
vhost_https_port = %d
|
||||||
|
`, vhostHTTPSPort)
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[https2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = example.com
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:%d
|
||||||
|
plugin_crt_path = %s
|
||||||
|
plugin_key_path = %s
|
||||||
|
`, localPort, crtPath, keyPath)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
localServer := httpserver.New(
|
||||||
|
httpserver.WithBindPort(localPort),
|
||||||
|
httpserver.WithResponse([]byte("test")),
|
||||||
|
)
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
Port(vhostHTTPSPort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
|
||||||
|
ServerName: "example.com",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
})
|
||||||
|
}).
|
||||||
|
ExpectResp([]byte("test")).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("https2https", func() {
|
||||||
|
generator := &cert.SelfSignedCertGenerator{}
|
||||||
|
artifacts, err := generator.Generate("example.com")
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
|
||||||
|
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
|
||||||
|
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
vhostHTTPSPort := f.AllocPort()
|
||||||
|
serverConf += fmt.Sprintf(`
|
||||||
|
vhost_https_port = %d
|
||||||
|
`, vhostHTTPSPort)
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[https2https]
|
||||||
|
type = https
|
||||||
|
custom_domains = example.com
|
||||||
|
plugin = https2https
|
||||||
|
plugin_local_addr = 127.0.0.1:%d
|
||||||
|
plugin_crt_path = %s
|
||||||
|
plugin_key_path = %s
|
||||||
|
`, localPort, crtPath, keyPath)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
localServer := httpserver.New(
|
||||||
|
httpserver.WithBindPort(localPort),
|
||||||
|
httpserver.WithResponse([]byte("test")),
|
||||||
|
httpserver.WithTlsConfig(tlsConfig),
|
||||||
|
)
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
Port(vhostHTTPSPort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
|
||||||
|
ServerName: "example.com",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
})
|
||||||
|
}).
|
||||||
|
ExpectResp([]byte("test")).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
|
})
|
@ -1,106 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("[Feature: Client-Plugins]", func() {
|
|
||||||
f := framework.NewDefaultFramework()
|
|
||||||
|
|
||||||
Describe("UnixDomainSocket", func() {
|
|
||||||
It("Expose a unix domain socket echo server", func() {
|
|
||||||
serverConf := consts.DefaultServerConfig
|
|
||||||
clientConf := consts.DefaultClientConfig
|
|
||||||
|
|
||||||
getProxyConf := func(proxyName string, portName string, extra string) string {
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
[%s]
|
|
||||||
type = tcp
|
|
||||||
remote_port = {{ .%s }}
|
|
||||||
plugin = unix_domain_socket
|
|
||||||
plugin_unix_path = {{ .%s }}
|
|
||||||
`+extra, proxyName, portName, framework.UDSEchoServerAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
proxyName string
|
|
||||||
portName string
|
|
||||||
extraConfig string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
proxyName: "normal",
|
|
||||||
portName: port.GenName("Normal"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
proxyName: "with-encryption",
|
|
||||||
portName: port.GenName("WithEncryption"),
|
|
||||||
extraConfig: "use_encryption = true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
proxyName: "with-compression",
|
|
||||||
portName: port.GenName("WithCompression"),
|
|
||||||
extraConfig: "use_compression = true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
proxyName: "with-encryption-and-compression",
|
|
||||||
portName: port.GenName("WithEncryptionAndCompression"),
|
|
||||||
extraConfig: `
|
|
||||||
use_encryption = true
|
|
||||||
use_compression = true
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// build all client config
|
|
||||||
for _, test := range tests {
|
|
||||||
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
|
|
||||||
}
|
|
||||||
// run frps and frpc
|
|
||||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
It("plugin http_proxy", func() {
|
|
||||||
serverConf := consts.DefaultServerConfig
|
|
||||||
clientConf := consts.DefaultClientConfig
|
|
||||||
|
|
||||||
remotePort := f.AllocPort()
|
|
||||||
clientConf += fmt.Sprintf(`
|
|
||||||
[tcp]
|
|
||||||
type = tcp
|
|
||||||
remote_port = %d
|
|
||||||
plugin = http_proxy
|
|
||||||
plugin_http_user = abc
|
|
||||||
plugin_http_passwd = 123
|
|
||||||
`, remotePort)
|
|
||||||
|
|
||||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
|
||||||
|
|
||||||
// http proxy, no auth info
|
|
||||||
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
|
||||||
r.HTTP().Proxy("http://127.0.0.1:" + strconv.Itoa(remotePort))
|
|
||||||
}).Ensure(framework.ExpectResponseCode(407))
|
|
||||||
|
|
||||||
// http proxy, correct auth
|
|
||||||
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
|
||||||
r.HTTP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
|
||||||
}).Ensure()
|
|
||||||
|
|
||||||
// connect TCP server by CONNECT method
|
|
||||||
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
|
||||||
r.TCP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user