mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 15:45:39 +00:00
Compare commits
149 Commits
v0.37.1
...
0d6d968fe8
Author | SHA1 | Date | |
---|---|---|---|
|
0d6d968fe8 | ||
|
98068402c8 | ||
|
4915852b9c | ||
|
756dd1ad5e | ||
|
c71efde303 | ||
|
9f029e3248 | ||
|
8095075719 | ||
|
2225a1781f | ||
|
0214b974dd | ||
|
738c53ce47 | ||
|
db52f07d34 | ||
|
f6b8645f56 | ||
|
2c2c4ecdbc | ||
|
3faae194d0 | ||
|
a22d6c9504 | ||
|
9800b4cfcf | ||
|
8f394dba27 | ||
|
fccd518512 | ||
|
8fb99ef7a9 | ||
|
968ba4d3a1 | ||
|
862b1642ba | ||
|
54eb704650 | ||
|
8c6303c1e5 | ||
|
871511ba52 | ||
|
cb6d7ba7f9 | ||
|
31f40aa913 | ||
|
2f59e967a0 | ||
|
fe8374e99b | ||
|
24f0b3afa5 | ||
|
39941117b6 | ||
|
6a1f9ad893 | ||
|
88e74ff24d | ||
|
18ab58eb25 | ||
|
534dc99d55 | ||
|
fa0593ae2c | ||
|
89fff7d11d | ||
|
38d42dbe4b | ||
|
aa31d7ad0b | ||
|
113e3b0b0d | ||
|
100148d925 | ||
|
6b3daffaf0 | ||
|
5e17bc7bf1 | ||
|
b1b8d9a82b | ||
|
24c7d1d9e2 | ||
|
d205c26480 | ||
|
0eecab06c1 | ||
|
ad3548d332 | ||
|
595aba5a9b | ||
|
679992db25 | ||
|
5cfbb976f4 | ||
|
b03f0ad1e6 | ||
|
804f2910fd | ||
|
a4189ba474 | ||
|
e2d28d9929 | ||
|
9ec84f8143 | ||
|
7678938c08 | ||
|
b2e3946800 | ||
|
af0b7939a7 | ||
|
2f66dc3e99 | ||
|
649df8827c | ||
|
da51adc276 | ||
|
e5af37bc8c | ||
|
8ab474cc97 | ||
|
e8c8d5903a | ||
|
a301046f3d | ||
|
34ab6b0e74 | ||
|
cf66ca10b4 | ||
|
3fbe6b659e | ||
|
6a71d71e58 | ||
|
6ecc97c857 | ||
|
ba492f07c3 | ||
|
9d077b02cf | ||
|
f4e4fbea62 | ||
|
3e721d122b | ||
|
1bc899ec12 | ||
|
6f2571980c | ||
|
8888610d83 | ||
|
fa7c05c617 | ||
|
218b354f82 | ||
|
c652b8ef07 | ||
|
5b8b145577 | ||
|
fe5fb0326b | ||
|
0711295b0a | ||
|
4af85da0c2 | ||
|
bd89eaba2f | ||
|
a72259c604 | ||
|
44eb513f05 | ||
|
eb1e19a821 | ||
|
6c658586f6 | ||
|
888ed25314 | ||
|
21240ed962 | ||
|
6481870d03 | ||
|
a7a4ba270d | ||
|
915d9f4c09 | ||
|
18a2af4703 | ||
|
305e40fa8a | ||
|
10f2620131 | ||
|
4acae540c8 | ||
|
11b13533a0 | ||
|
100d556336 | ||
|
452fe25cc6 | ||
|
63efa6b776 | ||
|
37c27169ac | ||
|
ce677820c6 | ||
|
1f88a7a0b8 | ||
|
eeea7602d9 | ||
|
bf635c0e90 | ||
|
cd31359a27 | ||
|
19739ed31a | ||
|
10100c28d9 | ||
|
88fcc079e8 | ||
|
ddc1e163c4 | ||
|
d20a6d3d75 | ||
|
6194273615 | ||
|
b2311e55e7 | ||
|
07873d471f | ||
|
2dab5d0bca | ||
|
9ca2b586f8 | ||
|
e59eacb8a2 | ||
|
0db4fc07fb | ||
|
70f4caac23 | ||
|
293003fcdb | ||
|
4bfc89d988 | ||
|
22412851b4 | ||
|
e9775bd70f | ||
|
ff7b8b0b62 | ||
|
491c1d7dc4 | ||
|
ea568e8a4f | ||
|
0fb6aeef58 | ||
|
032f33fe5a | ||
|
bbc8b438d5 | ||
|
05b1ace21f | ||
|
cbdd73b94f | ||
|
bf06e3b107 | ||
|
143750901e | ||
|
71489d194c | ||
|
85aa3df256 | ||
|
f1a51eba18 | ||
|
1d26ea440b | ||
|
998e678a7f | ||
|
0cee1877e3 | ||
|
72a7fd948e | ||
|
357c9b0dcb | ||
|
14bd0716d0 | ||
|
2f74f54f18 | ||
|
a62a9431b1 | ||
|
42745a3da2 | ||
|
82f80a22be | ||
|
f570dcb307 |
@@ -2,16 +2,16 @@ version: 2
|
||||
jobs:
|
||||
go-version-latest:
|
||||
docker:
|
||||
- image: circleci/golang:1.16-node
|
||||
working_directory: /go/src/github.com/fatedier/frp
|
||||
- image: cimg/go:1.20-node
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
- run: make alltest
|
||||
go-version-last:
|
||||
docker:
|
||||
- image: circleci/golang:1.15-node
|
||||
working_directory: /go/src/github.com/fatedier/frp
|
||||
- image: cimg/go:1.19-node
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- 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
|
||||
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"
|
10
.github/pull_request_template.md
vendored
Normal file
10
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
### Summary
|
||||
|
||||
copilot:summary
|
||||
|
||||
### WHY
|
||||
<!-- author to complete -->
|
||||
|
||||
### Walkthrough
|
||||
|
||||
copilot:walkthrough
|
110
.github/workflows/build-and-push-image.yml
vendored
110
.github/workflows/build-and-push-image.yml
vendored
@@ -9,69 +9,25 @@ on:
|
||||
description: 'Image tag'
|
||||
required: true
|
||||
default: 'test'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
binary:
|
||||
name: Build Golang project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
|
||||
- run: |
|
||||
# https://github.com/actions/setup-go/issues/107
|
||||
cp -f `which go` /usr/bin/go
|
||||
|
||||
- run: go version
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Archive artifacts for frpc
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: frpc
|
||||
path: bin/frpc
|
||||
|
||||
- name: Archive artifacts for frps
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: frps
|
||||
path: bin/frps
|
||||
|
||||
image:
|
||||
name: Build Image from Dockerfile and binaries
|
||||
runs-on: ubuntu-latest
|
||||
needs: binary
|
||||
steps:
|
||||
# environment
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
# download binaries of frpc and frps
|
||||
- name: Download binary of frpc
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: frpc
|
||||
path: bin/frpc
|
||||
|
||||
- name: Download binary of frps
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: frps
|
||||
path: bin/frps
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# get image tag name
|
||||
- name: Get Image Tag Name
|
||||
@@ -81,6 +37,18 @@ jobs:
|
||||
else
|
||||
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to the GPR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GPR_TOKEN }}
|
||||
|
||||
# prepare image tags
|
||||
- name: Prepare Image Tags
|
||||
@@ -92,26 +60,24 @@ jobs:
|
||||
echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
|
||||
# build images
|
||||
- name: Build Images
|
||||
run: |
|
||||
# for Docker hub
|
||||
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} .
|
||||
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS }} .
|
||||
# for GPR
|
||||
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} .
|
||||
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} .
|
||||
- name: Build and push frpc
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./dockerfiles/Dockerfile-for-frpc
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.TAG_FRPC }}
|
||||
${{ env.TAG_FRPC_GPR }}
|
||||
|
||||
# push to dockerhub
|
||||
- name: Publish to Dockerhub
|
||||
run: |
|
||||
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
||||
docker push ${{ env.TAG_FRPC }}
|
||||
docker push ${{ env.TAG_FRPS }}
|
||||
|
||||
# push to gpr
|
||||
- name: Publish to GPR
|
||||
run: |
|
||||
echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin
|
||||
docker push ${{ env.TAG_FRPC_GPR }}
|
||||
docker push ${{ env.TAG_FRPS_GPR }}
|
||||
- name: Build and push frps
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./dockerfiles/Dockerfile-for-frps
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.TAG_FRPS }}
|
||||
${{ env.TAG_FRPS_GPR }}
|
||||
|
41
.github/workflows/golangci-lint.yml
vendored
Normal file
41
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
# Optional: allow read access to pull request. Use with `only-new-issues` option.
|
||||
pull-requests: read
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v1.51
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||
# takes precedence over all other caching options.
|
||||
# skip-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||
# skip-pkg-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||
# skip-build-cache: true
|
12
.github/workflows/goreleaser.yml
vendored
12
.github/workflows/goreleaser.yml
vendored
@@ -8,25 +8,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: '1.20'
|
||||
|
||||
- run: |
|
||||
# https://github.com/actions/setup-go/issues/107
|
||||
cp -f `which go` /usr/bin/go
|
||||
|
||||
- name: Make All
|
||||
run: |
|
||||
./package.sh
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist --release-notes=./Release.md
|
||||
|
18
.github/workflows/stale.yml
vendored
18
.github/workflows/stale.yml
vendored
@@ -8,19 +8,27 @@ on:
|
||||
description: 'In debug mod'
|
||||
required: false
|
||||
default: 'false'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
issues: write # for actions/stale to close stale issues
|
||||
pull-requests: write # for actions/stale to close stale PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
- uses: actions/stale@v6
|
||||
with:
|
||||
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-pr-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: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
|
||||
stale-issue-label: 'lifecycle/stale'
|
||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
stale-pr-label: 'lifecycle/stale'
|
||||
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
days-before-stale: 45
|
||||
days-before-close: 10
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
debug-only: ${{ github.event.inputs.debug-only }}
|
||||
exempt-all-pr-milestones: true
|
||||
exempt-all-pr-assignees: true
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,6 +31,7 @@ test/bin/
|
||||
vendor/
|
||||
dist/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
144
.golangci.yml
Normal file
144
.golangci.yml
Normal file
@@ -0,0 +1,144 @@
|
||||
service:
|
||||
golangci-lint-version: 1.51.x # use the fixed version to not introduce new linters unexpectedly
|
||||
|
||||
run:
|
||||
concurrency: 4
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 20m
|
||||
build-tags:
|
||||
- integ
|
||||
- integfuzz
|
||||
# which dirs to skip: they won't be analyzed;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but next dirs are always skipped independently
|
||||
# from this option's value:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs:
|
||||
- genfiles$
|
||||
- vendor$
|
||||
- bin$
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
- ".*\\.pb\\.go"
|
||||
- ".*\\.gen\\.go"
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- unused
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- goimports
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- gci
|
||||
- gosec
|
||||
- asciicheck
|
||||
- prealloc
|
||||
- predeclared
|
||||
- makezero
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
ignore-words:
|
||||
- cancelled
|
||||
- marshalled
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 160
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- exitAfterDefer
|
||||
unused:
|
||||
check-exported: false
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/fatedier/frp/)
|
||||
gosec:
|
||||
severity: "low"
|
||||
confidence: "low"
|
||||
excludes:
|
||||
- G102
|
||||
- G112
|
||||
- G306
|
||||
- G401
|
||||
- G402
|
||||
- G404
|
||||
- G501
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
# exclude:
|
||||
# - composite literal uses unkeyed fields
|
||||
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on test files.
|
||||
- path: _test\.go$|^tests/|^samples/
|
||||
linters:
|
||||
- errcheck
|
||||
- maligned
|
||||
|
||||
# keep it until we only support go1.20
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA1019: rand.Seed has been deprecated"
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: true
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
@@ -1,7 +1,10 @@
|
||||
builds:
|
||||
- skip: true
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: '{{ .ProjectName }}_sha256_checksums.txt'
|
||||
algorithm: sha256
|
||||
extra_files:
|
||||
- glob: ./release/packages/*
|
||||
release:
|
||||
# Same as for github
|
||||
# Note: it can only be one: either github, gitlab or gitea
|
||||
|
14
Makefile
14
Makefile
@@ -12,13 +12,19 @@ file:
|
||||
rm -rf ./assets/frpc/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||
rm -rf ./assets/frps/statik
|
||||
rm -rf ./assets/frpc/statik
|
||||
go generate ./assets/...
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
fmt-more:
|
||||
gofumpt -l -w .
|
||||
|
||||
gci:
|
||||
gci write -s standard -s default -s "prefix(github.com/fatedier/frp/)" ./
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
frps:
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||
|
||||
@@ -40,7 +46,7 @@ e2e:
|
||||
e2e-trace:
|
||||
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
||||
|
||||
alltest: gotest e2e
|
||||
alltest: vet gotest e2e
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
|
@@ -2,7 +2,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
||||
|
||||
all: build
|
||||
|
||||
@@ -23,3 +23,5 @@ app:
|
||||
@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
|
||||
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
||||
@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
|
||||
@mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe
|
||||
@mv ./release/frps_windows_arm64 ./release/frps_windows_arm64.exe
|
||||
|
181
README.md
181
README.md
@@ -1,4 +1,3 @@
|
||||
|
||||
# frp
|
||||
|
||||
[](https://circleci.com/gh/fatedier/frp)
|
||||
@@ -6,11 +5,20 @@
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
<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="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
## 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 that allows you to expose a local server located behind a NAT or firewall to the Internet. It currently supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, enabling requests to be forwarded to internal services via domain name.
|
||||
|
||||
frp also has a P2P connect mode.
|
||||
frp also offers a P2P connect mode.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -19,12 +27,12 @@ frp also has a P2P connect mode.
|
||||
* [Development Status](#development-status)
|
||||
* [Architecture](#architecture)
|
||||
* [Example Usage](#example-usage)
|
||||
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||
* [Forward DNS query request](#forward-dns-query-request)
|
||||
* [Forward Unix domain socket](#forward-unix-domain-socket)
|
||||
* [Access your computer in a LAN network via SSH](#access-your-computer-in-a-lan-network-via-ssh)
|
||||
* [Accessing Internal Web Services with Custom Domains in LAN](#accessing-internal-web-services-with-custom-domains-in-lan)
|
||||
* [Forward DNS query requests](#forward-dns-query-requests)
|
||||
* [Forward Unix Domain Socket](#forward-unix-domain-socket)
|
||||
* [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
|
||||
* [Enable HTTPS for local HTTP(S) service](#enable-https-for-local-https-service)
|
||||
* [Enable HTTPS for a local HTTP(S) service](#enable-https-for-a-local-https-service)
|
||||
* [Expose your service privately](#expose-your-service-privately)
|
||||
* [P2P Mode](#p2p-mode)
|
||||
* [Features](#features)
|
||||
@@ -48,6 +56,7 @@ frp also has a P2P connect mode.
|
||||
* [For Each Proxy](#for-each-proxy)
|
||||
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||
* [Support KCP Protocol](#support-kcp-protocol)
|
||||
* [Support QUIC Protocol](#support-quic-protocol)
|
||||
* [Connection Pooling](#connection-pooling)
|
||||
* [Load balancing](#load-balancing)
|
||||
* [Service Health Check](#service-health-check)
|
||||
@@ -67,17 +76,18 @@ frp also has a P2P connect mode.
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
* [Donation](#donation)
|
||||
* [AliPay](#alipay)
|
||||
* [Wechat Pay](#wechat-pay)
|
||||
* [GitHub Sponsors](#github-sponsors)
|
||||
* [PayPal](#paypal)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## Development Status
|
||||
|
||||
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 currently under development. You can try the latest release version in the `master` branch, or use the `dev` branch to access the version currently 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 currently working on version 2 and attempting to perform some code refactoring and improvements. However, please note that it will not be compatible with version 1.
|
||||
|
||||
We will transition from version 0 to version 1 at the appropriate time and will only accept bug fixes and improvements, rather than big feature requests.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -85,15 +95,15 @@ frp is under development. Try the latest release version in the `master` branch,
|
||||
|
||||
## Example Usage
|
||||
|
||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your operating system and architecture.
|
||||
To begin, download the latest program for your operating system and architecture from the [Release](https://github.com/fatedier/frp/releases) page.
|
||||
|
||||
Put `frps` and `frps.ini` onto your server A with public IP.
|
||||
Next, place the `frps` binary and `frps.ini` configuration file on Server A, which has a public IP address.
|
||||
|
||||
Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected from public Internet).
|
||||
Finally, place the `frpc` binary and `frpc.ini` configuration file on Server B, which is located on a LAN that cannot be directly accessed from the public internet.
|
||||
|
||||
### Access your computer in LAN by SSH
|
||||
### Access your computer in a LAN network via SSH
|
||||
|
||||
1. Modify `frps.ini` on server A and set the `bind_port` to be connected to frp clients:
|
||||
1. Modify `frps.ini` on server A by setting the `bind_port` for frp clients to connect to:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -105,7 +115,7 @@ Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected fro
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. On server B, modify `frpc.ini` to put in your `frps` server public IP as `server_addr` field:
|
||||
3. Modify `frpc.ini` on server B and set the `server_addr` field to the public IP address of your frps server:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -120,23 +130,23 @@ Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected fro
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
Note that `local_port` (listened on client) and `remote_port` (exposed on server) are for traffic goes in/out the frp system, whereas `server_port` is used between frps.
|
||||
Note that the `local_port` (listened on the client) and `remote_port` (exposed on the server) are used for traffic going in and out of the frp system, while the `server_port` is used for communication between frps and frpc.
|
||||
|
||||
4. Start `frpc` on server B:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. From another machine, SSH to server B like this (assuming that username is `test`):
|
||||
5. To access server B from another machine through server A via SSH (assuming the username is `test`), use the following command:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### Visit your web service in LAN by custom domains
|
||||
### Accessing Internal Web Services with Custom Domains in LAN
|
||||
|
||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local IP.
|
||||
Sometimes we need to expose a local web service behind a NAT network to others for testing purposes with our own domain name.
|
||||
|
||||
However, we can expose an HTTP(S) service using frp.
|
||||
Unfortunately, we cannot resolve a domain name to a local IP. However, we can use frp to expose an HTTP(S) service.
|
||||
|
||||
1. Modify `frps.ini`, set the vhost HTTP port to 8080:
|
||||
1. Modify `frps.ini` and set the HTTP port for vhost to 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -149,7 +159,7 @@ However, we can expose an HTTP(S) service using frp.
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. The `local_port` is the port of your web service:
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. Specify the `local_port` of your web service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -167,11 +177,11 @@ However, we can expose an HTTP(S) service using frp.
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Resolve A record of `www.example.com` to the public IP of the remote frps server or CNAME record to your origin domain.
|
||||
5. Map the A record of `www.example.com` to either the public IP of the remote frps server or a CNAME record pointing to your original domain.
|
||||
|
||||
6. Now visit your local web service using url `http://www.example.com:8080`.
|
||||
6. Visit your local web service using url `http://www.example.com:8080`.
|
||||
|
||||
### Forward DNS query request
|
||||
### Forward DNS query requests
|
||||
|
||||
1. Modify `frps.ini`:
|
||||
|
||||
@@ -185,7 +195,7 @@ However, we can expose an HTTP(S) service using frp.
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server, forward DNS query request to Google Public DNS server `8.8.8.8:53`:
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. Forward DNS query requests to the Google Public DNS server `8.8.8.8:53`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -204,17 +214,17 @@ However, we can expose an HTTP(S) service using frp.
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Test DNS resolution using `dig` command:
|
||||
5. Test DNS resolution using the `dig` command:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.google.com`
|
||||
|
||||
### Forward Unix domain socket
|
||||
### Forward Unix Domain Socket
|
||||
|
||||
Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP.
|
||||
|
||||
Configure `frps` same as above.
|
||||
Configure `frps` as above.
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -229,17 +239,17 @@ Configure `frps` same as above.
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
2. Test: Get Docker version using `curl`:
|
||||
2. Test the configuration by getting the docker version using `curl`:
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### Expose a simple HTTP file server
|
||||
|
||||
Browser your files stored in the LAN, from public Internet.
|
||||
Expose a simple HTTP file server to access files stored in the LAN from the public Internet.
|
||||
|
||||
Configure `frps` same as above.
|
||||
Configure `frps` as described above, then:
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -257,13 +267,13 @@ Configure `frps` same as above.
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct user and password to view files in `/tmp/files` on the `frpc` machine.
|
||||
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct username and password to view files in `/tmp/files` on the `frpc` machine.
|
||||
|
||||
### Enable HTTPS for local HTTP(S) service
|
||||
### Enable HTTPS for a local HTTP(S) service
|
||||
|
||||
You may substitute `https2https` for the plugin, and point the `plugin_local_addr` to a HTTPS endpoint.
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -287,7 +297,7 @@ You may substitute `https2https` for the plugin, and point the `plugin_local_add
|
||||
|
||||
### Expose your service privately
|
||||
|
||||
Some services will be at risk if exposed directly to the public network. With **STCP** (secret TCP) mode, a preshared key is needed to access the service from another client.
|
||||
To mitigate risks associated with exposing certain services directly to the public network, STCP (Secret TCP) mode requires a preshared key to be used for access to the service from other clients.
|
||||
|
||||
Configure `frps` same as above.
|
||||
|
||||
@@ -329,24 +339,19 @@ Configure `frps` same as above.
|
||||
|
||||
### P2P Mode
|
||||
|
||||
**xtcp** is designed for transmitting large amounts of data directly between clients. A frps server is still needed, as P2P here only refers the actual data transmission.
|
||||
**xtcp** is designed to transmit large amounts of data directly between clients. A frps server is still needed, as P2P here only refers to the actual data transmission.
|
||||
|
||||
Note it can't penetrate all types of NAT devices. You might want to fallback to **stcp** if **xtcp** doesn't work.
|
||||
Note that it may not work with all types of NAT devices. You might want to fallback to stcp if xtcp doesn't work.
|
||||
|
||||
1. In `frps.ini` configure a UDP port for xtcp:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
bind_udp_port = 7001
|
||||
```
|
||||
|
||||
2. Start `frpc` on machine B, expose the SSH port. Note that `remote_port` field is removed:
|
||||
1. Start `frpc` on machine B, and expose the SSH port. Note that the `remote_port` field is removed:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
# set up a new stun server if the default one is not available.
|
||||
# nat_hole_stun_server = xxx
|
||||
|
||||
[p2p_ssh]
|
||||
type = xtcp
|
||||
@@ -355,13 +360,15 @@ Note it can't penetrate all types of NAT devices. You might want to fallback to
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
3. Start another `frpc` (typically on another machine C) with the config to connect to SSH using P2P mode:
|
||||
2. Start another `frpc` (typically on another machine C) with the configuration to connect to SSH using P2P mode:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
# set up a new stun server if the default one is not available.
|
||||
# nat_hole_stun_server = xxx
|
||||
|
||||
[p2p_ssh_visitor]
|
||||
type = xtcp
|
||||
@@ -370,9 +377,11 @@ Note it can't penetrate all types of NAT devices. You might want to fallback to
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 6000
|
||||
# when automatic tunnel persistence is required, set it to true
|
||||
keep_tunnel_open = false
|
||||
```
|
||||
|
||||
4. On machine C, connect to SSH on machine B, using this command:
|
||||
3. On machine C, connect to SSH on machine B, using this command:
|
||||
|
||||
`ssh -oPort=6000 127.0.0.1`
|
||||
|
||||
@@ -450,6 +459,21 @@ dashboard_pwd = admin
|
||||
|
||||
Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin`.
|
||||
|
||||
Additionally, you can use HTTPS port by using your domains wildcard or normal SSL certificate:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
# dashboard's username and password are both optional
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
dashboard_tls_mode = true
|
||||
dashboard_tls_cert_file = server.crt
|
||||
dashboard_tls_key_file = server.key
|
||||
```
|
||||
|
||||
Then visit `https://[server_addr]:7500` to see the dashboard in secure HTTPS connection, with username and password both being `admin`.
|
||||
|
||||

|
||||
|
||||
### Admin UI
|
||||
@@ -614,7 +638,7 @@ openssl req -new -sha256 -key server.key \
|
||||
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com")) \
|
||||
-out server.csr
|
||||
|
||||
openssl x509 -req -days 365 \
|
||||
openssl x509 -req -days 365 -sha256 \
|
||||
-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
|
||||
-extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com") \
|
||||
-out server.crt
|
||||
@@ -629,7 +653,7 @@ openssl req -new -sha256 -key client.key \
|
||||
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:client.com,DNS:example.client.com")) \
|
||||
-out client.csr
|
||||
|
||||
openssl x509 -req -days 365 \
|
||||
openssl x509 -req -days 365 -sha256 \
|
||||
-in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
|
||||
-extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com") \
|
||||
-out client.crt
|
||||
@@ -689,6 +713,8 @@ bandwidth_limit = 1MB
|
||||
|
||||
Set `bandwidth_limit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`.
|
||||
|
||||
Set `bandwidth_limit_mode` to `client` or `server` to limit bandwidth on the client or server side. Default is `client`.
|
||||
|
||||
### TCP Stream Multiplexing
|
||||
|
||||
frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.
|
||||
@@ -730,6 +756,35 @@ KCP mode uses UDP as the underlying transport. Using KCP in frp:
|
||||
protocol = kcp
|
||||
```
|
||||
|
||||
### Support QUIC Protocol
|
||||
|
||||
QUIC is a new multiplexed transport built on top of UDP.
|
||||
|
||||
Using QUIC in frp:
|
||||
|
||||
1. Enable QUIC in frps:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
# Specify a UDP port for QUIC.
|
||||
quic_bind_port = 7000
|
||||
```
|
||||
|
||||
The `quic_bind_port` number can be the same number as `bind_port`, since `bind_port` field specifies a TCP port.
|
||||
|
||||
2. Configure `frpc.ini` to use QUIC to connect to frps:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
# Same as the 'quic_bind_port' in frps.ini
|
||||
server_port = 7000
|
||||
protocol = quic
|
||||
```
|
||||
|
||||
### Connection Pooling
|
||||
|
||||
By default, frps creates a new frpc connection to the backend service upon a user request. With connection pooling, frps keeps a certain number of pre-established connections, reducing the time needed to establish a connection.
|
||||
@@ -841,7 +896,7 @@ custom_domains = test.example.com
|
||||
host_header_rewrite = dev.example.com
|
||||
```
|
||||
|
||||
The HTTP request will have the the `Host` header rewritten to `Host: dev.example.com` when it reaches the actual web server, although the request from the browser probably has `Host: test.example.com`.
|
||||
The HTTP request will have the `Host` header rewritten to `Host: dev.example.com` when it reaches the actual web server, although the request from the browser probably has `Host: test.example.com`.
|
||||
|
||||
### Setting other HTTP Headers
|
||||
|
||||
@@ -867,7 +922,7 @@ In this example, it will set header `X-From-Where: frp` in the HTTP request.
|
||||
|
||||
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
|
||||
|
||||
@@ -983,11 +1038,13 @@ server_port = 7000
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
custom_domains = test1
|
||||
local_port = 80
|
||||
|
||||
[proxy2]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
custom_domains = test2
|
||||
local_port = 8080
|
||||
```
|
||||
|
||||
In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:
|
||||
@@ -1073,15 +1130,11 @@ Interested in getting involved? We would like to help you!
|
||||
|
||||
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).
|
||||
|
||||

|
||||
|
||||
### Wechat Pay
|
||||
|
||||

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

|
||||
|
||||
### Paypal 捐赠
|
||||
|
||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
||||
|
20
Release.md
20
Release.md
@@ -1,5 +1,19 @@
|
||||
## Notes
|
||||
|
||||
We have thoroughly refactored xtcp in this version to improve its penetration rate and stability.
|
||||
|
||||
In this version, different penetration strategies can be attempted by retrying connections multiple times. Once a hole is successfully punched, the strategy will be recorded in the server cache for future reuse. When new users connect, the successfully penetrated tunnel can be reused instead of punching a new hole.
|
||||
|
||||
**Due to a significant refactor of xtcp, this version is not compatible with previous versions of xtcp.**
|
||||
|
||||
**To use features related to xtcp, both frpc and frps need to be updated to the latest version.**
|
||||
|
||||
### New
|
||||
|
||||
* The frpc has added the `nathole discover` command for testing the NAT type of the current network.
|
||||
* `XTCP` has been refactored, resulting in a significant improvement in the success rate of penetration.
|
||||
* When verifying passwords, use `subtle.ConstantTimeCompare` and introduce a certain delay when the password is incorrect.
|
||||
|
||||
### Fix
|
||||
|
||||
* Plugin `https2https` not work.
|
||||
* `context canceled` problem for `http_proxy` plugin when multiple requests reuse same connection.
|
||||
* In some cases, frps can't get server name for `https` proxy.
|
||||
* Fix the problem of lagging when opening multiple table entries in the frps dashboard.
|
||||
|
@@ -14,22 +14,15 @@
|
||||
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
)
|
||||
|
||||
var (
|
||||
// store static files in memory by statik
|
||||
// read-only filesystem created by "embed" for embedded files
|
||||
content fs.FS
|
||||
|
||||
FileSystem http.FileSystem
|
||||
|
||||
// if prefix is not empty, we get file content from disk
|
||||
@@ -38,40 +31,18 @@ var (
|
||||
|
||||
// if path is empty, load assets in memory
|
||||
// or set FileSystem using disk files
|
||||
func Load(path string) (err error) {
|
||||
func Load(path string) {
|
||||
prefixPath = path
|
||||
if prefixPath != "" {
|
||||
FileSystem = http.Dir(prefixPath)
|
||||
return nil
|
||||
} else {
|
||||
FileSystem, err = fs.New()
|
||||
FileSystem = http.FS(content)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadFile(file string) (content string, err error) {
|
||||
if prefixPath == "" {
|
||||
file, err := FileSystem.Open(path.Join("/", file))
|
||||
if err != nil {
|
||||
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)
|
||||
func Register(fileSystem fs.FS) {
|
||||
subFs, err := fs.Sub(fileSystem, "static")
|
||||
if err == nil {
|
||||
content = subFs
|
||||
}
|
||||
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)
|
||||
}
|
Binary file not shown.
Binary file not shown.
32
assets/frpc/static/index-1c7ed8b0.js
Normal file
32
assets/frpc/static/index-1c7ed8b0.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/frpc/static/index-1e2a7ce0.css
Normal file
1
assets/frpc/static/index-1e2a7ce0.css
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1,16 @@
|
||||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5d5774096cf5c1b4d5af"></script><script type="text/javascript" src="vendor.js?dc42700731a508d39009"></script></body> </html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frp client admin UI</title>
|
||||
<script type="module" crossorigin src="./index-1c7ed8b0.js"></script>
|
||||
<link rel="stylesheet" href="./index-1e2a7ce0.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@@ -1 +0,0 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"dc42700731a508d39009"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
14
assets/frps/embed.go
Normal file
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)
|
||||
}
|
Binary file not shown.
Binary file not shown.
1
assets/frps/static/index-1e0c7400.css
Normal file
1
assets/frps/static/index-1e0c7400.css
Normal file
File diff suppressed because one or more lines are too long
74
assets/frps/static/index-93e38bbf.js
Normal file
74
assets/frps/static/index-93e38bbf.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1,16 @@
|
||||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5d154ba4c6b342d8c0c3"></script><script type="text/javascript" src="vendor.js?ddbd1f69fb6e67be4b78"></script></body> </html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frps dashboard</title>
|
||||
<script type="module" crossorigin src="./index-93e38bbf.js"></script>
|
||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@@ -1 +0,0 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,u,c){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"ddbd1f69fb6e67be4b78"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -17,36 +17,49 @@ package client
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 10 * time.Second
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
httpServerReadTimeout = 60 * time.Second
|
||||
httpServerWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
router.HandleFunc("/healthz", svr.healthz)
|
||||
|
||||
// api, see dashboard_api.go
|
||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
// 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.AdminUser, svr.cfg.AdminPwd
|
||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).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
|
||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
@@ -64,6 +77,8 @@ func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
go server.Serve(ln)
|
||||
go func() {
|
||||
_ = server.Serve(ln)
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
@@ -17,11 +17,16 @@ package client
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
@@ -32,6 +37,11 @@ type GeneralResponse struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
// /healthz
|
||||
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
// GET api/reload
|
||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
@@ -41,7 +51,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info("api response [/api/reload], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -60,18 +70,9 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
log.Info("success reload conf")
|
||||
return
|
||||
}
|
||||
|
||||
type StatusResp struct {
|
||||
TCP []ProxyStatusResp `json:"tcp"`
|
||||
UDP []ProxyStatusResp `json:"udp"`
|
||||
HTTP []ProxyStatusResp `json:"http"`
|
||||
HTTPS []ProxyStatusResp `json:"https"`
|
||||
STCP []ProxyStatusResp `json:"stcp"`
|
||||
XTCP []ProxyStatusResp `json:"xtcp"`
|
||||
SUDP []ProxyStatusResp `json:"sudp"`
|
||||
}
|
||||
type StatusResp map[string][]ProxyStatusResp
|
||||
|
||||
type ProxyStatusResp struct {
|
||||
Name string `json:"name"`
|
||||
@@ -83,12 +84,6 @@ type ProxyStatusResp struct {
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
type ByProxyStatusResp []ProxyStatusResp
|
||||
|
||||
func (a ByProxyStatusResp) Len() int { return len(a) }
|
||||
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
|
||||
|
||||
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
@@ -96,53 +91,17 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
||||
Status: status.Phase,
|
||||
Err: status.Err,
|
||||
}
|
||||
switch cfg := status.Cfg.(type) {
|
||||
case *config.TCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.UDPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.HTTPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
baseCfg := status.Cfg.GetBaseInfo()
|
||||
if baseCfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = baseCfg.Plugin
|
||||
|
||||
if status.Err == "" {
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
case *config.HTTPSProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
if lo.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
case *config.STCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
case *config.XTCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
case *config.SUDPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
}
|
||||
return psr
|
||||
}
|
||||
@@ -151,50 +110,29 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res StatusResp
|
||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||
)
|
||||
res.TCP = make([]ProxyStatusResp, 0)
|
||||
res.UDP = make([]ProxyStatusResp, 0)
|
||||
res.HTTP = make([]ProxyStatusResp, 0)
|
||||
res.HTTPS = make([]ProxyStatusResp, 0)
|
||||
res.STCP = make([]ProxyStatusResp, 0)
|
||||
res.XTCP = make([]ProxyStatusResp, 0)
|
||||
res.SUDP = make([]ProxyStatusResp, 0)
|
||||
|
||||
log.Info("Http request [/api/status]")
|
||||
defer func() {
|
||||
log.Info("Http response [/api/status]")
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
_, _ = w.Write(buf)
|
||||
}()
|
||||
|
||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
switch status.Type {
|
||||
case "tcp":
|
||||
res.TCP = append(res.TCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "udp":
|
||||
res.UDP = append(res.UDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "http":
|
||||
res.HTTP = append(res.HTTP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "https":
|
||||
res.HTTPS = append(res.HTTPS, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "stcp":
|
||||
res.STCP = append(res.STCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "xtcp":
|
||||
res.XTCP = append(res.XTCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "sudp":
|
||||
res.SUDP = append(res.SUDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
|
||||
for _, arrs := range res {
|
||||
if len(arrs) <= 1 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(arrs, func(i, j int) bool {
|
||||
return strings.Compare(arrs[i].Name, arrs[j].Name) < 0
|
||||
})
|
||||
}
|
||||
sort.Sort(ByProxyStatusResp(res.TCP))
|
||||
sort.Sort(ByProxyStatusResp(res.UDP))
|
||||
sort.Sort(ByProxyStatusResp(res.HTTP))
|
||||
sort.Sort(ByProxyStatusResp(res.HTTPS))
|
||||
sort.Sort(ByProxyStatusResp(res.STCP))
|
||||
sort.Sort(ByProxyStatusResp(res.XTCP))
|
||||
sort.Sort(ByProxyStatusResp(res.SUDP))
|
||||
return
|
||||
}
|
||||
|
||||
// GET api/config
|
||||
@@ -206,7 +144,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -246,12 +184,12 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
// get new config content
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||
@@ -268,7 +206,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// get token from origin content
|
||||
token := ""
|
||||
b, err := ioutil.ReadFile(svr.cfgFile)
|
||||
b, err := os.ReadFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -307,7 +245,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
content = strings.Join(newRows, "\n")
|
||||
|
||||
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
|
||||
err = os.WriteFile(svr.cfgFile, []byte(content), 0o644)
|
||||
if err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
|
@@ -16,29 +16,30 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/client/visitor"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
type Control struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
// service context
|
||||
ctx context.Context
|
||||
xl *xlog.Logger
|
||||
|
||||
// Unique ID obtained from frps.
|
||||
// It should be attached to the login message when reconnecting.
|
||||
runID string
|
||||
|
||||
// manage all proxies
|
||||
@@ -46,13 +47,12 @@ type Control struct {
|
||||
pm *proxy.Manager
|
||||
|
||||
// manage all visitors
|
||||
vm *VisitorManager
|
||||
vm *visitor.Manager
|
||||
|
||||
// control connection
|
||||
conn net.Conn
|
||||
|
||||
// tcp stream multiplexing, if enabled
|
||||
session *fmux.Session
|
||||
cm *ConnectionManager
|
||||
|
||||
// put a message in this channel to send it over control connection to server
|
||||
sendCh chan (msg.Message)
|
||||
@@ -75,32 +75,26 @@ type Control struct {
|
||||
writerShutdown *shutdown.Shutdown
|
||||
msgHandlerShutdown *shutdown.Shutdown
|
||||
|
||||
// The UDP port that the server is listening on
|
||||
serverUDPPort int
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
xl *xlog.Logger
|
||||
|
||||
// service context
|
||||
ctx context.Context
|
||||
|
||||
// sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
msgTransporter transport.MessageTransporter
|
||||
}
|
||||
|
||||
func NewControl(ctx context.Context, runID string, conn net.Conn, session *fmux.Session,
|
||||
func NewControl(
|
||||
ctx context.Context, runID string, conn net.Conn, cm *ConnectionManager,
|
||||
clientCfg config.ClientCommonConf,
|
||||
pxyCfgs map[string]config.ProxyConf,
|
||||
visitorCfgs map[string]config.VisitorConf,
|
||||
serverUDPPort int,
|
||||
authSetter auth.Setter) *Control {
|
||||
|
||||
authSetter auth.Setter,
|
||||
) *Control {
|
||||
// new xlog instance
|
||||
ctl := &Control{
|
||||
ctx: ctx,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
runID: runID,
|
||||
conn: conn,
|
||||
session: session,
|
||||
cm: cm,
|
||||
pxyCfgs: pxyCfgs,
|
||||
sendCh: make(chan msg.Message, 100),
|
||||
readCh: make(chan msg.Message, 100),
|
||||
@@ -110,14 +104,12 @@ func NewControl(ctx context.Context, runID string, conn net.Conn, session *fmux.
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
msgHandlerShutdown: shutdown.New(),
|
||||
serverUDPPort: serverUDPPort,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
authSetter: authSetter,
|
||||
}
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter)
|
||||
|
||||
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
|
||||
ctl.vm = visitor.NewManager(ctl.ctx, ctl.clientCfg, ctl.connectServer, ctl.msgTransporter)
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
return ctl
|
||||
}
|
||||
@@ -130,13 +122,13 @@ func (ctl *Control) Run() {
|
||||
|
||||
// start all visitors
|
||||
go ctl.vm.Run()
|
||||
return
|
||||
}
|
||||
|
||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||
xl := ctl.xl
|
||||
workConn, err := ctl.connectServer()
|
||||
if err != nil {
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,7 +147,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||
|
||||
var startMsg msg.StartWorkConn
|
||||
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
||||
xl.Error("work connection closed before response StartWorkConn message: %v", err)
|
||||
xl.Trace("work connection closed before response StartWorkConn message: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
@@ -181,61 +173,39 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) Close() error {
|
||||
ctl.pm.Close()
|
||||
ctl.conn.Close()
|
||||
ctl.vm.Close()
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
func (ctl *Control) HandleNatHoleResp(inMsg *msg.NatHoleResp) {
|
||||
xl := ctl.xl
|
||||
|
||||
// Dispatch the NatHoleResp message to the related proxy.
|
||||
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||
if !ok {
|
||||
xl.Trace("dispatch NatHoleResp message to related proxy error")
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) Close() error {
|
||||
return ctl.GracefulClose(0)
|
||||
}
|
||||
|
||||
func (ctl *Control) GracefulClose(d time.Duration) error {
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
|
||||
time.Sleep(d)
|
||||
|
||||
ctl.conn.Close()
|
||||
ctl.cm.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosedDoneCh returns a channel which will be closed after all resources are released
|
||||
// ClosedDoneCh returns a channel that will be closed after all resources are released
|
||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||
return ctl.closedDoneCh
|
||||
}
|
||||
|
||||
// connectServer return a new connection to frps
|
||||
func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
xl := ctl.xl
|
||||
if ctl.clientCfg.TCPMux {
|
||||
stream, errRet := ctl.session.OpenStream()
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
}
|
||||
conn = stream
|
||||
} else {
|
||||
var tlsConfig *tls.Config
|
||||
sn := ctl.clientCfg.TLSServerName
|
||||
if sn == "" {
|
||||
sn = ctl.clientCfg.ServerAddr
|
||||
}
|
||||
|
||||
if ctl.clientCfg.TLSEnable {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
ctl.clientCfg.TLSCertFile,
|
||||
ctl.clientCfg.TLSKeyFile,
|
||||
ctl.clientCfg.TLSTrustedCaFile,
|
||||
sn)
|
||||
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration when connecting to server, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort))
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig)
|
||||
|
||||
if err != nil {
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
return ctl.cm.Connect()
|
||||
}
|
||||
|
||||
// reader read all messages from frps and send to readCh
|
||||
@@ -290,7 +260,7 @@ func (ctl *Control) writer() {
|
||||
}
|
||||
}
|
||||
|
||||
// msgHandler handles all channel events and do corresponding operations.
|
||||
// msgHandler handles all channel events and performs corresponding operations.
|
||||
func (ctl *Control) msgHandler() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
@@ -301,16 +271,27 @@ func (ctl *Control) msgHandler() {
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
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)
|
||||
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)
|
||||
defer hbCheck.Stop()
|
||||
hbCheckCh = hbCheck.C
|
||||
}
|
||||
|
||||
ctl.lastPong = time.Now()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-hbSend.C:
|
||||
case <-hbSendCh:
|
||||
// send heartbeat to server
|
||||
xl.Debug("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
@@ -319,7 +300,7 @@ func (ctl *Control) msgHandler() {
|
||||
return
|
||||
}
|
||||
ctl.sendCh <- pingMsg
|
||||
case <-hbCheck.C:
|
||||
case <-hbCheckCh:
|
||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
@@ -336,6 +317,8 @@ func (ctl *Control) msgHandler() {
|
||||
go ctl.HandleReqWorkConn(m)
|
||||
case *msg.NewProxyResp:
|
||||
ctl.HandleNewProxyResp(m)
|
||||
case *msg.NatHoleResp:
|
||||
ctl.HandleNatHoleResp(m)
|
||||
case *msg.Pong:
|
||||
if m.Error != "" {
|
||||
xl.Error("Pong contains error: %s", m.Error)
|
||||
@@ -355,25 +338,20 @@ func (ctl *Control) worker() {
|
||||
go ctl.reader()
|
||||
go ctl.writer()
|
||||
|
||||
select {
|
||||
case <-ctl.closedCh:
|
||||
// close related channels and wait until other goroutines done
|
||||
close(ctl.readCh)
|
||||
ctl.readerShutdown.WaitDone()
|
||||
ctl.msgHandlerShutdown.WaitDone()
|
||||
<-ctl.closedCh
|
||||
// close related channels and wait until other goroutines done
|
||||
close(ctl.readCh)
|
||||
ctl.readerShutdown.WaitDone()
|
||||
ctl.msgHandlerShutdown.WaitDone()
|
||||
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
|
||||
close(ctl.closedDoneCh)
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
close(ctl.closedDoneCh)
|
||||
ctl.cm.Close()
|
||||
}
|
||||
|
||||
func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
||||
|
@@ -6,18 +6,9 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
type Type int
|
||||
var ErrPayloadType = errors.New("error payload type")
|
||||
|
||||
const (
|
||||
EvStartProxy Type = iota
|
||||
EvCloseProxy
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPayloadType = errors.New("error payload type")
|
||||
)
|
||||
|
||||
type Handler func(evType Type, payload interface{}) error
|
||||
type Handler func(payload interface{}) error
|
||||
|
||||
type StartProxyPayload struct {
|
||||
NewProxyMsg *msg.NewProxy
|
||||
|
@@ -19,7 +19,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -27,9 +26,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHealthCheckType = errors.New("error health check type")
|
||||
)
|
||||
var ErrHealthCheckType = errors.New("error health check type")
|
||||
|
||||
type Monitor struct {
|
||||
checkType string
|
||||
@@ -55,8 +52,8 @@ type Monitor struct {
|
||||
func NewMonitor(ctx context.Context, checkType string,
|
||||
intervalS int, timeoutS int, maxFailedTimes int,
|
||||
addr string, url string,
|
||||
statusNormalFn func(), statusFailedFn func()) *Monitor {
|
||||
|
||||
statusNormalFn func(), statusFailedFn func(),
|
||||
) *Monitor {
|
||||
if intervalS <= 0 {
|
||||
intervalS = 10
|
||||
}
|
||||
@@ -153,7 +150,7 @@ func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
|
||||
req, err := http.NewRequest("GET", monitor.url, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", monitor.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -162,7 +159,7 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||
|
@@ -17,29 +17,24 @@ package proxy
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Proxy defines how to handle work connections for different proxy type.
|
||||
@@ -52,19 +47,24 @@ type Proxy interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
||||
func NewProxy(
|
||||
ctx context.Context,
|
||||
pxyConf config.ProxyConf,
|
||||
clientCfg config.ClientCommonConf,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) (pxy Proxy) {
|
||||
var limiter *rate.Limiter
|
||||
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
||||
if limitBytes > 0 {
|
||||
if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeClient {
|
||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||
}
|
||||
|
||||
baseProxy := BaseProxy{
|
||||
clientCfg: clientCfg,
|
||||
serverUDPPort: serverUDPPort,
|
||||
limiter: limiter,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
clientCfg: clientCfg,
|
||||
limiter: limiter,
|
||||
msgTransporter: msgTransporter,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TCPProxyConf:
|
||||
@@ -113,10 +113,10 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
closed bool
|
||||
clientCfg config.ClientCommonConf
|
||||
serverUDPPort int
|
||||
limiter *rate.Limiter
|
||||
closed bool
|
||||
clientCfg config.ClientCommonConf
|
||||
msgTransporter transport.MessageTransporter
|
||||
limiter *rate.Limiter
|
||||
|
||||
mu sync.RWMutex
|
||||
xl *xlog.Logger
|
||||
@@ -268,462 +268,10 @@ func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XTCPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.XTCPProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer conn.Close()
|
||||
var natHoleSidMsg msg.NatHoleSid
|
||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||
if err != nil {
|
||||
xl.Error("xtcp read from workConn error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
natHoleClientMsg := &msg.NatHoleClient{
|
||||
ProxyName: pxy.cfg.ProxyName,
|
||||
Sid: natHoleSidMsg.Sid,
|
||||
}
|
||||
raddr, _ := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
|
||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||
if err != nil {
|
||||
xl.Error("dial server udp addr error: %v", err)
|
||||
return
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||
if err != nil {
|
||||
xl.Error("send natHoleClientMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for client address at most 5 seconds.
|
||||
var natHoleRespMsg msg.NatHoleResp
|
||||
clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
buf := pool.GetBuf(1024)
|
||||
n, err := clientConn.Read(buf)
|
||||
if err != nil {
|
||||
xl.Error("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||
if err != nil {
|
||||
xl.Error("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
clientConn.SetReadDeadline(time.Time{})
|
||||
clientConn.Close()
|
||||
|
||||
if natHoleRespMsg.Error != "" {
|
||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Send detect message
|
||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
}
|
||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
if err != nil {
|
||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
return
|
||||
}
|
||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
xl.Trace("send all detect msg done")
|
||||
|
||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||
|
||||
// Listen for clientConn's address and wait for visitor connection
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
xl.Error("listen on visitorConn's local adress error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||
sidBuf := pool.GetBuf(1024)
|
||||
var uAddr *net.UDPAddr
|
||||
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||
if err != nil {
|
||||
xl.Warn("get sid from visitor error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Time{})
|
||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||
xl.Warn("incorrect sid from visitor")
|
||||
return
|
||||
}
|
||||
pool.PutBuf(sidBuf)
|
||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||
|
||||
kcpConn, err := frpNet.NewKCPConnFromUDP(lConn, false, uAddr.String())
|
||||
if err != nil {
|
||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||
if err != nil {
|
||||
xl.Error("create yamux server from kcp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Accept()
|
||||
if err != nil {
|
||||
xl.Error("accept for yamux connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
muxConn, []byte(pxy.cfg.Sk), m)
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//uConn := ipv4.NewConn(tConn)
|
||||
//uConn.SetTTL(3)
|
||||
|
||||
tConn.Write(content)
|
||||
tConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.UDPProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
readCh chan *msg.UDPPacket
|
||||
|
||||
// include msg.UDPPacket and msg.Ping
|
||||
sendCh chan msg.Message
|
||||
workConn net.Conn
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
|
||||
if !pxy.closed {
|
||||
pxy.closed = true
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
if pxy.readCh != nil {
|
||||
close(pxy.readCh)
|
||||
}
|
||||
if pxy.sendCh != nil {
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
pxy.sendCh = make(chan msg.Message, 1024)
|
||||
pxy.closed = false
|
||||
pxy.mu.Unlock()
|
||||
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
for {
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
if errRet := errors.PanicToError(func() {
|
||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
xl.Info("writer goroutine for udp work connection closed")
|
||||
}()
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.Ping:
|
||||
xl.Trace("send ping message to udp workConn")
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("udp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
var errRet error
|
||||
for {
|
||||
time.Sleep(time.Duration(30) * time.Second)
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
go heartbeatFn(pxy.workConn, pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
||||
|
||||
type SUDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.SUDPProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
return
|
||||
default:
|
||||
close(pxy.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
workConn := conn
|
||||
readCh := make(chan *msg.UDPPacket, 1024)
|
||||
sendCh := make(chan msg.Message, 1024)
|
||||
isClose := false
|
||||
|
||||
mu := &sync.Mutex{}
|
||||
|
||||
closeFn := func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if isClose {
|
||||
return
|
||||
}
|
||||
|
||||
isClose = true
|
||||
if workConn != nil {
|
||||
workConn.Close()
|
||||
}
|
||||
close(readCh)
|
||||
close(sendCh)
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
defer closeFn()
|
||||
|
||||
for {
|
||||
// first to check sudp proxy is closed or not
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
if errRet := errors.PanicToError(func() {
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
closeFn()
|
||||
xl.Info("writer goroutine for sudp work connection closed")
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
||||
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||
case *msg.Ping:
|
||||
xl.Trace("frpc send ping message to frpc visitor")
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("sudp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
closeFn()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
||||
return
|
||||
}
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(workConn, sendCh)
|
||||
go workConnReaderFn(workConn, readCh)
|
||||
go heartbeatFn(workConn, sendCh)
|
||||
|
||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn,
|
||||
) {
|
||||
xl := xlog.FromContextSafe(ctx)
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
@@ -778,7 +326,7 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
h.WriteTo(buf)
|
||||
_, _ = h.WriteTo(buf)
|
||||
extraInfo = buf.Bytes()
|
||||
}
|
||||
}
|
||||
@@ -791,7 +339,10 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
||||
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 {
|
||||
workConn.Close()
|
||||
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
||||
@@ -802,9 +353,16 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
|
||||
if len(extraInfo) > 0 {
|
||||
localConn.Write(extraInfo)
|
||||
if _, err := localConn.Write(extraInfo); err != nil {
|
||||
workConn.Close()
|
||||
xl.Error("write extraInfo to local conn error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, remote)
|
||||
_, _, errs := frpIo.Join(localConn, remote)
|
||||
xl.Debug("join connections closed")
|
||||
if len(errs) > 0 {
|
||||
xl.Trace("join connections errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,17 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
@@ -9,34 +23,33 @@ import (
|
||||
"github.com/fatedier/frp/client/event"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
sendCh chan (msg.Message)
|
||||
proxies map[string]*Wrapper
|
||||
proxies map[string]*Wrapper
|
||||
msgTransporter transport.MessageTransporter
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
clientCfg config.ClientCommonConf
|
||||
|
||||
// The UDP port that the server is listening on
|
||||
serverUDPPort int
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager {
|
||||
func NewManager(
|
||||
ctx context.Context,
|
||||
clientCfg config.ClientCommonConf,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) *Manager {
|
||||
return &Manager{
|
||||
sendCh: msgSendCh,
|
||||
proxies: make(map[string]*Wrapper),
|
||||
closed: false,
|
||||
clientCfg: clientCfg,
|
||||
serverUDPPort: serverUDPPort,
|
||||
ctx: ctx,
|
||||
proxies: make(map[string]*Wrapper),
|
||||
msgTransporter: msgTransporter,
|
||||
closed: false,
|
||||
clientCfg: clientCfg,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +88,7 @@ func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWo
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *Manager) HandleEvent(evType event.Type, payload interface{}) error {
|
||||
func (pm *Manager) HandleEvent(payload interface{}) error {
|
||||
var m msg.Message
|
||||
switch e := payload.(type) {
|
||||
case *event.StartProxyPayload:
|
||||
@@ -86,10 +99,7 @@ func (pm *Manager) HandleEvent(evType event.Type, payload interface{}) error {
|
||||
return event.ErrPayloadType
|
||||
}
|
||||
|
||||
err := errors.PanicToError(func() {
|
||||
pm.sendCh <- m
|
||||
})
|
||||
return err
|
||||
return pm.msgTransporter.Send(m)
|
||||
}
|
||||
|
||||
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||
@@ -113,10 +123,8 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
cfg, ok := pxyCfgs[name]
|
||||
if !ok {
|
||||
del = true
|
||||
} else {
|
||||
if !pxy.Cfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
} else if !pxy.Cfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
|
||||
if del {
|
||||
@@ -133,7 +141,7 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
addPxyNames := make([]string, 0)
|
||||
for name, cfg := range pxyCfgs {
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
|
||||
|
@@ -1,3 +1,17 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
@@ -8,13 +22,14 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
|
||||
"github.com/fatedier/frp/client/event"
|
||||
"github.com/fatedier/frp/client/health"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,9 +42,9 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
statusCheckInterval time.Duration = 3 * time.Second
|
||||
waitResponseTimeout = 20 * time.Second
|
||||
startErrTimeout = 30 * time.Second
|
||||
statusCheckInterval = 3 * time.Second
|
||||
waitResponseTimeout = 20 * time.Second
|
||||
startErrTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
type WorkingStatus struct {
|
||||
@@ -56,6 +71,8 @@ type Wrapper struct {
|
||||
// event handler
|
||||
handler event.Handler
|
||||
|
||||
msgTransporter transport.MessageTransporter
|
||||
|
||||
health uint32
|
||||
lastSendStartMsg time.Time
|
||||
lastStartErr time.Time
|
||||
@@ -67,7 +84,13 @@ type Wrapper struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper {
|
||||
func NewWrapper(
|
||||
ctx context.Context,
|
||||
cfg config.ProxyConf,
|
||||
clientCfg config.ClientCommonConf,
|
||||
eventHandler event.Handler,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) *Wrapper {
|
||||
baseInfo := cfg.GetBaseInfo()
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
||||
pw := &Wrapper{
|
||||
@@ -80,6 +103,7 @@ func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.Clie
|
||||
closeCh: make(chan struct{}),
|
||||
healthNotifyCh: make(chan struct{}),
|
||||
handler: eventHandler,
|
||||
msgTransporter: msgTransporter,
|
||||
xl: xl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
}
|
||||
@@ -92,7 +116,7 @@ func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.Clie
|
||||
xl.Trace("enable health check monitor")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter)
|
||||
return pw
|
||||
}
|
||||
|
||||
@@ -145,7 +169,7 @@ func (pw *Wrapper) Stop() {
|
||||
}
|
||||
|
||||
func (pw *Wrapper) close() {
|
||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||
_ = pw.handler(&event.CloseProxyPayload{
|
||||
CloseProxyMsg: &msg.CloseProxy{
|
||||
ProxyName: pw.Name,
|
||||
},
|
||||
@@ -174,7 +198,7 @@ func (pw *Wrapper) checkWorker() {
|
||||
var newProxyMsg msg.NewProxy
|
||||
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
||||
pw.lastSendStartMsg = now
|
||||
pw.handler(event.EvStartProxy, &event.StartProxyPayload{
|
||||
_ = pw.handler(&event.StartProxyPayload{
|
||||
NewProxyMsg: &newProxyMsg,
|
||||
})
|
||||
}
|
||||
@@ -201,7 +225,7 @@ func (pw *Wrapper) checkWorker() {
|
||||
func (pw *Wrapper) statusNormalCallback() {
|
||||
xl := pw.xl
|
||||
atomic.StoreUint32(&pw.health, 0)
|
||||
errors.PanicToError(func() {
|
||||
_ = errors.PanicToError(func() {
|
||||
select {
|
||||
case pw.healthNotifyCh <- struct{}{}:
|
||||
default:
|
||||
@@ -213,7 +237,7 @@ func (pw *Wrapper) statusNormalCallback() {
|
||||
func (pw *Wrapper) statusFailedCallback() {
|
||||
xl := pw.xl
|
||||
atomic.StoreUint32(&pw.health, 1)
|
||||
errors.PanicToError(func() {
|
||||
_ = errors.PanicToError(func() {
|
||||
select {
|
||||
case pw.healthNotifyCh <- struct{}{}:
|
||||
default:
|
||||
|
190
client/proxy/sudp.go
Normal file
190
client/proxy/sudp.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
type SUDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.SUDPProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
return
|
||||
default:
|
||||
close(pxy.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
workConn := conn
|
||||
readCh := make(chan *msg.UDPPacket, 1024)
|
||||
sendCh := make(chan msg.Message, 1024)
|
||||
isClose := false
|
||||
|
||||
mu := &sync.Mutex{}
|
||||
|
||||
closeFn := func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if isClose {
|
||||
return
|
||||
}
|
||||
|
||||
isClose = true
|
||||
if workConn != nil {
|
||||
workConn.Close()
|
||||
}
|
||||
close(readCh)
|
||||
close(sendCh)
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
defer closeFn()
|
||||
|
||||
for {
|
||||
// first to check sudp proxy is closed or not
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
if errRet := errors.PanicToError(func() {
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
closeFn()
|
||||
xl.Info("writer goroutine for sudp work connection closed")
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
||||
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||
case *msg.Ping:
|
||||
xl.Trace("frpc send ping message to frpc visitor")
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("sudp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heartbeatFn := func(sendCh chan msg.Message) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
closeFn()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
||||
return
|
||||
}
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(workConn, sendCh)
|
||||
go workConnReaderFn(workConn, readCh)
|
||||
go heartbeatFn(sendCh)
|
||||
|
||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
157
client/proxy/udp.go
Normal file
157
client/proxy/udp.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
// UDP
|
||||
type UDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.UDPProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
readCh chan *msg.UDPPacket
|
||||
|
||||
// include msg.UDPPacket and msg.Ping
|
||||
sendCh chan msg.Message
|
||||
workConn net.Conn
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
|
||||
if !pxy.closed {
|
||||
pxy.closed = true
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
if pxy.readCh != nil {
|
||||
close(pxy.readCh)
|
||||
}
|
||||
if pxy.sendCh != nil {
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
pxy.sendCh = make(chan msg.Message, 1024)
|
||||
pxy.closed = false
|
||||
pxy.mu.Unlock()
|
||||
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
for {
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
if errRet := errors.PanicToError(func() {
|
||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
xl.Info("writer goroutine for udp work connection closed")
|
||||
}()
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.Ping:
|
||||
xl.Trace("send ping message to udp workConn")
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("udp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
heartbeatFn := func(sendCh chan msg.Message) {
|
||||
var errRet error
|
||||
for {
|
||||
time.Sleep(time.Duration(30) * time.Second)
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
go heartbeatFn(pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
200
client/proxy/xtcp.go
Normal file
200
client/proxy/xtcp.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
"github.com/quic-go/quic-go"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
// XTCP
|
||||
type XTCPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.XTCPProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer conn.Close()
|
||||
var natHoleSidMsg msg.NatHoleSid
|
||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||
if err != nil {
|
||||
xl.Error("xtcp read from workConn error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||
if err != nil {
|
||||
xl.Warn("nathole prepare error: %v", err)
|
||||
return
|
||||
}
|
||||
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||
defer prepareResult.ListenConn.Close()
|
||||
|
||||
// send NatHoleClient msg to server
|
||||
transactionID := nathole.NewTransactionID()
|
||||
natHoleClientMsg := &msg.NatHoleClient{
|
||||
TransactionID: transactionID,
|
||||
ProxyName: pxy.cfg.ProxyName,
|
||||
Sid: natHoleSidMsg.Sid,
|
||||
MappedAddrs: prepareResult.Addrs,
|
||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||
}
|
||||
|
||||
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
||||
if err != nil {
|
||||
xl.Warn("nathole exchange info error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||
|
||||
listenConn := prepareResult.ListenConn
|
||||
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Sk))
|
||||
if err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("make hole error: %v", err)
|
||||
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||
Sid: natHoleRespMsg.Sid,
|
||||
Success: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
listenConn = newListenConn
|
||||
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||
|
||||
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||
Sid: natHoleRespMsg.Sid,
|
||||
Success: true,
|
||||
})
|
||||
|
||||
if natHoleRespMsg.Protocol == "kcp" {
|
||||
pxy.listenByKCP(listenConn, raddr, startWorkConnMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// default is quic
|
||||
pxy.listenByQUIC(listenConn, raddr, startWorkConnMsg)
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
listenConn.Close()
|
||||
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||
if err != nil {
|
||||
xl.Warn("dial udp error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
remote, err := frpNet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
if err != nil {
|
||||
xl.Warn("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err := fmux.Server(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
xl.Error("create mux session error: %v", err)
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
for {
|
||||
muxConn, err := session.Accept()
|
||||
if err != nil {
|
||||
xl.Error("accept connection error: %v", err)
|
||||
return
|
||||
}
|
||||
go HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
muxConn, []byte(pxy.cfg.Sk), startWorkConnMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer listenConn.Close()
|
||||
|
||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||
if err != nil {
|
||||
xl.Warn("create tls config error: %v", err)
|
||||
return
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
||||
&quic.Config{
|
||||
MaxIdleTimeout: time.Duration(pxy.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(pxy.clientCfg.QUICMaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(pxy.clientCfg.QUICKeepalivePeriod) * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
xl.Warn("dial quic error: %v", err)
|
||||
return
|
||||
}
|
||||
// only accept one connection from raddr
|
||||
c, err := quicListener.Accept(pxy.ctx)
|
||||
if err != nil {
|
||||
xl.Error("quic accept connection error: %v", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
stream, err := c.AcceptStream(pxy.ctx)
|
||||
if err != nil {
|
||||
xl.Debug("quic accept stream error: %v", err)
|
||||
_ = c.CloseWithError(0, "")
|
||||
return
|
||||
}
|
||||
go HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||
frpNet.QuicStreamToNetConn(stream, c), []byte(pxy.cfg.Sk), startWorkConnMsg)
|
||||
}
|
||||
}
|
@@ -17,16 +17,22 @@ package client
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
@@ -34,12 +40,17 @@ import (
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
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/xlog"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
// TODO: remove this when we drop support for go1.19
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
@@ -61,9 +72,6 @@ type Service struct {
|
||||
// string if no configuration file was used.
|
||||
cfgFile string
|
||||
|
||||
// This is configured by the login response from frps
|
||||
serverUDPPort int
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
|
||||
// service context
|
||||
@@ -72,8 +80,12 @@ type Service struct {
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
|
||||
|
||||
func NewService(
|
||||
cfg config.ClientCommonConf,
|
||||
pxyCfgs map[string]config.ProxyConf,
|
||||
visitorCfgs map[string]config.VisitorConf,
|
||||
cfgFile string,
|
||||
) (svr *Service, err error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
svr = &Service{
|
||||
authSetter: auth.NewAuthSetter(cfg.ClientConfig),
|
||||
@@ -97,9 +109,24 @@ func (svr *Service) GetController() *Control {
|
||||
func (svr *Service) Run() error {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
// set custom DNSServer
|
||||
if svr.cfg.DNSServer != "" {
|
||||
dnsAddr := svr.cfg.DNSServer
|
||||
if _, _, err := net.SplitHostPort(dnsAddr); err != nil {
|
||||
dnsAddr = net.JoinHostPort(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
|
||||
for {
|
||||
conn, session, err := svr.login()
|
||||
conn, cm, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("login to server failed: %v", err)
|
||||
|
||||
@@ -108,10 +135,10 @@ func (svr *Service) Run() error {
|
||||
if svr.cfg.LoginFailExit {
|
||||
return err
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
||||
} else {
|
||||
// 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, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
@@ -124,13 +151,10 @@ func (svr *Service) Run() error {
|
||||
|
||||
if svr.cfg.AdminPort != 0 {
|
||||
// Init admin server assets
|
||||
err := assets.Load(svr.cfg.AssetsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Load assets error: %v", err)
|
||||
}
|
||||
assets.Load(svr.cfg.AssetsDir)
|
||||
|
||||
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
|
||||
err = svr.RunAdminServer(address)
|
||||
err := svr.RunAdminServer(address)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
@@ -160,8 +184,11 @@ func (svr *Service) keepControllerWorking() {
|
||||
|
||||
// the first three retry with no delay
|
||||
if reconnectCounts > 3 {
|
||||
time.Sleep(reconnectDelay)
|
||||
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
||||
xl.Info("wait %v to reconnect", reconnectDelay)
|
||||
reconnectDelay *= 2
|
||||
} else {
|
||||
util.RandomSleep(time.Second, 0, 0.5)
|
||||
}
|
||||
reconnectCounts++
|
||||
|
||||
@@ -174,28 +201,26 @@ func (svr *Service) keepControllerWorking() {
|
||||
}
|
||||
|
||||
for {
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
opErr := &net.OpError{}
|
||||
// quick retry for dial error
|
||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
||||
delayTime = 2 * time.Second
|
||||
} else {
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, cm, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
|
||||
util.RandomSleep(delayTime, 0.9, 1.1)
|
||||
|
||||
delayTime *= 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
|
||||
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, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
if svr.ctl != nil {
|
||||
@@ -211,56 +236,23 @@ func (svr *Service) keepControllerWorking() {
|
||||
// login creates a connection to frps and registers it self as a client
|
||||
// conn: control connection
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
if svr.cfg.TLSEnable {
|
||||
sn := svr.cfg.TLSServerName
|
||||
if sn == "" {
|
||||
sn = svr.cfg.ServerAddr
|
||||
}
|
||||
cm = NewConnectionManager(svr.ctx, &svr.cfg)
|
||||
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
svr.cfg.TLSCertFile,
|
||||
svr.cfg.TLSKeyFile,
|
||||
svr.cfg.TLSTrustedCaFile,
|
||||
sn)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration when service login, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort))
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig)
|
||||
if err != nil {
|
||||
return
|
||||
if err = cm.OpenConnection(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
if session != nil {
|
||||
session.Close()
|
||||
}
|
||||
cm.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if svr.cfg.TCPMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
session, err = fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stream, errRet := session.OpenStream()
|
||||
if errRet != nil {
|
||||
session.Close()
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
conn = stream
|
||||
conn, err = cm.Connect()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loginMsg := &msg.Login{
|
||||
@@ -284,11 +276,11 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
}
|
||||
|
||||
var loginRespMsg msg.LoginResp
|
||||
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
@@ -300,8 +292,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
xl.ResetPrefixes()
|
||||
xl.AppendPrefix(svr.runID)
|
||||
|
||||
svr.serverUDPPort = loginRespMsg.ServerUDPPort
|
||||
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
|
||||
xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -311,13 +302,184 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
|
||||
svr.visitorCfgs = visitorCfgs
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl != nil {
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
svr.GracefulClose(time.Duration(0))
|
||||
}
|
||||
|
||||
func (svr *Service) GracefulClose(d time.Duration) {
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
|
||||
svr.ctlMu.RLock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.Close()
|
||||
svr.ctl.GracefulClose(d)
|
||||
}
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
svr.cancel()
|
||||
}
|
||||
|
||||
type ConnectionManager struct {
|
||||
ctx context.Context
|
||||
cfg *config.ClientCommonConf
|
||||
|
||||
muxSession *fmux.Session
|
||||
quicConn quic.Connection
|
||||
}
|
||||
|
||||
func NewConnectionManager(ctx context.Context, cfg *config.ClientCommonConf) *ConnectionManager {
|
||||
return &ConnectionManager{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) OpenConnection() error {
|
||||
xl := xlog.FromContextSafe(cm.ctx)
|
||||
|
||||
// special for quic
|
||||
if strings.EqualFold(cm.cfg.Protocol, "quic") {
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
sn := cm.cfg.TLSServerName
|
||||
if sn == "" {
|
||||
sn = cm.cfg.ServerAddr
|
||||
}
|
||||
if cm.cfg.TLSEnable {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
cm.cfg.TLSCertFile,
|
||||
cm.cfg.TLSKeyFile,
|
||||
cm.cfg.TLSTrustedCaFile,
|
||||
sn)
|
||||
} else {
|
||||
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||
}
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return err
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
|
||||
conn, err := quic.DialAddr(
|
||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||
tlsConfig, &quic.Config{
|
||||
MaxIdleTimeout: time.Duration(cm.cfg.QUICMaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(cm.cfg.QUICMaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(cm.cfg.QUICKeepalivePeriod) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.quicConn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
if !cm.cfg.TCPMux {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := cm.realConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.TCPMuxKeepaliveInterval) * time.Second
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err := fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.muxSession = session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) Connect() (net.Conn, error) {
|
||||
if cm.quicConn != nil {
|
||||
stream, err := cm.quicConn.OpenStreamSync(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return frpNet.QuicStreamToNetConn(stream, cm.quicConn), nil
|
||||
} else if cm.muxSession != nil {
|
||||
stream, err := cm.muxSession.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
return cm.realConnect()
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(cm.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
if cm.cfg.TLSEnable {
|
||||
sn := cm.cfg.TLSServerName
|
||||
if sn == "" {
|
||||
sn = cm.cfg.ServerAddr
|
||||
}
|
||||
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
cm.cfg.TLSCertFile,
|
||||
cm.cfg.TLSKeyFile,
|
||||
cm.cfg.TLSTrustedCaFile,
|
||||
sn)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.HTTPProxy)
|
||||
if err != nil {
|
||||
xl.Error("fail to parse proxy url")
|
||||
return nil, err
|
||||
}
|
||||
dialOptions := []libdial.DialOption{}
|
||||
protocol := cm.cfg.Protocol
|
||||
if protocol == "websocket" {
|
||||
protocol = "tcp"
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
||||
}
|
||||
if cm.cfg.ConnectServerLocalIP != "" {
|
||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.ConnectServerLocalIP))
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(cm.cfg.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(cm.cfg.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
libdial.WithTLSConfig(tlsConfig),
|
||||
libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, cm.cfg.DisableCustomTLSFirstByte),
|
||||
}),
|
||||
)
|
||||
conn, err := libdial.Dial(
|
||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||
dialOptions...,
|
||||
)
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) Close() error {
|
||||
if cm.quicConn != nil {
|
||||
_ = cm.quicConn.CloseWithError(0, "")
|
||||
}
|
||||
if cm.muxSession != nil {
|
||||
_ = cm.muxSession.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,554 +0,0 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
// Visitor is used for forward traffics from local port tot remote service.
|
||||
type Visitor interface {
|
||||
Run() error
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
||||
baseVisitor := BaseVisitor{
|
||||
ctl: ctl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
}
|
||||
switch cfg := cfg.(type) {
|
||||
case *config.STCPVisitorConf:
|
||||
visitor = &STCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XTCPVisitorConf:
|
||||
visitor = &XTCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.SUDPVisitorConf:
|
||||
visitor = &SUDPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
checkCloseCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseVisitor struct {
|
||||
ctl *Control
|
||||
l net.Listener
|
||||
closed bool
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type STCPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.STCPVisitorConf
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go sv.worker()
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Close() {
|
||||
sv.l.Close()
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("stcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
xl.Debug("get a new stcp user connection")
|
||||
visitorConn, err := sv.ctl.connectServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer visitorConn.Close()
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.UseEncryption,
|
||||
UseCompression: sv.cfg.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if sv.cfg.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, remote)
|
||||
}
|
||||
|
||||
type XTCPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.XTCPVisitorConf
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go sv.worker()
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Close() {
|
||||
sv.l.Close()
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("xtcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
xl.Debug("get a new xtcp user connection")
|
||||
if sv.ctl.serverUDPPort == 0 {
|
||||
xl.Error("xtcp is not supported by server")
|
||||
return
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
|
||||
if err != nil {
|
||||
xl.Error("resolve server UDP addr error")
|
||||
return
|
||||
}
|
||||
|
||||
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
||||
if err != nil {
|
||||
xl.Warn("dial server udp addr error: %v", err)
|
||||
return
|
||||
}
|
||||
defer visitorConn.Close()
|
||||
|
||||
now := time.Now().Unix()
|
||||
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
||||
if err != nil {
|
||||
xl.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for client address at most 10 seconds.
|
||||
var natHoleRespMsg msg.NatHoleResp
|
||||
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
buf := pool.GetBuf(1024)
|
||||
n, err := visitorConn.Read(buf)
|
||||
if err != nil {
|
||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||
if err != nil {
|
||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
pool.PutBuf(buf)
|
||||
|
||||
if natHoleRespMsg.Error != "" {
|
||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Close visitorConn, so we can use it's local address.
|
||||
visitorConn.Close()
|
||||
|
||||
// send sid message to client
|
||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||
if err != nil {
|
||||
xl.Error("resolve client udp address error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
xl.Error("dial client udp address error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||
|
||||
// read ack sid from client
|
||||
sidBuf := pool.GetBuf(1024)
|
||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||
n, err = lConn.Read(sidBuf)
|
||||
if err != nil {
|
||||
xl.Warn("get sid from client error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Time{})
|
||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||
xl.Warn("incorrect sid from client")
|
||||
return
|
||||
}
|
||||
pool.PutBuf(sidBuf)
|
||||
|
||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
// wrap kcp connection
|
||||
var remote io.ReadWriteCloser
|
||||
remote, err = frpNet.NewKCPConnFromUDP(lConn, true, natHoleRespMsg.ClientAddr)
|
||||
if err != nil {
|
||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Client(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
xl.Error("create yamux session error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Open()
|
||||
if err != nil {
|
||||
xl.Error("open yamux stream error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var muxConnRWCloser io.ReadWriteCloser = muxConn
|
||||
if sv.cfg.UseEncryption {
|
||||
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if sv.cfg.UseCompression {
|
||||
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, muxConnRWCloser)
|
||||
xl.Debug("join connections closed")
|
||||
}
|
||||
|
||||
type SUDPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
checkCloseCh chan struct{}
|
||||
// udpConn is the listener of udp packet
|
||||
udpConn *net.UDPConn
|
||||
readCh chan *msg.UDPPacket
|
||||
sendCh chan *msg.UDPPacket
|
||||
|
||||
cfg *config.SUDPVisitorConf
|
||||
}
|
||||
|
||||
// SUDP Run start listen a udp port
|
||||
func (sv *SUDPVisitor) Run() (err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||
}
|
||||
|
||||
sv.udpConn, err = net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
||||
}
|
||||
|
||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
|
||||
xl.Info("sudp start to work, listen on %s", addr)
|
||||
|
||||
go sv.dispatcher()
|
||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) dispatcher() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
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 {
|
||||
case <-sv.checkCloseCh:
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||
continue
|
||||
}
|
||||
|
||||
sv.worker(visitorConn)
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Debug("starting sudp proxy worker")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
closeCh := make(chan struct{})
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
close(closeCh)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
|
||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
xl.Debug("frpc visitor get ping message from frpc")
|
||||
continue
|
||||
case *msg.UDPPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
sv.readCh <- m
|
||||
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnSenderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-sv.sendCh:
|
||||
if !ok {
|
||||
xl.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn)
|
||||
|
||||
wg.Wait()
|
||||
xl.Info("sudp worker is closed")
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
visitorConn, err := sv.ctl.connectServer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.UseEncryption,
|
||||
UseCompression: sv.cfg.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
||||
}
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sv.cfg.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) Close() {
|
||||
sv.mu.Lock()
|
||||
defer sv.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
close(sv.checkCloseCh)
|
||||
}
|
||||
if sv.udpConn != nil {
|
||||
sv.udpConn.Close()
|
||||
}
|
||||
close(sv.readCh)
|
||||
close(sv.sendCh)
|
||||
}
|
118
client/visitor/stcp.go
Normal file
118
client/visitor/stcp.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type STCPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.STCPVisitorConf
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go sv.worker()
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Close() {
|
||||
sv.l.Close()
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("stcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
xl.Debug("get a new stcp user connection")
|
||||
visitorConn, err := sv.connectServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer visitorConn.Close()
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.UseEncryption,
|
||||
UseCompression: sv.cfg.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if sv.cfg.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, remote)
|
||||
}
|
262
client/visitor/sudp.go
Normal file
262
client/visitor/sudp.go
Normal file
@@ -0,0 +1,262 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type SUDPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
checkCloseCh chan struct{}
|
||||
// udpConn is the listener of udp packet
|
||||
udpConn *net.UDPConn
|
||||
readCh chan *msg.UDPPacket
|
||||
sendCh chan *msg.UDPPacket
|
||||
|
||||
cfg *config.SUDPVisitorConf
|
||||
}
|
||||
|
||||
// SUDP Run start listen a udp port
|
||||
func (sv *SUDPVisitor) Run() (err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||
}
|
||||
|
||||
sv.udpConn, err = net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
||||
}
|
||||
|
||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
|
||||
xl.Info("sudp start to work, listen on %s", addr)
|
||||
|
||||
go sv.dispatcher()
|
||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) dispatcher() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
var (
|
||||
visitorConn net.Conn
|
||||
err error
|
||||
|
||||
firstPacket *msg.UDPPacket
|
||||
)
|
||||
|
||||
for {
|
||||
select {
|
||||
case firstPacket = <-sv.sendCh:
|
||||
if firstPacket == nil {
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
}
|
||||
case <-sv.checkCloseCh:
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
}
|
||||
|
||||
visitorConn, err = sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// visitorConn always be closed when worker done.
|
||||
sv.worker(visitorConn, firstPacket)
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Debug("starting sudp proxy worker")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
closeCh := make(chan struct{})
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
close(closeCh)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
|
||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
xl.Debug("frpc visitor get ping message from frpc")
|
||||
continue
|
||||
case *msg.UDPPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
sv.readCh <- m
|
||||
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnSenderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
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 {
|
||||
select {
|
||||
case udpMsg, ok := <-sv.sendCh:
|
||||
if !ok {
|
||||
xl.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn)
|
||||
|
||||
wg.Wait()
|
||||
xl.Info("sudp worker is closed")
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
visitorConn, err := sv.connectServer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.UseEncryption,
|
||||
UseCompression: sv.cfg.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
||||
}
|
||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sv.cfg.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) Close() {
|
||||
sv.mu.Lock()
|
||||
defer sv.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
close(sv.checkCloseCh)
|
||||
}
|
||||
if sv.udpConn != nil {
|
||||
sv.udpConn.Close()
|
||||
}
|
||||
close(sv.readCh)
|
||||
close(sv.sendCh)
|
||||
}
|
77
client/visitor/visitor.go
Normal file
77
client/visitor/visitor.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
// Visitor is used for forward traffics from local port tot remote service.
|
||||
type Visitor interface {
|
||||
Run() error
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewVisitor(
|
||||
ctx context.Context,
|
||||
cfg config.VisitorConf,
|
||||
clientCfg config.ClientCommonConf,
|
||||
connectServer func() (net.Conn, error),
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) (visitor Visitor) {
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
||||
baseVisitor := BaseVisitor{
|
||||
clientCfg: clientCfg,
|
||||
connectServer: connectServer,
|
||||
msgTransporter: msgTransporter,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
}
|
||||
switch cfg := cfg.(type) {
|
||||
case *config.STCPVisitorConf:
|
||||
visitor = &STCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XTCPVisitorConf:
|
||||
visitor = &XTCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
startTunnelCh: make(chan struct{}),
|
||||
}
|
||||
case *config.SUDPVisitorConf:
|
||||
visitor = &SUDPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
checkCloseCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseVisitor struct {
|
||||
clientCfg config.ClientCommonConf
|
||||
connectServer func() (net.Conn, error)
|
||||
msgTransporter transport.MessageTransporter
|
||||
l net.Listener
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
@@ -12,22 +12,25 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type VisitorManager struct {
|
||||
ctl *Control
|
||||
|
||||
cfgs map[string]config.VisitorConf
|
||||
visitors map[string]Visitor
|
||||
type Manager struct {
|
||||
clientCfg config.ClientCommonConf
|
||||
connectServer func() (net.Conn, error)
|
||||
msgTransporter transport.MessageTransporter
|
||||
cfgs map[string]config.VisitorConf
|
||||
visitors map[string]Visitor
|
||||
|
||||
checkInterval time.Duration
|
||||
|
||||
@@ -37,18 +40,25 @@ type VisitorManager struct {
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
|
||||
return &VisitorManager{
|
||||
ctl: ctl,
|
||||
cfgs: make(map[string]config.VisitorConf),
|
||||
visitors: make(map[string]Visitor),
|
||||
checkInterval: 10 * time.Second,
|
||||
ctx: ctx,
|
||||
stopCh: make(chan struct{}),
|
||||
func NewManager(
|
||||
ctx context.Context,
|
||||
clientCfg config.ClientCommonConf,
|
||||
connectServer func() (net.Conn, error),
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) *Manager {
|
||||
return &Manager{
|
||||
clientCfg: clientCfg,
|
||||
connectServer: connectServer,
|
||||
msgTransporter: msgTransporter,
|
||||
cfgs: make(map[string]config.VisitorConf),
|
||||
visitors: make(map[string]Visitor),
|
||||
checkInterval: 10 * time.Second,
|
||||
ctx: ctx,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Run() {
|
||||
func (vm *Manager) Run() {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
|
||||
ticker := time.NewTicker(vm.checkInterval)
|
||||
@@ -65,7 +75,7 @@ func (vm *VisitorManager) Run() {
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
if _, exist := vm.visitors[name]; !exist {
|
||||
xl.Info("try to start visitor [%s]", name)
|
||||
vm.startVisitor(cfg)
|
||||
_ = vm.startVisitor(cfg)
|
||||
}
|
||||
}
|
||||
vm.mu.Unlock()
|
||||
@@ -74,10 +84,10 @@ func (vm *VisitorManager) Run() {
|
||||
}
|
||||
|
||||
// Hold lock before calling this function.
|
||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||
func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
|
||||
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.connectServer, vm.msgTransporter)
|
||||
err = visitor.Run()
|
||||
if err != nil {
|
||||
xl.Warn("start error: %v", err)
|
||||
@@ -88,7 +98,7 @@ func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
@@ -99,10 +109,8 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
cfg, ok := cfgs[name]
|
||||
if !ok {
|
||||
del = true
|
||||
} else {
|
||||
if !oldCfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
} else if !oldCfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
|
||||
if del {
|
||||
@@ -123,16 +131,15 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
if _, ok := vm.cfgs[name]; !ok {
|
||||
vm.cfgs[name] = cfg
|
||||
addNames = append(addNames, name)
|
||||
vm.startVisitor(cfg)
|
||||
_ = vm.startVisitor(cfg)
|
||||
}
|
||||
}
|
||||
if len(addNames) > 0 {
|
||||
xl.Info("visitor added: %v", addNames)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Close() {
|
||||
func (vm *Manager) Close() {
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
for _, v := range vm.visitors {
|
410
client/visitor/xtcp.go
Normal file
410
client/visitor/xtcp.go
Normal file
@@ -0,0 +1,410 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
var ErrNoTunnelSession = errors.New("no tunnel session")
|
||||
|
||||
type XTCPVisitor struct {
|
||||
*BaseVisitor
|
||||
session TunnelSession
|
||||
startTunnelCh chan struct{}
|
||||
retryLimiter *rate.Limiter
|
||||
cancel context.CancelFunc
|
||||
|
||||
cfg *config.XTCPVisitorConf
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Run() (err error) {
|
||||
sv.ctx, sv.cancel = context.WithCancel(sv.ctx)
|
||||
|
||||
if sv.cfg.Protocol == "kcp" {
|
||||
sv.session = NewKCPTunnelSession()
|
||||
} else {
|
||||
sv.session = NewQUICTunnelSession(&sv.clientCfg)
|
||||
}
|
||||
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go sv.worker()
|
||||
go sv.processTunnelStartEvents()
|
||||
if sv.cfg.KeepTunnelOpen {
|
||||
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
||||
go sv.keepTunnelOpenWorker()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Close() {
|
||||
sv.l.Close()
|
||||
sv.cancel()
|
||||
if sv.session != nil {
|
||||
sv.session.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("xtcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) processTunnelStartEvents() {
|
||||
for {
|
||||
select {
|
||||
case <-sv.ctx.Done():
|
||||
return
|
||||
case <-sv.startTunnelCh:
|
||||
start := time.Now()
|
||||
sv.makeNatHole()
|
||||
duration := time.Since(start)
|
||||
// avoid too frequently
|
||||
if duration < 10*time.Second {
|
||||
time.Sleep(10*time.Second - duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
ticker := time.NewTicker(time.Duration(sv.cfg.MinRetryInterval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
sv.startTunnelCh <- struct{}{}
|
||||
for {
|
||||
select {
|
||||
case <-sv.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
xl.Debug("keepTunnelOpenWorker try to check tunnel...")
|
||||
conn, err := sv.getTunnelConn()
|
||||
if err != nil {
|
||||
xl.Warn("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
||||
_ = sv.retryLimiter.Wait(sv.ctx)
|
||||
continue
|
||||
}
|
||||
xl.Debug("keepTunnelOpenWorker check success")
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
xl.Debug("get a new xtcp user connection")
|
||||
|
||||
// Open a tunnel connection to the server. If there is already a successful hole-punching connection,
|
||||
// it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout.
|
||||
tunnelConn, err := sv.openTunnel()
|
||||
if err != nil {
|
||||
xl.Error("open tunnel error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
||||
if sv.cfg.UseEncryption {
|
||||
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if sv.cfg.UseCompression {
|
||||
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
||||
}
|
||||
|
||||
_, _, errs := frpIo.Join(userConn, muxConnRWCloser)
|
||||
xl.Debug("join connections closed")
|
||||
if len(errs) > 0 {
|
||||
xl.Trace("join connections errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// openTunnel will open a tunnel connection to the target server.
|
||||
func (sv *XTCPVisitor) openTunnel() (conn net.Conn, err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
timeoutC := time.After(20 * time.Second)
|
||||
immediateTrigger := make(chan struct{}, 1)
|
||||
defer close(immediateTrigger)
|
||||
immediateTrigger <- struct{}{}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-sv.ctx.Done():
|
||||
return nil, sv.ctx.Err()
|
||||
case <-immediateTrigger:
|
||||
conn, err = sv.getTunnelConn()
|
||||
case <-ticker.C:
|
||||
conn, err = sv.getTunnelConn()
|
||||
case <-timeoutC:
|
||||
return nil, fmt.Errorf("open tunnel timeout")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err != ErrNoTunnelSession {
|
||||
xl.Warn("get tunnel connection error: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
|
||||
conn, err := sv.session.OpenConn(sv.ctx)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
sv.session.Close()
|
||||
|
||||
select {
|
||||
case sv.startTunnelCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 0. PreCheck
|
||||
// 1. Prepare
|
||||
// 2. ExchangeInfo
|
||||
// 3. MakeNATHole
|
||||
// 4. Create a tunnel session using an underlying UDP connection.
|
||||
func (sv *XTCPVisitor) makeNatHole() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
if err := nathole.PreCheck(sv.ctx, sv.msgTransporter, sv.cfg.ServerName, 5*time.Second); err != nil {
|
||||
xl.Warn("nathole precheck error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||
if err != nil {
|
||||
xl.Warn("nathole prepare error: %v", err)
|
||||
return
|
||||
}
|
||||
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||
|
||||
listenConn := prepareResult.ListenConn
|
||||
|
||||
// send NatHoleVisitor to server
|
||||
now := time.Now().Unix()
|
||||
transactionID := nathole.NewTransactionID()
|
||||
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||
TransactionID: transactionID,
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
Protocol: sv.cfg.Protocol,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
MappedAddrs: prepareResult.Addrs,
|
||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||
}
|
||||
|
||||
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.msgTransporter, transactionID, natHoleVisitorMsg, 5*time.Second)
|
||||
if err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("nathole exchange info error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||
|
||||
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("make hole error: %v", err)
|
||||
return
|
||||
}
|
||||
listenConn = newListenConn
|
||||
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||
|
||||
if err := sv.session.Init(listenConn, raddr); err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("init tunnel session error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type TunnelSession interface {
|
||||
Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error
|
||||
OpenConn(context.Context) (net.Conn, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
type KCPTunnelSession struct {
|
||||
session *fmux.Session
|
||||
lConn *net.UDPConn
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewKCPTunnelSession() TunnelSession {
|
||||
return &KCPTunnelSession{}
|
||||
}
|
||||
|
||||
func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||
listenConn.Close()
|
||||
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial udp error: %v", err)
|
||||
}
|
||||
remote, err := frpNet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create kcp connection from udp connection error: %v", err)
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err := fmux.Client(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
remote.Close()
|
||||
return fmt.Errorf("initial client session error: %v", err)
|
||||
}
|
||||
ks.mu.Lock()
|
||||
ks.session = session
|
||||
ks.lConn = lConn
|
||||
ks.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *KCPTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||
ks.mu.RLock()
|
||||
defer ks.mu.RUnlock()
|
||||
session := ks.session
|
||||
if session == nil {
|
||||
return nil, ErrNoTunnelSession
|
||||
}
|
||||
return session.Open()
|
||||
}
|
||||
|
||||
func (ks *KCPTunnelSession) Close() {
|
||||
ks.mu.Lock()
|
||||
defer ks.mu.Unlock()
|
||||
if ks.session != nil {
|
||||
_ = ks.session.Close()
|
||||
ks.session = nil
|
||||
}
|
||||
if ks.lConn != nil {
|
||||
_ = ks.lConn.Close()
|
||||
ks.lConn = nil
|
||||
}
|
||||
}
|
||||
|
||||
type QUICTunnelSession struct {
|
||||
session quic.Connection
|
||||
listenConn *net.UDPConn
|
||||
mu sync.RWMutex
|
||||
|
||||
clientCfg *config.ClientCommonConf
|
||||
}
|
||||
|
||||
func NewQUICTunnelSession(clientCfg *config.ClientCommonConf) TunnelSession {
|
||||
return &QUICTunnelSession{
|
||||
clientCfg: clientCfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||
tlsConfig, err := transport.NewClientTLSConfig("", "", "", raddr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create tls config error: %v", err)
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
quicConn, err := quic.Dial(listenConn, raddr, raddr.String(), tlsConfig,
|
||||
&quic.Config{
|
||||
MaxIdleTimeout: time.Duration(qs.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(qs.clientCfg.QUICMaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(qs.clientCfg.QUICKeepalivePeriod) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial quic error: %v", err)
|
||||
}
|
||||
qs.mu.Lock()
|
||||
qs.session = quicConn
|
||||
qs.listenConn = listenConn
|
||||
qs.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||
qs.mu.RLock()
|
||||
defer qs.mu.RUnlock()
|
||||
session := qs.session
|
||||
if session == nil {
|
||||
return nil, ErrNoTunnelSession
|
||||
}
|
||||
stream, err := session.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return frpNet.QuicStreamToNetConn(stream, session), nil
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) Close() {
|
||||
qs.mu.Lock()
|
||||
defer qs.mu.Unlock()
|
||||
if qs.session != nil {
|
||||
_ = qs.session.CloseWithError(0, "")
|
||||
qs.session = nil
|
||||
}
|
||||
if qs.listenConn != nil {
|
||||
_ = qs.listenConn.Close()
|
||||
qs.listenConn = nil
|
||||
}
|
||||
}
|
@@ -15,18 +15,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||
_ "github.com/fatedier/frp/assets/frpc"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
sub.Execute()
|
||||
}
|
||||
|
@@ -19,10 +19,10 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -39,6 +39,8 @@ func init() {
|
||||
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
|
||||
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
httpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
httpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(httpCmd)
|
||||
}
|
||||
@@ -70,6 +72,12 @@ var httpCmd = &cobra.Command{
|
||||
cfg.HostHeaderRewrite = hostHeaderRewrite
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
|
@@ -35,6 +35,8 @@ func init() {
|
||||
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(httpsCmd)
|
||||
}
|
||||
@@ -62,6 +64,12 @@ var httpsCmd = &cobra.Command{
|
||||
cfg.SubDomain = subDomain
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
|
97
cmd/frpc/sub/nathole.go
Normal file
97
cmd/frpc/sub/nathole.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
)
|
||||
|
||||
var (
|
||||
natHoleSTUNServer string
|
||||
natHoleLocalAddr string
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCommonFlags(natholeCmd)
|
||||
|
||||
rootCmd.AddCommand(natholeCmd)
|
||||
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
||||
|
||||
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "", "STUN server address for nathole")
|
||||
natholeCmd.PersistentFlags().StringVarP(&natHoleLocalAddr, "nat_hole_local_addr", "l", "", "local address to connect STUN server")
|
||||
}
|
||||
|
||||
var natholeCmd = &cobra.Command{
|
||||
Use: "nathole",
|
||||
Short: "Actions about nathole",
|
||||
}
|
||||
|
||||
var natholeDiscoveryCmd = &cobra.Command{
|
||||
Use: "discover",
|
||||
Short: "Discover nathole information from stun server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// ignore error here, because we can use command line pameters
|
||||
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
cfg = config.GetDefaultClientConf()
|
||||
}
|
||||
if natHoleSTUNServer != "" {
|
||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||
}
|
||||
|
||||
if err := validateForNatHoleDiscovery(cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addrs, localAddr, err := nathole.Discover([]string{cfg.NatHoleSTUNServer}, natHoleLocalAddr)
|
||||
if err != nil {
|
||||
fmt.Println("discover error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(addrs) < 2 {
|
||||
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addrs)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
localIPs, _ := nathole.ListLocalIPsForNatHole(10)
|
||||
|
||||
natFeature, err := nathole.ClassifyNATFeature(addrs, localIPs)
|
||||
if err != nil {
|
||||
fmt.Println("classify nat feature error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("STUN server:", cfg.NatHoleSTUNServer)
|
||||
fmt.Println("Your NAT type is:", natFeature.NatType)
|
||||
fmt.Println("Behavior is:", natFeature.Behavior)
|
||||
fmt.Println("External address is:", addrs)
|
||||
fmt.Println("Local address is:", localAddr.String())
|
||||
fmt.Println("Public Network:", natFeature.PublicNetwork)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func validateForNatHoleDiscovery(cfg config.ClientCommonConf) error {
|
||||
if cfg.NatHoleSTUNServer == "" {
|
||||
return fmt.Errorf("nat_hole_stun_server can not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -17,14 +17,14 @@ package sub
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -76,7 +76,7 @@ func reload(clientCfg config.ClientCommonConf) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -15,23 +15,24 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,6 +42,7 @@ const (
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
showVersion bool
|
||||
|
||||
serverAddr string
|
||||
@@ -51,36 +53,36 @@ var (
|
||||
logFile string
|
||||
logMaxDays int
|
||||
disableLogColor bool
|
||||
dnsServer string
|
||||
|
||||
proxyName string
|
||||
localIP string
|
||||
localPort int
|
||||
remotePort int
|
||||
useEncryption bool
|
||||
useCompression bool
|
||||
customDomains string
|
||||
subDomain string
|
||||
httpUser string
|
||||
httpPwd string
|
||||
locations string
|
||||
hostHeaderRewrite string
|
||||
role string
|
||||
sk string
|
||||
multiplexer string
|
||||
serverName string
|
||||
bindAddr string
|
||||
bindPort int
|
||||
proxyName string
|
||||
localIP string
|
||||
localPort int
|
||||
remotePort int
|
||||
useEncryption bool
|
||||
useCompression bool
|
||||
bandwidthLimit string
|
||||
bandwidthLimitMode string
|
||||
customDomains string
|
||||
subDomain string
|
||||
httpUser string
|
||||
httpPwd string
|
||||
locations string
|
||||
hostHeaderRewrite string
|
||||
role string
|
||||
sk string
|
||||
multiplexer string
|
||||
serverName string
|
||||
bindAddr string
|
||||
bindPort int
|
||||
|
||||
tlsEnable bool
|
||||
|
||||
kcpDoneCh chan struct{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
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")
|
||||
|
||||
kcpDoneCh = make(chan struct{})
|
||||
}
|
||||
|
||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
||||
@@ -93,6 +95,7 @@ func RegisterCommonFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
|
||||
cmd.PersistentFlags().StringVarP(&dnsServer, "dns_server", "", "", "specify dns server instead of using system default one")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -104,6 +107,13 @@ var rootCmd = &cobra.Command{
|
||||
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 != "" {
|
||||
_ = runMultipleClients(cfgDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not show command usage here.
|
||||
err := runClient(cfgFile)
|
||||
if err != nil {
|
||||
@@ -114,19 +124,39 @@ var rootCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runMultipleClients(cfgDir string) error {
|
||||
var wg sync.WaitGroup
|
||||
err := filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || 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 err
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSignal(svr *client.Service) {
|
||||
ch := make(chan os.Signal)
|
||||
func handleSignal(svr *client.Service, doneCh chan struct{}) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
svr.Close()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
close(kcpDoneCh)
|
||||
svr.GracefulClose(500 * time.Millisecond)
|
||||
close(doneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
@@ -151,6 +181,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
cfg.LogFile = logFile
|
||||
cfg.LogMaxDays = int64(logMaxDays)
|
||||
cfg.DisableLogColor = disableLogColor
|
||||
cfg.DNSServer = dnsServer
|
||||
|
||||
// Only token authentication is supported in cmd mode
|
||||
cfg.ClientConfig = auth.GetDefaultClientConf()
|
||||
@@ -159,7 +190,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
|
||||
cfg.Complete()
|
||||
if err = cfg.Validate(); err != nil {
|
||||
err = fmt.Errorf("Parse config error: %v", err)
|
||||
err = fmt.Errorf("parse config error: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -179,22 +210,12 @@ func startService(
|
||||
visitorCfgs map[string]config.VisitorConf,
|
||||
cfgFile string,
|
||||
) (err error) {
|
||||
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
|
||||
if cfg.DNSServer != "" {
|
||||
s := cfg.DNSServer
|
||||
if !strings.Contains(s, ":") {
|
||||
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)
|
||||
},
|
||||
}
|
||||
if cfgFile != "" {
|
||||
log.Trace("start frpc service for config file [%s]", cfgFile)
|
||||
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
|
||||
}
|
||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if errRet != nil {
|
||||
@@ -202,14 +223,16 @@ func startService(
|
||||
return
|
||||
}
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if cfg.Protocol == "kcp" {
|
||||
go handleSignal(svr)
|
||||
closedDoneCh := make(chan struct{})
|
||||
shouldGracefulClose := cfg.Protocol == "kcp" || cfg.Protocol == "quic"
|
||||
// Capture the exit signal if we use kcp or quic.
|
||||
if shouldGracefulClose {
|
||||
go handleSignal(svr, closedDoneCh)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
if err == nil && cfg.Protocol == "kcp" {
|
||||
<-kcpDoneCh
|
||||
if err == nil && shouldGracefulClose {
|
||||
<-closedDoneCh
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -18,16 +18,16 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -77,71 +77,31 @@ func status(clientCfg config.ClientCommonConf) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
res := make(client.StatusResp)
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.TCP) > 0 {
|
||||
fmt.Println("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.TCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
types := []string{"tcp", "udp", "tcpmux", "http", "https", "stcp", "sudp", "xtcp"}
|
||||
for _, pxyType := range types {
|
||||
arrs := res[pxyType]
|
||||
if len(arrs) == 0 {
|
||||
continue
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.UDP) > 0 {
|
||||
fmt.Println("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.UDP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.HTTP) > 0 {
|
||||
fmt.Println("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.HTTP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.HTTPS) > 0 {
|
||||
fmt.Println("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.HTTPS {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.STCP) > 0 {
|
||||
fmt.Println("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.STCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.XTCP) > 0 {
|
||||
fmt.Println("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.XTCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
fmt.Println(strings.ToUpper(pxyType))
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range arrs {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -18,10 +18,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,6 +37,8 @@ func init() {
|
||||
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(stcpCmd)
|
||||
}
|
||||
@@ -59,7 +61,8 @@ var stcpCmd = &cobra.Command{
|
||||
prefix = user + "."
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
switch role {
|
||||
case "server":
|
||||
cfg := &config.STCPProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.STCPProxy
|
||||
@@ -69,13 +72,19 @@ var stcpCmd = &cobra.Command{
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
case "visitor":
|
||||
cfg := &config.STCPVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.STCPProxy
|
||||
@@ -92,7 +101,7 @@ var stcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
visitorConfs[cfg.ProxyName] = cfg
|
||||
} else {
|
||||
default:
|
||||
fmt.Println("invalid role")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@@ -18,10 +18,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,6 +37,8 @@ func init() {
|
||||
sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(sudpCmd)
|
||||
}
|
||||
@@ -59,7 +61,8 @@ var sudpCmd = &cobra.Command{
|
||||
prefix = user + "."
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
switch role {
|
||||
case "server":
|
||||
cfg := &config.SUDPProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.SUDPProxy
|
||||
@@ -69,13 +72,19 @@ var sudpCmd = &cobra.Command{
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
case "visitor":
|
||||
cfg := &config.SUDPVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.SUDPProxy
|
||||
@@ -92,7 +101,7 @@ var sudpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
visitorConfs[cfg.ProxyName] = cfg
|
||||
} else {
|
||||
default:
|
||||
fmt.Println("invalid role")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@@ -33,6 +33,8 @@ func init() {
|
||||
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(tcpCmd)
|
||||
}
|
||||
@@ -59,6 +61,12 @@ var tcpCmd = &cobra.Command{
|
||||
cfg.RemotePort = remotePort
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
|
@@ -36,6 +36,8 @@ func init() {
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
|
||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(tcpMuxCmd)
|
||||
}
|
||||
@@ -64,6 +66,12 @@ var tcpMuxCmd = &cobra.Command{
|
||||
cfg.Multiplexer = multiplexer
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
|
@@ -18,10 +18,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -33,6 +33,8 @@ func init() {
|
||||
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
udpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
udpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(udpCmd)
|
||||
}
|
||||
@@ -59,6 +61,12 @@ var udpCmd = &cobra.Command{
|
||||
cfg.RemotePort = remotePort
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
|
@@ -18,9 +18,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@@ -18,10 +18,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,6 +37,8 @@ func init() {
|
||||
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
|
||||
rootCmd.AddCommand(xtcpCmd)
|
||||
}
|
||||
@@ -59,7 +61,8 @@ var xtcpCmd = &cobra.Command{
|
||||
prefix = user + "."
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
switch role {
|
||||
case "server":
|
||||
cfg := &config.XTCPProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.XTCPProxy
|
||||
@@ -69,13 +72,19 @@ var xtcpCmd = &cobra.Command{
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
case "visitor":
|
||||
cfg := &config.XTCPVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.XTCPProxy
|
||||
@@ -92,7 +101,7 @@ var xtcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
visitorConfs[cfg.ProxyName] = cfg
|
||||
} else {
|
||||
default:
|
||||
fmt.Println("invalid role")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@@ -20,12 +20,13 @@ import (
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||
_ "github.com/fatedier/frp/assets/frps"
|
||||
_ "github.com/fatedier/frp/pkg/metrics"
|
||||
)
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
// TODO: remove this when we drop support for go1.19
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
Execute()
|
||||
|
@@ -18,14 +18,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/server"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,31 +37,30 @@ var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
|
||||
bindAddr string
|
||||
bindPort int
|
||||
bindUDPPort int
|
||||
kcpBindPort int
|
||||
proxyBindAddr string
|
||||
vhostHTTPPort int
|
||||
vhostHTTPSPort int
|
||||
vhostHTTPTimeout int64
|
||||
dashboardAddr string
|
||||
dashboardPort int
|
||||
dashboardUser string
|
||||
dashboardPwd string
|
||||
enablePrometheus bool
|
||||
assetsDir string
|
||||
logFile string
|
||||
logLevel string
|
||||
logMaxDays int64
|
||||
disableLogColor bool
|
||||
token string
|
||||
subDomainHost string
|
||||
tcpMux bool
|
||||
allowPorts string
|
||||
maxPoolCount int64
|
||||
maxPortsPerClient int64
|
||||
tlsOnly bool
|
||||
bindAddr string
|
||||
bindPort int
|
||||
kcpBindPort int
|
||||
proxyBindAddr string
|
||||
vhostHTTPPort int
|
||||
vhostHTTPSPort int
|
||||
vhostHTTPTimeout int64
|
||||
dashboardAddr string
|
||||
dashboardPort int
|
||||
dashboardUser string
|
||||
dashboardPwd string
|
||||
enablePrometheus bool
|
||||
logFile string
|
||||
logLevel string
|
||||
logMaxDays int64
|
||||
disableLogColor bool
|
||||
token string
|
||||
subDomainHost string
|
||||
allowPorts string
|
||||
maxPortsPerClient int64
|
||||
tlsOnly bool
|
||||
dashboardTLSMode bool
|
||||
dashboardTLSCertFile string
|
||||
dashboardTLSKeyFile string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -70,13 +69,12 @@ func init() {
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindUDPPort, "bind_udp_port", "", 0, "bind udp port")
|
||||
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
||||
rootCmd.PersistentFlags().Int64VarP(&vhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dashboard address")
|
||||
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
@@ -91,6 +89,9 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
|
||||
rootCmd.PersistentFlags().BoolVarP(&tlsOnly, "tls_only", "", false, "frps tls only")
|
||||
rootCmd.PersistentFlags().BoolVarP(&dashboardTLSMode, "dashboard_tls_mode", "", false, "dashboard tls mode")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardTLSCertFile, "dashboard_tls_cert_file", "", "", "dashboard tls cert file")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardTLSKeyFile, "dashboard_tls_key_file", "", "", "dashboard tls key file")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -145,7 +146,7 @@ func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonC
|
||||
cfg.Complete()
|
||||
err = cfg.Validate()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse config error: %v", err)
|
||||
err = fmt.Errorf("parse config error: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -156,7 +157,6 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.BindPort = bindPort
|
||||
cfg.BindUDPPort = bindUDPPort
|
||||
cfg.KCPBindPort = kcpBindPort
|
||||
cfg.ProxyBindAddr = proxyBindAddr
|
||||
cfg.VhostHTTPPort = vhostHTTPPort
|
||||
@@ -167,6 +167,9 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||
cfg.DashboardUser = dashboardUser
|
||||
cfg.DashboardPwd = dashboardPwd
|
||||
cfg.EnablePrometheus = enablePrometheus
|
||||
cfg.DashboardTLSCertFile = dashboardTLSCertFile
|
||||
cfg.DashboardTLSKeyFile = dashboardTLSKeyFile
|
||||
cfg.DashboardTLSMode = dashboardTLSMode
|
||||
cfg.LogFile = logFile
|
||||
cfg.LogLevel = logLevel
|
||||
cfg.LogMaxDays = logMaxDays
|
||||
@@ -180,7 +183,7 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||
err = fmt.Errorf("parse conf error: allow_ports: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -18,9 +18,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@@ -6,6 +6,16 @@
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# STUN server to help penetrate NAT hole.
|
||||
# nat_hole_stun_server = stun.easyvoip.com:3478
|
||||
|
||||
# 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
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
@@ -33,6 +43,8 @@ authenticate_new_work_conns = false
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
authentication_method =
|
||||
|
||||
# oidc_client_id specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||
# By default, this value is "".
|
||||
oidc_client_id =
|
||||
@@ -44,10 +56,19 @@ oidc_client_secret =
|
||||
# oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_audience =
|
||||
|
||||
# oidc_scope specifies the permisssions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_scope =
|
||||
|
||||
# oidc_token_endpoint_url specifies the URL which implements OIDC Token Endpoint.
|
||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
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
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
@@ -60,7 +81,11 @@ admin_pwd = admin
|
||||
pool_count = 5
|
||||
|
||||
# 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}
|
||||
user = your_name
|
||||
@@ -70,9 +95,18 @@ user = your_name
|
||||
login_fail_exit = true
|
||||
|
||||
# communication protocol used to connect to server
|
||||
# now it supports tcp, kcp and websocket, default is tcp
|
||||
# supports tcp, kcp, quic and websocket now, default is 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
|
||||
|
||||
# quic protocol options
|
||||
# quic_keepalive_period = 10
|
||||
# quic_max_idle_timeout = 30
|
||||
# quic_max_incoming_streams = 100000
|
||||
|
||||
# if tls_enable is true, frpc will connect frps by tls
|
||||
tls_enable = true
|
||||
|
||||
@@ -84,12 +118,13 @@ tls_enable = true
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# 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
|
||||
# start = ssh,dns
|
||||
|
||||
# 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_timeout = 90
|
||||
|
||||
@@ -105,6 +140,14 @@ udp_packet_size = 1500
|
||||
# include other config files for proxies.
|
||||
# includes = ./confd/*.ini
|
||||
|
||||
# By default, frpc will connect frps with first custom byte if tls is enabled.
|
||||
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
|
||||
disable_custom_tls_first_byte = false
|
||||
|
||||
# Enable golang pprof handlers in admin listener.
|
||||
# Admin port must be set first.
|
||||
pprof_enable = false
|
||||
|
||||
# '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'
|
||||
[ssh]
|
||||
@@ -114,6 +157,8 @@ local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# limit bandwidth for this proxy, unit is KB and MB
|
||||
bandwidth_limit = 1MB
|
||||
# where to limit bandwidth, can be 'client' or 'server', default is 'client'
|
||||
bandwidth_limit_mode = client
|
||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||
use_encryption = false
|
||||
# if true, message will be compressed
|
||||
@@ -181,11 +226,13 @@ use_compression = true
|
||||
# if not set, you can access this custom_domains without certification
|
||||
http_user = 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
|
||||
custom_domains = web02.yourdomain.com
|
||||
custom_domains = web01.yourdomain.com
|
||||
# locations is only available for http type
|
||||
locations = /,/pic
|
||||
# route requests to this service if http basic auto user is abc
|
||||
# route_by_http_user = abc
|
||||
host_header_rewrite = example.com
|
||||
# params with prefix "header_" will be used to update http request headers
|
||||
header_X-From-Where = frp
|
||||
@@ -203,7 +250,7 @@ local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
subdomain = web01
|
||||
subdomain = web02
|
||||
custom_domains = web02.yourdomain.com
|
||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||
# v1 or v2 or empty
|
||||
@@ -311,6 +358,11 @@ bind_addr = 127.0.0.1
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
# when automatic tunnel persistence is required, set it to true
|
||||
keep_tunnel_open = false
|
||||
# effective when keep_tunnel_open is set to true, the number of attempts to punch through per hour
|
||||
max_retries_an_hour = 8
|
||||
min_retry_interval = 90
|
||||
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
@@ -318,3 +370,4 @@ multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
# route_by_http_user = user1
|
||||
|
@@ -6,13 +6,18 @@
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
|
||||
# udp port to help make udp hole to penetrate nat
|
||||
bind_udp_port = 7001
|
||||
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'
|
||||
# if not set, kcp is disabled in frps
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
||||
# if not set, kcp is disabled in frps.
|
||||
kcp_bind_port = 7000
|
||||
|
||||
# udp port used for quic protocol.
|
||||
# if not set, quic is disabled in frps.
|
||||
# quic_bind_port = 7002
|
||||
# quic protocol options
|
||||
# quic_keepalive_period = 10
|
||||
# quic_max_idle_timeout = 30
|
||||
# quic_max_incoming_streams = 100000
|
||||
|
||||
# specify which address proxy will listen for, default value is same with bind_addr
|
||||
# proxy_bind_addr = 127.0.0.1
|
||||
|
||||
@@ -30,6 +35,9 @@ vhost_https_port = 443
|
||||
# HTTP CONNECT requests. By default, this value is 0.
|
||||
# tcpmux_httpconnect_port = 1337
|
||||
|
||||
# If tcpmux_passthrough is true, frps won't do any update on traffic.
|
||||
# tcpmux_passthrough = false
|
||||
|
||||
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||
# dashboard_addr's default value is same with bind_addr
|
||||
# dashboard is available only if dashboard_port is set
|
||||
@@ -40,6 +48,11 @@ dashboard_port = 7500
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
|
||||
# dashboard TLS mode
|
||||
dashboard_tls_mode = false
|
||||
# dashboard_tls_cert_file = server.crt
|
||||
# dashboard_tls_key_file = server.key
|
||||
|
||||
# enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
|
||||
enable_prometheus = true
|
||||
|
||||
@@ -86,13 +99,12 @@ oidc_audience =
|
||||
# By default, this value is 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.
|
||||
# By default, this value is false.
|
||||
oidc_skip_issuer_check = false
|
||||
|
||||
# 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
|
||||
|
||||
# user_conn_timeout configure, it's not recommended to modify the default value
|
||||
@@ -120,7 +132,15 @@ tls_only = false
|
||||
subdomain_host = frps.com
|
||||
|
||||
# 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 = /path/to/404.html
|
||||
@@ -130,6 +150,13 @@ tcp_mux = true
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
# Enable golang pprof handlers in dashboard listener.
|
||||
# Dashboard port must be set first
|
||||
pprof_enable = false
|
||||
|
||||
# Retention time for NAT hole punching strategy data.
|
||||
nat_hole_analysis_data_reserve_hours = 168
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
|
@@ -1,15 +0,0 @@
|
||||
[Unit]
|
||||
Description=Frp Client Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
|
||||
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
|
||||
LimitNOFILE=1048576
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@@ -1,15 +0,0 @@
|
||||
[Unit]
|
||||
Description=Frp Client Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
|
||||
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
|
||||
LimitNOFILE=1048576
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Frp Server Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
|
||||
LimitNOFILE=1048576
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Frp Server Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
|
||||
LimitNOFILE=1048576
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
BIN
doc/pic/sponsor_asocks.jpg
Normal file
BIN
doc/pic/sponsor_asocks.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
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
|
||||
|
||||
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
Currently `Login`, `NewProxy`, `CloseProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
|
||||
#### Login
|
||||
|
||||
@@ -88,7 +88,8 @@ Client login operation
|
||||
"privilege_key": <string>,
|
||||
"run_id": <string>,
|
||||
"pool_count": <int>,
|
||||
"metas": map<string>string
|
||||
"metas": map<string>string,
|
||||
"client_address": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -109,6 +110,8 @@ Create new proxy
|
||||
"proxy_type": <string>,
|
||||
"use_encryption": <bool>,
|
||||
"use_compression": <bool>,
|
||||
"bandwidth_limit": <string>,
|
||||
"bandwidth_limit_mode": <string>,
|
||||
"group": <string>,
|
||||
"group_key": <string>,
|
||||
|
||||
@@ -135,6 +138,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
|
||||
|
||||
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,14 +1,12 @@
|
||||
FROM alpine:3.12.0 AS temp
|
||||
FROM golang:1.20 AS building
|
||||
|
||||
COPY bin/frpc /tmp
|
||||
COPY . /building
|
||||
WORKDIR /building
|
||||
|
||||
RUN chmod -R 777 /tmp/frpc
|
||||
RUN make frpc
|
||||
|
||||
FROM alpine:3
|
||||
|
||||
FROM alpine:3.12.0
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=temp /tmp/frpc /usr/bin
|
||||
COPY --from=building /building/bin/frpc /usr/bin/frpc
|
||||
|
||||
ENTRYPOINT ["/usr/bin/frpc"]
|
||||
|
@@ -1,14 +1,12 @@
|
||||
FROM alpine:3.12.0 AS temp
|
||||
FROM golang:1.20 AS building
|
||||
|
||||
COPY bin/frps /tmp
|
||||
COPY . /building
|
||||
WORKDIR /building
|
||||
|
||||
RUN chmod -R 777 /tmp/frps
|
||||
RUN make frps
|
||||
|
||||
FROM alpine:3
|
||||
|
||||
FROM alpine:3.12.0
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=temp /tmp/frps /usr/bin
|
||||
COPY --from=building /building/bin/frps /usr/bin/frps
|
||||
|
||||
ENTRYPOINT ["/usr/bin/frps"]
|
||||
|
95
go.mod
95
go.mod
@@ -1,40 +1,77 @@
|
||||
module github.com/fatedier/frp
|
||||
|
||||
go 1.16
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
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.20230320133937-a7edcc8c793d
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||
github.com/go-playground/validator/v10 v10.6.1
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/go-playground/validator/v10 v10.11.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
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/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.13.0
|
||||
github.com/pires/go-proxyproto v0.5.0
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/rakyll/statik v0.1.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/yamux v0.1.1
|
||||
github.com/onsi/ginkgo/v2 v2.8.3
|
||||
github.com/onsi/gomega v1.27.0
|
||||
github.com/pion/stun v0.4.0
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/quic-go/quic-go v0.34.0
|
||||
github.com/rodaine/table v1.0.1
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
k8s.io/apimachinery v0.21.2
|
||||
k8s.io/client-go v0.21.2
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.3.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
k8s.io/apimachinery v0.26.1
|
||||
k8s.io/client-go v0.26.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.15 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/pion/transport/v2 v2.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
|
||||
which ginkgo &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ginkgo not found, try to install..."
|
||||
go get -u github.com/onsi/ginkgo/ginkgo
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3
|
||||
fi
|
||||
|
||||
debug=false
|
||||
@@ -17,4 +17,4 @@ if [ x${LOG_LEVEL} != x"" ]; then
|
||||
logLevel=${LOG_LEVEL}
|
||||
fi
|
||||
|
||||
ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
||||
ginkgo -nodes=8 --poll-progress-after=30s ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
||||
|
@@ -15,7 +15,7 @@ rm -rf ./release/packages
|
||||
mkdir -p ./release/packages
|
||||
|
||||
os_all='linux windows darwin freebsd'
|
||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
|
||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64'
|
||||
|
||||
cd ./release
|
||||
|
||||
|
@@ -18,10 +18,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
type OidcClientConfig struct {
|
||||
@@ -34,20 +34,30 @@ type OidcClientConfig struct {
|
||||
// is "".
|
||||
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
|
||||
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||
//if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcScope specifies the scope of the token in OIDC authentication
|
||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
|
||||
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
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 {
|
||||
return OidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcScope: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcAdditionalEndpointParams: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +98,21 @@ type OidcAuthProvider struct {
|
||||
}
|
||||
|
||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
||||
eps := make(map[string][]string)
|
||||
for k, v := range cfg.OidcAdditionalEndpointParams {
|
||||
eps[k] = []string{v}
|
||||
}
|
||||
|
||||
if cfg.OidcAudience != "" {
|
||||
eps["audience"] = []string{cfg.OidcAudience}
|
||||
}
|
||||
|
||||
tokenGenerator := &clientcredentials.Config{
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcAudience},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcScope},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
EndpointParams: eps,
|
||||
}
|
||||
|
||||
return &OidcAuthProvider{
|
||||
|
@@ -73,30 +73,30 @@ func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkC
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error {
|
||||
if util.GetAuthKey(auth.token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
|
||||
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
|
||||
return fmt.Errorf("token in login doesn't match token from configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyPing(pingMsg *msg.Ping) error {
|
||||
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
return nil
|
||||
}
|
||||
|
||||
if util.GetAuthKey(auth.token, pingMsg.Timestamp) != pingMsg.PrivilegeKey {
|
||||
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
|
||||
return fmt.Errorf("token in heartbeat doesn't match token from configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
return nil
|
||||
}
|
||||
|
||||
if util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) != newWorkConnMsg.PrivilegeKey {
|
||||
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
|
||||
return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
|
||||
}
|
||||
return nil
|
||||
|
@@ -20,10 +20,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// ClientCommonConf contains information for a client service. It is
|
||||
@@ -38,6 +38,17 @@ type ClientCommonConf struct {
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `ini:"server_port" json:"server_port"`
|
||||
// STUN server to help penetrate NAT hole.
|
||||
NatHoleSTUNServer string `ini:"nat_hole_stun_server" json:"nat_hole_stun_server"`
|
||||
// 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
|
||||
// this value is "", the server will be connected to directly. By default,
|
||||
// this value is read from the "http_proxy" environment variable.
|
||||
@@ -86,6 +97,9 @@ type ClientCommonConf struct {
|
||||
// the server must have TCP multiplexing enabled as well. By default, this
|
||||
// value is true.
|
||||
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
|
||||
// clients. If this value is not "", proxy names will automatically be
|
||||
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||
@@ -101,11 +115,15 @@ type ClientCommonConf struct {
|
||||
// all supplied proxies are enabled. By default, this value is an empty
|
||||
// set.
|
||||
Start []string `ini:"start" json:"start"`
|
||||
//Start map[string]struct{} `json:"start"`
|
||||
// Start map[string]struct{} `json:"start"`
|
||||
// Protocol specifies the protocol to use when interacting with the server.
|
||||
// Valid values are "tcp", "kcp" and "websocket". By default, this value
|
||||
// Valid values are "tcp", "kcp", "quic" and "websocket". By default, this value
|
||||
// is "tcp".
|
||||
Protocol string `ini:"protocol" json:"protocol"`
|
||||
// QUIC protocol options
|
||||
QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period" validate:"gte=0"`
|
||||
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout" validate:"gte=0"`
|
||||
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams" validate:"gte=0"`
|
||||
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||
// with the server. If "tls_cert_file" and "tls_key_file" are valid,
|
||||
// client will load the supplied tls configuration.
|
||||
@@ -121,16 +139,19 @@ type ClientCommonConf struct {
|
||||
// It only works when "tls_enable" is valid and tls configuration of server
|
||||
// has been specified.
|
||||
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.
|
||||
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
|
||||
// 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"`
|
||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||
// 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"`
|
||||
// Client meta info
|
||||
Metas map[string]string `ini:"-" json:"metas"`
|
||||
@@ -139,41 +160,40 @@ type ClientCommonConf struct {
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
// Include other config files for proxies.
|
||||
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.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
AdminPwd: "",
|
||||
AssetsDir: "",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
User: "",
|
||||
DNSServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
AdminAddr: "127.0.0.1",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,15 +206,13 @@ func (cfg *ClientCommonConf) Complete() {
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Validate() error {
|
||||
if cfg.HeartbeatInterval <= 0 {
|
||||
return fmt.Errorf("invalid heartbeat_interval")
|
||||
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
}
|
||||
|
||||
if cfg.TLSEnable == false {
|
||||
if !cfg.TLSEnable {
|
||||
if cfg.TLSCertFile != "" {
|
||||
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
|
||||
}
|
||||
@@ -208,7 +226,7 @@ func (cfg *ClientCommonConf) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Protocol != "tcp" && cfg.Protocol != "kcp" && cfg.Protocol != "websocket" {
|
||||
if cfg.Protocol != "tcp" && cfg.Protocol != "kcp" && cfg.Protocol != "websocket" && cfg.Protocol != "quic" {
|
||||
return fmt.Errorf("invalid protocol")
|
||||
}
|
||||
|
||||
@@ -249,6 +267,8 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
||||
}
|
||||
|
||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||
|
||||
return common, nil
|
||||
}
|
||||
|
||||
@@ -259,7 +279,6 @@ func LoadAllProxyConfsFromIni(
|
||||
source interface{},
|
||||
start []string,
|
||||
) (map[string]ProxyConf, map[string]VisitorConf, error) {
|
||||
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
@@ -344,7 +363,6 @@ func LoadAllProxyConfsFromIni(
|
||||
}
|
||||
|
||||
func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {
|
||||
|
||||
// Validation
|
||||
localPortStr := section.Key("local_port").String()
|
||||
remotePortStr := section.Key("remote_port").String()
|
||||
@@ -382,8 +400,12 @@ func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {
|
||||
}
|
||||
|
||||
copySection(section, tmpsection)
|
||||
tmpsection.NewKey("local_port", fmt.Sprintf("%d", localPorts[i]))
|
||||
tmpsection.NewKey("remote_port", fmt.Sprintf("%d", remotePorts[i]))
|
||||
if _, err := tmpsection.NewKey("local_port", fmt.Sprintf("%d", localPorts[i])); err != nil {
|
||||
return fmt.Errorf("local_port new key in section error: %v", err)
|
||||
}
|
||||
if _, err := tmpsection.NewKey("remote_port", fmt.Sprintf("%d", remotePorts[i])); err != nil {
|
||||
return fmt.Errorf("remote_port new key in section error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -391,6 +413,6 @@ func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {
|
||||
|
||||
func copySection(source, target *ini.Section) {
|
||||
for key, value := range source.KeysHash() {
|
||||
target.NewKey(key, value)
|
||||
_, _ = target.NewKey(key, value)
|
||||
}
|
||||
}
|
||||
|
@@ -17,18 +17,17 @@ package config
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
testUser = "test"
|
||||
)
|
||||
|
||||
var (
|
||||
testClientBytesWithFull = []byte(`
|
||||
var testClientBytesWithFull = []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
server_addr = 0.0.0.9
|
||||
@@ -75,6 +74,7 @@ var (
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
bandwidth_limit = 19MB
|
||||
bandwidth_limit_mode = server
|
||||
use_encryption
|
||||
use_compression
|
||||
remote_port = 6009
|
||||
@@ -237,7 +237,6 @@ var (
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`)
|
||||
)
|
||||
|
||||
func Test_LoadClientCommonConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
@@ -259,33 +258,40 @@ func Test_LoadClientCommonConf(t *testing.T) {
|
||||
OidcTokenEndpointURL: "endpoint_url",
|
||||
},
|
||||
},
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.9",
|
||||
AdminPort: 7409,
|
||||
AdminUser: "admin9",
|
||||
AdminPwd: "admin9",
|
||||
AssetsDir: "./static9",
|
||||
PoolCount: 59,
|
||||
TCPMux: true,
|
||||
User: "your_name",
|
||||
LoginFailExit: true,
|
||||
Protocol: "tcp",
|
||||
TLSEnable: true,
|
||||
TLSCertFile: "client.crt",
|
||||
TLSKeyFile: "client.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
TLSServerName: "example.com",
|
||||
DNSServer: "8.8.8.9",
|
||||
Start: []string{"ssh", "dns"},
|
||||
HeartbeatInterval: 39,
|
||||
HeartbeatTimeout: 99,
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.9",
|
||||
AdminPort: 7409,
|
||||
AdminUser: "admin9",
|
||||
AdminPwd: "admin9",
|
||||
AssetsDir: "./static9",
|
||||
PoolCount: 59,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
User: "your_name",
|
||||
LoginFailExit: true,
|
||||
Protocol: "tcp",
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
TLSEnable: true,
|
||||
TLSCertFile: "client.crt",
|
||||
TLSKeyFile: "client.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
TLSServerName: "example.com",
|
||||
DNSServer: "8.8.8.9",
|
||||
Start: []string{"ssh", "dns"},
|
||||
HeartbeatInterval: 39,
|
||||
HeartbeatTimeout: 99,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
@@ -305,13 +311,14 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
proxyExpected := map[string]ProxyConf{
|
||||
testUser + ".ssh": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
ProxyName: testUser + ".ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
BandwidthLimitMode: BandwidthLimitModeServer,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
@@ -338,6 +345,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 9,
|
||||
},
|
||||
@@ -349,6 +357,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
@@ -360,6 +369,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
@@ -371,6 +381,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6019,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6019,
|
||||
},
|
||||
@@ -384,6 +395,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 59,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
@@ -397,6 +409,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6000,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6000,
|
||||
},
|
||||
@@ -410,6 +423,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
@@ -423,6 +437,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
@@ -443,6 +458,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
@@ -467,6 +483,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalPort: 8009,
|
||||
},
|
||||
ProxyProtocolVersion: "v2",
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
@@ -481,6 +498,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
@@ -493,6 +511,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
@@ -505,6 +524,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 10701,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"tunnel1"},
|
||||
@@ -523,6 +543,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
"plugin_unix_path": "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6003,
|
||||
},
|
||||
@@ -538,6 +559,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
"plugin_http_passwd": "abc",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6004,
|
||||
},
|
||||
@@ -553,6 +575,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
"plugin_passwd": "abc",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6005,
|
||||
},
|
||||
@@ -570,6 +593,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
"plugin_http_passwd": "abc",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6006,
|
||||
},
|
||||
@@ -588,6 +612,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
"plugin_header_X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"test.yourdomain.com"},
|
||||
@@ -606,6 +631,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
"plugin_header_X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"test.yourdomain.com"},
|
||||
@@ -635,6 +661,9 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9001,
|
||||
},
|
||||
Protocol: "quic",
|
||||
MaxRetriesAnHour: 8,
|
||||
MinRetryInterval: 90,
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,6 @@ package config
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -43,7 +42,7 @@ func ParseClientConfig(filePath string) (
|
||||
}
|
||||
cfg.Complete()
|
||||
if err = cfg.Validate(); err != nil {
|
||||
err = fmt.Errorf("Parse config error: %v", err)
|
||||
err = fmt.Errorf("parse config error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -77,7 +76,7 @@ func getIncludeContents(paths []string) ([]byte, error) {
|
||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
files, err := ioutil.ReadDir(absDir)
|
||||
files, err := os.ReadDir(absDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -16,13 +16,15 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Proxy
|
||||
@@ -139,6 +141,10 @@ type BaseProxyConf struct {
|
||||
// BandwidthLimit limit the bandwidth
|
||||
// 0 means no limit
|
||||
BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
|
||||
// BandwidthLimitMode specifies whether to limit the bandwidth on the
|
||||
// client or server side. Valid values include "client" and "server".
|
||||
// By default, this value is "client".
|
||||
BandwidthLimitMode string `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
|
||||
|
||||
// meta info for each proxy
|
||||
Metas map[string]string `ini:"-" json:"metas"`
|
||||
@@ -162,6 +168,7 @@ type HTTPProxyConf struct {
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
|
||||
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
|
||||
Headers map[string]string `ini:"-" json:"headers"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
@@ -178,8 +185,11 @@ type TCPProxyConf struct {
|
||||
|
||||
// TCPMux
|
||||
type TCPMuxProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
|
||||
Multiplexer string `ini:"multiplexer"`
|
||||
}
|
||||
@@ -315,6 +325,7 @@ func defaultBaseProxyConf(proxyType string) BaseProxyConf {
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +342,7 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
||||
cfg.GroupKey != cmp.GroupKey ||
|
||||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion ||
|
||||
!cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) ||
|
||||
cfg.BandwidthLimitMode != cmp.BandwidthLimitMode ||
|
||||
!reflect.DeepEqual(cfg.Metas, cmp.Metas) {
|
||||
return false
|
||||
}
|
||||
@@ -370,7 +382,7 @@ func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Sect
|
||||
}
|
||||
|
||||
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
|
||||
s := fmt.Sprintf("http://%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
s := "http://" + net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
|
||||
s += "/"
|
||||
}
|
||||
@@ -385,6 +397,11 @@ func (cfg *BaseProxyConf) marshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.ProxyType = cfg.ProxyType
|
||||
pMsg.UseEncryption = cfg.UseEncryption
|
||||
pMsg.UseCompression = cfg.UseCompression
|
||||
pMsg.BandwidthLimit = cfg.BandwidthLimit.String()
|
||||
// leave it empty for default value to reduce traffic
|
||||
if cfg.BandwidthLimitMode != "client" {
|
||||
pMsg.BandwidthLimitMode = cfg.BandwidthLimitMode
|
||||
}
|
||||
pMsg.Group = cfg.Group
|
||||
pMsg.GroupKey = cfg.GroupKey
|
||||
pMsg.Metas = cfg.Metas
|
||||
@@ -395,6 +412,12 @@ func (cfg *BaseProxyConf) unmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.ProxyType = pMsg.ProxyType
|
||||
cfg.UseEncryption = pMsg.UseEncryption
|
||||
cfg.UseCompression = pMsg.UseCompression
|
||||
if pMsg.BandwidthLimit != "" {
|
||||
cfg.BandwidthLimit, _ = NewBandwidthQuantity(pMsg.BandwidthLimit)
|
||||
}
|
||||
if pMsg.BandwidthLimitMode != "" {
|
||||
cfg.BandwidthLimitMode = pMsg.BandwidthLimitMode
|
||||
}
|
||||
cfg.Group = pMsg.Group
|
||||
cfg.GroupKey = pMsg.GroupKey
|
||||
cfg.Metas = pMsg.Metas
|
||||
@@ -407,6 +430,10 @@ func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
||||
}
|
||||
|
||||
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -416,7 +443,10 @@ func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) checkForSvr(conf ServerCommonConf) error {
|
||||
func (cfg *BaseProxyConf) checkForSvr() (err error) {
|
||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -557,6 +587,9 @@ func (cfg *TCPProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -576,7 +609,10 @@ func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if cfg.Multiplexer != cmpConf.Multiplexer {
|
||||
if cfg.Multiplexer != cmpConf.Multiplexer ||
|
||||
cfg.HTTPUser != cmpConf.HTTPUser ||
|
||||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
|
||||
cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -601,6 +637,9 @@ func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.CustomDomains = pMsg.CustomDomains
|
||||
cfg.SubDomain = pMsg.SubDomain
|
||||
cfg.Multiplexer = pMsg.Multiplexer
|
||||
cfg.HTTPUser = pMsg.HTTPUser
|
||||
cfg.HTTPPwd = pMsg.HTTPPwd
|
||||
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
@@ -610,6 +649,9 @@ func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.CustomDomains = cfg.CustomDomains
|
||||
pMsg.SubDomain = cfg.SubDomain
|
||||
pMsg.Multiplexer = cfg.Multiplexer
|
||||
pMsg.HTTPUser = cfg.HTTPUser
|
||||
pMsg.HTTPPwd = cfg.HTTPPwd
|
||||
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
|
||||
@@ -630,6 +672,10 @@ func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
||||
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
|
||||
}
|
||||
@@ -701,6 +747,9 @@ func (cfg *UDPProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -724,6 +773,7 @@ func (cfg *HTTPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cfg.HTTPUser != cmpConf.HTTPUser ||
|
||||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
|
||||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
|
||||
cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser ||
|
||||
!reflect.DeepEqual(cfg.Headers, cmpConf.Headers) {
|
||||
return false
|
||||
}
|
||||
@@ -754,6 +804,7 @@ func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.HTTPUser = pMsg.HTTPUser
|
||||
cfg.HTTPPwd = pMsg.HTTPPwd
|
||||
cfg.Headers = pMsg.Headers
|
||||
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
@@ -767,6 +818,7 @@ func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.HTTPUser = cfg.HTTPUser
|
||||
pMsg.HTTPPwd = cfg.HTTPPwd
|
||||
pMsg.Headers = cfg.Headers
|
||||
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) CheckForCli() (err error) {
|
||||
@@ -783,6 +835,10 @@ func (cfg *HTTPProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if serverCfg.VhostHTTPPort == 0 {
|
||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
}
|
||||
@@ -855,6 +911,10 @@ func (cfg *HTTPSProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if serverCfg.VhostHTTPSPort == 0 {
|
||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||
}
|
||||
@@ -927,6 +987,9 @@ func (cfg *SUDPProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -993,6 +1056,9 @@ func (cfg *STCPProxyConf) CheckForCli() (err error) {
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1012,7 +1078,6 @@ func (cfg *XTCPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cfg.Sk != cmpConf.Sk {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1026,7 +1091,6 @@ func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *
|
||||
if cfg.Role == "" {
|
||||
cfg.Role = "server"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1054,10 +1118,12 @@ func (cfg *XTCPProxyConf) CheckForCli() (err error) {
|
||||
if cfg.Role != "server" {
|
||||
return fmt.Errorf("role should be 'server'")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -17,10 +17,10 @@ package config
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -49,7 +49,6 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
source []byte
|
||||
expected ProxyConf
|
||||
}{
|
||||
|
||||
{
|
||||
sname: "ssh",
|
||||
source: []byte(`
|
||||
@@ -59,6 +58,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
bandwidth_limit = 19MB
|
||||
bandwidth_limit_mode = server
|
||||
use_encryption
|
||||
use_compression
|
||||
remote_port = 6009
|
||||
@@ -72,13 +72,14 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
meta_var2 = 234`),
|
||||
expected: &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
ProxyName: testProxyPrefix + "ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
BandwidthLimitMode: BandwidthLimitModeServer,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
@@ -115,6 +116,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 9,
|
||||
},
|
||||
@@ -140,6 +142,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 59,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
@@ -183,6 +186,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
@@ -221,6 +225,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalPort: 8009,
|
||||
},
|
||||
ProxyProtocolVersion: "v2",
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
@@ -247,6 +252,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
@@ -271,6 +277,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
@@ -294,6 +301,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 10701,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"tunnel1"},
|
||||
@@ -348,6 +356,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
@@ -359,6 +368,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
@@ -370,6 +380,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6019,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6019,
|
||||
},
|
||||
@@ -397,6 +408,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6000,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6000,
|
||||
},
|
||||
@@ -410,6 +422,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
@@ -423,6 +436,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
@@ -457,5 +471,4 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -18,12 +18,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// ServerCommonConf contains information for a server service. It is
|
||||
@@ -38,14 +38,18 @@ type ServerCommonConf struct {
|
||||
// BindPort specifies the port that the server listens on. By default, this
|
||||
// value is 7000.
|
||||
BindPort int `ini:"bind_port" json:"bind_port" validate:"gte=0,lte=65535"`
|
||||
// BindUDPPort specifies the UDP port that the server listens on. If this
|
||||
// value is 0, the server will not listen for UDP connections. By default,
|
||||
// this value is 0
|
||||
BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port" validate:"gte=0,lte=65535"`
|
||||
// KCPBindPort specifies the KCP port that the server listens on. If this
|
||||
// value is 0, the server will not listen for KCP connections. By default,
|
||||
// this value is 0.
|
||||
KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port" validate:"gte=0,lte=65535"`
|
||||
// QUICBindPort specifies the QUIC port that the server listens on.
|
||||
// Set this value to 0 will disable this feature.
|
||||
// By default, the value is 0.
|
||||
QUICBindPort int `ini:"quic_bind_port" json:"quic_bind_port" validate:"gte=0,lte=65535"`
|
||||
// QUIC protocol options
|
||||
QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period" validate:"gte=0"`
|
||||
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout" validate:"gte=0"`
|
||||
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams" validate:"gte=0"`
|
||||
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||
// may be the same as BindAddr.
|
||||
ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"`
|
||||
@@ -62,6 +66,8 @@ type ServerCommonConf struct {
|
||||
// requests on one single port. If it's not - it will listen on this value for
|
||||
// HTTP CONNECT requests. By default, this value is 0.
|
||||
TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port" validate:"gte=0,lte=65535"`
|
||||
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
|
||||
TCPMuxPassthrough bool `ini:"tcpmux_passthrough" json:"tcpmux_passthrough"`
|
||||
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
||||
// HTTP server, in seconds. By default, this value is 60.
|
||||
VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"`
|
||||
@@ -72,6 +78,17 @@ type ServerCommonConf struct {
|
||||
// value is 0, the dashboard will not be started. By default, this value is
|
||||
// 0.
|
||||
DashboardPort int `ini:"dashboard_port" json:"dashboard_port" validate:"gte=0,lte=65535"`
|
||||
// DashboardTLSCertFile specifies the path of the cert file that the server will
|
||||
// load. If "dashboard_tls_cert_file", "dashboard_tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration.
|
||||
DashboardTLSCertFile string `ini:"dashboard_tls_cert_file" json:"dashboard_tls_cert_file"`
|
||||
// DashboardTLSKeyFile specifies the path of the secret key that the server will
|
||||
// load. If "dashboard_tls_cert_file", "dashboard_tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration.
|
||||
DashboardTLSKeyFile string `ini:"dashboard_tls_key_file" json:"dashboard_tls_key_file"`
|
||||
// DashboardTLSMode specifies the mode of the dashboard between HTTP or HTTPS modes. By
|
||||
// default, this value is false, which is HTTP mode.
|
||||
DashboardTLSMode bool `ini:"dashboard_tls_mode" json:"dashboard_tls_mode"`
|
||||
// DashboardUser specifies the username that the dashboard will use for
|
||||
// login.
|
||||
DashboardUser string `ini:"dashboard_user" json:"dashboard_user"`
|
||||
@@ -118,6 +135,12 @@ type ServerCommonConf struct {
|
||||
// from a client to share a single TCP connection. By default, this value
|
||||
// is true.
|
||||
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
|
||||
// value is "", a default page will be displayed. By default, this value is
|
||||
// "".
|
||||
@@ -127,6 +150,8 @@ type ServerCommonConf struct {
|
||||
// If the length of this value is 0, all ports are allowed. By default,
|
||||
// this value is an empty set.
|
||||
AllowPorts map[int]struct{} `ini:"-" json:"-"`
|
||||
// Original string.
|
||||
AllowPortsStr string `ini:"-" json:"-"`
|
||||
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||
// this value is 5.
|
||||
MaxPoolCount int64 `ini:"max_pool_count" json:"max_pool_count"`
|
||||
@@ -154,7 +179,7 @@ type ServerCommonConf struct {
|
||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||
// 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"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
@@ -164,53 +189,45 @@ type ServerCommonConf struct {
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
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"`
|
||||
// NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data.
|
||||
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
ServerConfig: auth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUDPPort: 0,
|
||||
KCPBindPort: 0,
|
||||
ProxyBindAddr: "",
|
||||
VhostHTTPPort: 0,
|
||||
VhostHTTPSPort: 0,
|
||||
TCPMuxHTTPConnectPort: 0,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
ServerConfig: auth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
||||
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
@@ -243,6 +260,7 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
||||
for _, port := range allowPorts {
|
||||
common.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
common.AllowPortsStr = allowPortStr
|
||||
}
|
||||
|
||||
// plugin.xxx
|
||||
@@ -282,6 +300,23 @@ func (cfg *ServerCommonConf) Complete() {
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Validate() error {
|
||||
if !cfg.DashboardTLSMode {
|
||||
if cfg.DashboardTLSCertFile != "" {
|
||||
fmt.Println("WARNING! dashboard_tls_cert_file is invalid when dashboard_tls_mode is false")
|
||||
}
|
||||
|
||||
if cfg.DashboardTLSKeyFile != "" {
|
||||
fmt.Println("WARNING! dashboard_tls_key_file is invalid when dashboard_tls_mode is false")
|
||||
}
|
||||
} else {
|
||||
if cfg.DashboardTLSCertFile == "" {
|
||||
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
|
||||
}
|
||||
|
||||
if cfg.DashboardTLSKeyFile == "" {
|
||||
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
|
||||
}
|
||||
}
|
||||
return validator.New().Struct(cfg)
|
||||
}
|
||||
|
||||
|
@@ -17,10 +17,10 @@ package config
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_LoadServerCommonConf(t *testing.T) {
|
||||
@@ -104,8 +104,10 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
BindUDPPort: 7008,
|
||||
KCPBindPort: 7007,
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
ProxyBindAddr: "127.0.0.9",
|
||||
VhostHTTPPort: 89,
|
||||
VhostHTTPSPort: 449,
|
||||
@@ -126,20 +128,24 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
HeartbeatTimeout: 99,
|
||||
UserConnTimeout: 9,
|
||||
AllowPorts: map[int]struct{}{
|
||||
10: struct{}{},
|
||||
11: struct{}{},
|
||||
12: struct{}{},
|
||||
99: struct{}{},
|
||||
10: {},
|
||||
11: {},
|
||||
12: {},
|
||||
99: {},
|
||||
},
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: true,
|
||||
TLSCertFile: "server.crt",
|
||||
TLSKeyFile: "server.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
UDPPacketSize: 1509,
|
||||
AllowPortsStr: "10-12,99",
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: true,
|
||||
TLSCertFile: "server.crt",
|
||||
TLSKeyFile: "server.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
UDPPacketSize: 1509,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
|
||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||
"user-manager": {
|
||||
@@ -174,27 +180,32 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
BindUDPPort: 7008,
|
||||
ProxyBindAddr: "0.0.0.9",
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
ProxyBindAddr: "0.0.0.9",
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user