Compare commits
278 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
de690a55c8 | ||
|
1ced733340 | ||
|
ce366ee17f | ||
|
3128350dd6 | ||
|
3be6efdd28 | ||
|
6cbb26283c | ||
|
75edea3370 | ||
|
e687aef37e | ||
|
a23455a737 | ||
|
e208043323 | ||
|
a78814a2e9 | ||
|
773169e0c4 | ||
|
9757a351c6 | ||
|
1e8db66743 | ||
|
e0dd947e6a | ||
|
8b86e1473c | ||
|
b8d3ace113 | ||
|
450b8393bc | ||
|
27db6217ec | ||
|
6542dcd4ed | ||
|
092e5d3f94 | ||
|
01fed8d1a9 | ||
|
f47d8ab97f | ||
|
bb912d6c37 | ||
|
c73096f2bf | ||
|
0358113948 | ||
|
8593eff752 | ||
|
dff56cb0ca | ||
|
4383756fd4 | ||
|
6ba849fc75 | ||
|
9d5638cae6 | ||
|
62352c7ba5 | ||
|
f7a06cbe61 | ||
|
3a08c2aeb0 | ||
|
b14192a8d3 | ||
|
2466e65f43 | ||
|
2855ac71e3 | ||
|
fe4ca1b54e | ||
|
edd7cf8967 | ||
|
03c8d7bf96 | ||
|
2dcdb24cc4 | ||
|
d47e138bc9 | ||
|
f1fb2d721a | ||
|
ae73ec2fed | ||
|
e8045194cd | ||
|
69cc422edf | ||
|
b4d5d8c756 | ||
|
c6f9d8d403 | ||
|
939c490768 | ||
|
f390e4a401 | ||
|
77990c31ef | ||
|
e680acf42d | ||
|
522e2c94c1 | ||
|
301515d2e8 | ||
|
f0442d0cd5 | ||
|
9ced717d69 | ||
|
92cb0b30c2 | ||
|
e81b36c5ba | ||
|
d0d396becb | ||
|
ee3892798d | ||
|
405969085f | ||
|
c1893ee1b4 | ||
|
eaae212d2d | ||
|
885278c045 | ||
|
2626d6ed92 | ||
|
f3a71bc08f | ||
|
dd7e2e8473 | ||
|
07946e9752 | ||
|
e52727e01c | ||
|
ba937e9fbf | ||
|
d2d03a8fd9 | ||
|
590ccda677 | ||
|
86f90f4d27 | ||
|
f16ef00975 | ||
|
b36f3834eb | ||
|
c08be0fd92 | ||
|
bc5fb91c05 | ||
|
002831ea82 | ||
|
acf33db4e4 | ||
|
3585f5c0c0 | ||
|
8383d528d9 | ||
|
fa977c839f | ||
|
86c2ad78c8 | ||
|
d5589213c5 | ||
|
e0c979e98e | ||
|
e6ec5a509b | ||
|
43ba7bd338 | ||
|
49443cb2c6 | ||
|
32f09c4b60 | ||
|
f63b4d5c29 | ||
|
b3946489dd | ||
|
7ae3719b82 | ||
|
80cfd0938e | ||
|
52f66b05e6 | ||
|
b2b580be22 | ||
|
3e0c78233a | ||
|
b6361fb143 | ||
|
adb04e81e7 | ||
|
518ca2ceb2 | ||
|
4957fd23ee | ||
|
2f958c2095 | ||
|
dc34a68542 | ||
|
3529158f31 | ||
|
9152c59570 | ||
|
2af2cf7dbd | ||
|
cf025d6320 | ||
|
e8ace492a5 | ||
|
1c8bc0bfa8 | ||
|
b31c67d7c0 | ||
|
8023d147b0 | ||
|
6a488cc081 | ||
|
7418ae098d | ||
|
7999791708 | ||
|
f7efbfeec5 | ||
|
1e8806d26b | ||
|
d01f4a3ec1 | ||
|
596262d5e0 | ||
|
cdfa8fa66f | ||
|
256b87321d | ||
|
5b7b81a117 | ||
|
2a9a7a0e4a | ||
|
5e77c8e2d3 | ||
|
3bf6605e1a | ||
|
3540910879 | ||
|
2d67e2e0c6 | ||
|
cc2076970f | ||
|
e7652f4ccc | ||
|
e66e77cb8f | ||
|
1bc9d1a28e | ||
|
7ad62818bd | ||
|
9ecafeab40 | ||
|
95cf418963 | ||
|
6d9e0c20f6 | ||
|
97d3cf1a3b | ||
|
38f297a395 | ||
|
7c799ee921 | ||
|
69ae2b0b69 | ||
|
d5b41f1e14 | ||
|
8b432e179d | ||
|
f5d5a00eef | ||
|
526e809bd5 | ||
|
e8deb65c4b | ||
|
184223cb2f | ||
|
5760c1cf92 | ||
|
5c4d820eb4 | ||
|
46266e4d30 | ||
|
a6478aeac8 | ||
|
806b55c292 | ||
|
496b1f1078 | ||
|
9cb0726ebc | ||
|
31190c703d | ||
|
1452facf77 | ||
|
6d4d8e616d | ||
|
a7ad967231 | ||
|
01a0d557ef | ||
|
b9c24e9b69 | ||
|
df12cc2b9d | ||
|
7cc67e852e | ||
|
307d1bfa3f | ||
|
5eb8f3db03 | ||
|
3ae1a4f45a | ||
|
21d8e674f0 | ||
|
5e70d5bee0 | ||
|
5c8ea51eb5 | ||
|
bae0b4d7c0 | ||
|
74255f711e | ||
|
7cd02f5bd8 | ||
|
c95311d1a0 | ||
|
885b029fcf | ||
|
f1454e91f5 | ||
|
e9e12cf888 | ||
|
6430afcfa5 | ||
|
3235addaaa | ||
|
46ff40543a | ||
|
efcc028a3d | ||
|
90861b6821 | ||
|
8f105adbca | ||
|
b1789afbab | ||
|
88c7e8bf7c | ||
|
fc4e787fe2 | ||
|
4c4d5f0d0d | ||
|
801e8c6742 | ||
|
b146989703 | ||
|
685d7618f3 | ||
|
15a245766e | ||
|
e1cef053be | ||
|
9ba6a06470 | ||
|
ea08de668e | ||
|
de85c9455a | ||
|
cceab7e1b1 | ||
|
9aef3b9944 | ||
|
341a5e3e3a | ||
|
c7a0cfc66d | ||
|
555db9d272 | ||
|
98068402c8 | ||
|
4915852b9c | ||
|
756dd1ad5e | ||
|
c71efde303 | ||
|
9f029e3248 | ||
|
8095075719 | ||
|
2225a1781f | ||
|
0214b974dd | ||
|
738c53ce47 | ||
|
db52f07d34 | ||
|
f6b8645f56 | ||
|
2c2c4ecdbc | ||
|
3faae194d0 | ||
|
a22d6c9504 | ||
|
9800b4cfcf | ||
|
8f394dba27 | ||
|
fccd518512 | ||
|
968ba4d3a1 | ||
|
862b1642ba | ||
|
54eb704650 | ||
|
8c6303c1e5 | ||
|
871511ba52 | ||
|
cb6d7ba7f9 | ||
|
31f40aa913 | ||
|
2f59e967a0 | ||
|
fe8374e99b | ||
|
24f0b3afa5 | ||
|
39941117b6 | ||
|
6a1f9ad893 | ||
|
18ab58eb25 | ||
|
fa0593ae2c | ||
|
89fff7d11d | ||
|
38d42dbe4b | ||
|
aa31d7ad0b | ||
|
113e3b0b0d | ||
|
100148d925 | ||
|
6b3daffaf0 | ||
|
5e17bc7bf1 | ||
|
b1b8d9a82b | ||
|
24c7d1d9e2 | ||
|
d205c26480 | ||
|
0eecab06c1 | ||
|
ad3548d332 | ||
|
679992db25 | ||
|
5cfbb976f4 | ||
|
b03f0ad1e6 | ||
|
804f2910fd | ||
|
e2d28d9929 | ||
|
7678938c08 | ||
|
b2e3946800 | ||
|
af0b7939a7 | ||
|
2f66dc3e99 | ||
|
649df8827c | ||
|
da51adc276 | ||
|
e5af37bc8c | ||
|
e8c8d5903a | ||
|
34ab6b0e74 | ||
|
cf66ca10b4 | ||
|
3fbe6b659e | ||
|
6a71d71e58 | ||
|
6ecc97c857 | ||
|
ba492f07c3 | ||
|
9d077b02cf | ||
|
f4e4fbea62 | ||
|
3e721d122b | ||
|
1bc899ec12 | ||
|
6f2571980c | ||
|
fa7c05c617 | ||
|
218b354f82 | ||
|
c652b8ef07 | ||
|
5b8b145577 | ||
|
0711295b0a | ||
|
4af85da0c2 | ||
|
bd89eaba2f | ||
|
a72259c604 | ||
|
44eb513f05 | ||
|
6c658586f6 | ||
|
888ed25314 | ||
|
21240ed962 | ||
|
6481870d03 | ||
|
a7a4ba270d | ||
|
915d9f4c09 | ||
|
18a2af4703 | ||
|
305e40fa8a |
@ -2,22 +2,15 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.18-node
|
- image: cimg/go:1.23-node
|
||||||
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: make
|
- run: make
|
||||||
- run: make alltest
|
- run: make alltest
|
||||||
go-version-last:
|
|
||||||
docker:
|
|
||||||
- image: cimg/go:1.17-node
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: make
|
|
||||||
- run: make alltest
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build_and_test:
|
build_and_test:
|
||||||
jobs:
|
jobs:
|
||||||
- go-version-latest
|
- go-version-latest
|
||||||
- go-version-last
|
|
||||||
|
1
.github/FUNDING.yml
vendored
@ -1,3 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [fatedier]
|
github: [fatedier]
|
||||||
|
custom: ["https://afdian.com/a/fatedier"]
|
||||||
|
3
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
### WHY
|
||||||
|
|
||||||
|
<!-- author to complete -->
|
112
.github/workflows/build-and-push-image.yml
vendored
@ -2,76 +2,32 @@ name: Build Image and Publish to Dockerhub & GPR
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ created ]
|
types: [ published ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: 'Image tag'
|
description: 'Image tag'
|
||||||
required: true
|
required: true
|
||||||
default: 'test'
|
default: 'test'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
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.18
|
|
||||||
|
|
||||||
- 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:
|
image:
|
||||||
name: Build Image from Dockerfile and binaries
|
name: Build Image from Dockerfile and binaries
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: binary
|
|
||||||
steps:
|
steps:
|
||||||
# environment
|
# environment
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# get image tag name
|
# get image tag name
|
||||||
- name: Get Image Tag Name
|
- name: Get Image Tag Name
|
||||||
@ -81,6 +37,18 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Login to the GPR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GPR_TOKEN }}
|
||||||
|
|
||||||
# prepare image tags
|
# prepare image tags
|
||||||
- name: 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_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
# build images
|
- name: Build and push frpc
|
||||||
- name: Build Images
|
uses: docker/build-push-action@v5
|
||||||
run: |
|
with:
|
||||||
# for Docker hub
|
context: .
|
||||||
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} .
|
file: ./dockerfiles/Dockerfile-for-frpc
|
||||||
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS }} .
|
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||||
# for GPR
|
push: true
|
||||||
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} .
|
tags: |
|
||||||
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} .
|
${{ env.TAG_FRPC }}
|
||||||
|
${{ env.TAG_FRPC_GPR }}
|
||||||
|
|
||||||
# push to dockerhub
|
- name: Build and push frps
|
||||||
- name: Publish to Dockerhub
|
uses: docker/build-push-action@v5
|
||||||
run: |
|
with:
|
||||||
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
context: .
|
||||||
docker push ${{ env.TAG_FRPC }}
|
file: ./dockerfiles/Dockerfile-for-frps
|
||||||
docker push ${{ env.TAG_FRPS }}
|
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||||
|
push: true
|
||||||
# push to gpr
|
tags: |
|
||||||
- name: Publish to GPR
|
${{ env.TAG_FRPS }}
|
||||||
run: |
|
${{ env.TAG_FRPS_GPR }}
|
||||||
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 }}
|
|
||||||
|
36
.github/workflows/golangci-lint.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23'
|
||||||
|
cache: false
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v8
|
||||||
|
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: v2.1
|
||||||
|
|
||||||
|
# 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
|
14
.github/workflows/goreleaser.yml
vendored
@ -8,27 +8,23 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: '1.23'
|
||||||
|
|
||||||
- run: |
|
|
||||||
# https://github.com/actions/setup-go/issues/107
|
|
||||||
cp -f `which go` /usr/bin/go
|
|
||||||
|
|
||||||
- name: Make All
|
- name: Make All
|
||||||
run: |
|
run: |
|
||||||
./package.sh
|
./package.sh
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist --release-notes=./Release.md
|
args: release --clean --release-notes=./Release.md
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
|
||||||
|
21
.github/workflows/stale.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: "Close stale issues"
|
name: "Close stale issues and PRs"
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "20 0 * * *"
|
- cron: "20 0 * * *"
|
||||||
@ -8,21 +8,28 @@ on:
|
|||||||
description: 'In debug mod'
|
description: 'In debug mod'
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
|
permissions:
|
||||||
|
issues: write # for actions/stale to close stale issues
|
||||||
|
pull-requests: write # for actions/stale to close stale PRs
|
||||||
|
actions: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
stale-issue-message: 'Issues go stale after 14d of inactivity. Stale issues rot after an additional 3d 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 14d of inactivity. Stale PRs rot after an additional 3d of inactivity and eventually close."
|
||||||
stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
|
|
||||||
stale-issue-label: 'lifecycle/stale'
|
stale-issue-label: 'lifecycle/stale'
|
||||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||||
stale-pr-label: 'lifecycle/stale'
|
stale-pr-label: 'lifecycle/stale'
|
||||||
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||||
days-before-stale: 30
|
days-before-stale: 14
|
||||||
days-before-close: 7
|
days-before-close: 3
|
||||||
debug-only: ${{ github.event.inputs.debug-only }}
|
debug-only: ${{ github.event.inputs.debug-only }}
|
||||||
exempt-all-pr-milestones: true
|
exempt-all-pr-milestones: true
|
||||||
exempt-all-pr-assignees: true
|
exempt-all-pr-assignees: true
|
||||||
|
operations-per-run: 200
|
||||||
|
5
.gitignore
vendored
@ -29,8 +29,13 @@ packages/
|
|||||||
release/
|
release/
|
||||||
test/bin/
|
test/bin/
|
||||||
vendor/
|
vendor/
|
||||||
|
lastversion/
|
||||||
dist/
|
dist/
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.autogen_ssh_key
|
||||||
|
client.crt
|
||||||
|
client.key
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
*.swp
|
*.swp
|
||||||
|
111
.golangci.yml
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
version: "2"
|
||||||
|
run:
|
||||||
|
concurrency: 4
|
||||||
|
build-tags:
|
||||||
|
- integ
|
||||||
|
- integfuzz
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- copyloopvar
|
||||||
|
- errcheck
|
||||||
|
- gocritic
|
||||||
|
- gosec
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- lll
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
settings:
|
||||||
|
errcheck:
|
||||||
|
check-type-assertions: false
|
||||||
|
check-blank: false
|
||||||
|
gocritic:
|
||||||
|
disabled-checks:
|
||||||
|
- exitAfterDefer
|
||||||
|
gosec:
|
||||||
|
excludes:
|
||||||
|
- G401
|
||||||
|
- G402
|
||||||
|
- G404
|
||||||
|
- G501
|
||||||
|
- G115
|
||||||
|
severity: low
|
||||||
|
confidence: low
|
||||||
|
govet:
|
||||||
|
disable:
|
||||||
|
- shadow
|
||||||
|
lll:
|
||||||
|
line-length: 160
|
||||||
|
tab-width: 1
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
ignore-rules:
|
||||||
|
- cancelled
|
||||||
|
- marshalled
|
||||||
|
unparam:
|
||||||
|
check-exported: false
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
rules:
|
||||||
|
- linters:
|
||||||
|
- errcheck
|
||||||
|
- maligned
|
||||||
|
path: _test\.go$|^tests/|^samples/
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
text: use underscores in Go names
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: unused-parameter
|
||||||
|
- linters:
|
||||||
|
- unparam
|
||||||
|
text: is always false
|
||||||
|
paths:
|
||||||
|
- .*\.pb\.go
|
||||||
|
- .*\.gen\.go
|
||||||
|
- genfiles$
|
||||||
|
- vendor$
|
||||||
|
- bin$
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(github.com/fatedier/frp/)
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- .*\.pb\.go
|
||||||
|
- .*\.gen\.go
|
||||||
|
- genfiles$
|
||||||
|
- vendor$
|
||||||
|
- bin$
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
issues:
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
@ -1,7 +1,7 @@
|
|||||||
builds:
|
builds:
|
||||||
- skip: true
|
- skip: true
|
||||||
checksum:
|
checksum:
|
||||||
name_template: '{{ .ProjectName }}_{{ .Version }}_sha256_checksums.txt'
|
name_template: '{{ .ProjectName }}_sha256_checksums.txt'
|
||||||
algorithm: sha256
|
algorithm: sha256
|
||||||
extra_files:
|
extra_files:
|
||||||
- glob: ./release/packages/*
|
- glob: ./release/packages/*
|
||||||
|
37
Makefile
@ -1,11 +1,14 @@
|
|||||||
export PATH := $(GOPATH)/bin:$(PATH)
|
export PATH := $(PATH):`go env GOPATH`/bin
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
all: fmt build
|
all: env fmt build
|
||||||
|
|
||||||
build: frps frpc
|
build: frps frpc
|
||||||
|
|
||||||
|
env:
|
||||||
|
@go version
|
||||||
|
|
||||||
# compile assets into binary file
|
# compile assets into binary file
|
||||||
file:
|
file:
|
||||||
rm -rf ./assets/frps/static/*
|
rm -rf ./assets/frps/static/*
|
||||||
@ -16,11 +19,20 @@ file:
|
|||||||
fmt:
|
fmt:
|
||||||
go 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:
|
frps:
|
||||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o bin/frps ./cmd/frps
|
||||||
|
|
||||||
frpc:
|
frpc:
|
||||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc
|
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o bin/frpc ./cmd/frpc
|
||||||
|
|
||||||
test: gotest
|
test: gotest
|
||||||
|
|
||||||
@ -37,8 +49,23 @@ e2e:
|
|||||||
e2e-trace:
|
e2e-trace:
|
||||||
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
||||||
|
|
||||||
alltest: gotest e2e
|
e2e-compatibility-last-frpc:
|
||||||
|
if [ ! -d "./lastversion" ]; then \
|
||||||
|
TARGET_DIRNAME=lastversion ./hack/download.sh; \
|
||||||
|
fi
|
||||||
|
FRPC_PATH="`pwd`/lastversion/frpc" ./hack/run-e2e.sh
|
||||||
|
rm -r ./lastversion
|
||||||
|
|
||||||
|
e2e-compatibility-last-frps:
|
||||||
|
if [ ! -d "./lastversion" ]; then \
|
||||||
|
TARGET_DIRNAME=lastversion ./hack/download.sh; \
|
||||||
|
fi
|
||||||
|
FRPS_PATH="`pwd`/lastversion/frps" ./hack/run-e2e.sh
|
||||||
|
rm -r ./lastversion
|
||||||
|
|
||||||
|
alltest: vet gotest e2e
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ./bin/frpc
|
rm -f ./bin/frpc
|
||||||
rm -f ./bin/frps
|
rm -f ./bin/frps
|
||||||
|
rm -rf ./lastversion
|
||||||
|
@ -1,25 +1,37 @@
|
|||||||
export PATH := $(GOPATH)/bin:$(PATH)
|
export PATH := $(PATH):`go env GOPATH`/bin
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat
|
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 openbsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 linux:loong64 android:arm64
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build: app
|
build: app
|
||||||
|
|
||||||
app:
|
app:
|
||||||
@$(foreach n, $(os-archs),\
|
@$(foreach n, $(os-archs), \
|
||||||
os=$(shell echo "$(n)" | cut -d : -f 1);\
|
os=$(shell echo "$(n)" | cut -d : -f 1); \
|
||||||
arch=$(shell echo "$(n)" | cut -d : -f 2);\
|
arch=$(shell echo "$(n)" | cut -d : -f 2); \
|
||||||
gomips=$(shell echo "$(n)" | cut -d : -f 3);\
|
extra=$(shell echo "$(n)" | cut -d : -f 3); \
|
||||||
target_suffix=$${os}_$${arch};\
|
flags=''; \
|
||||||
echo "Build $${os}-$${arch}...";\
|
target_suffix=$${os}_$${arch}; \
|
||||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\
|
if [ "$${os}" = "linux" ] && [ "$${arch}" = "arm" ] && [ "$${extra}" != "" ] ; then \
|
||||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\
|
if [ "$${extra}" = "7" ]; then \
|
||||||
echo "Build $${os}-$${arch} done";\
|
flags=GOARM=7; \
|
||||||
|
target_suffix=$${os}_arm_hf; \
|
||||||
|
elif [ "$${extra}" = "5" ]; then \
|
||||||
|
flags=GOARM=5; \
|
||||||
|
target_suffix=$${os}_arm; \
|
||||||
|
fi; \
|
||||||
|
elif [ "$${os}" = "linux" ] && ([ "$${arch}" = "mips" ] || [ "$${arch}" = "mipsle" ]) && [ "$${extra}" != "" ] ; then \
|
||||||
|
flags=GOMIPS=$${extra}; \
|
||||||
|
fi; \
|
||||||
|
echo "Build $${os}-$${arch}$${extra:+ ($${extra})}..."; \
|
||||||
|
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $${flags} go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o ./release/frpc_$${target_suffix} ./cmd/frpc; \
|
||||||
|
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $${flags} go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o ./release/frps_$${target_suffix} ./cmd/frps; \
|
||||||
|
echo "Build $${os}-$${arch}$${extra:+ ($${extra})} done"; \
|
||||||
)
|
)
|
||||||
@mv ./release/frpc_windows_386 ./release/frpc_windows_386.exe
|
|
||||||
@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
|
|
||||||
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
||||||
@mv ./release/frps_windows_amd64 ./release/frps_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
|
||||||
|
87
README_zh.md
@ -1,48 +1,48 @@
|
|||||||
# frp
|
# frp
|
||||||
|
|
||||||
[](https://travis-ci.org/fatedier/frp)
|
[](https://circleci.com/gh/fatedier/frp)
|
||||||
[](https://github.com/fatedier/frp/releases)
|
[](https://github.com/fatedier/frp/releases)
|
||||||
|
[](https://goreportcard.com/report/github.com/fatedier/frp)
|
||||||
|
[](https://somsubhra.github.io/github-release-stats/?username=fatedier&repository=frp)
|
||||||
|
|
||||||
[README](README.md) | [中文文档](README_zh.md)
|
[README](README.md) | [中文文档](README_zh.md)
|
||||||
|
|
||||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||||
|
|
||||||
<h3 align="center">Platinum Sponsors</h3>
|
## Sponsors
|
||||||
<!--platinum sponsors start-->
|
|
||||||
|
|
||||||
<p align="center">
|
frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者们的支持。如果你愿意加入他们的行列,请考虑 [赞助 frp 的开发](https://github.com/sponsors/fatedier)。
|
||||||
<a href="https://www.doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_doppler.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!--platinum sponsors end-->
|
|
||||||
|
|
||||||
<h3 align="center">Gold Sponsors</h3>
|
<h3 align="center">Gold Sponsors</h3>
|
||||||
<!--gold sponsors start-->
|
<!--gold sponsors start-->
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://jb.gg/frp" target="_blank">
|
||||||
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||||
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||||
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
<h3 align="center">Silver Sponsors</h3>
|
|
||||||
|
|
||||||
* Sakura Frp - 欢迎点击 "加入我们"
|
|
||||||
|
|
||||||
## 为什么使用 frp ?
|
## 为什么使用 frp ?
|
||||||
|
|
||||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
||||||
|
|
||||||
* 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。
|
* 客户端服务端通信支持 TCP、QUIC、KCP 以及 Websocket 等多种协议。
|
||||||
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。
|
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间,降低请求延迟。
|
||||||
* 代理组间的负载均衡。
|
* 代理组间的负载均衡。
|
||||||
* 端口复用,多个服务通过同一个服务端端口暴露。
|
* 端口复用,多个服务通过同一个服务端端口暴露。
|
||||||
* 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
|
* 支持 P2P 通信,流量不经过服务器中转,充分利用带宽资源。
|
||||||
* 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。
|
* 多个原生支持的客户端插件(静态文件查看,HTTPS/HTTP 协议转换,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
|
||||||
|
* 高度扩展性的服务端插件系统,易于结合自身需求进行功能扩展。
|
||||||
* 服务端和客户端 UI 页面。
|
* 服务端和客户端 UI 页面。
|
||||||
|
|
||||||
## 开发状态
|
## 开发状态
|
||||||
@ -51,13 +51,25 @@ frp 目前已被很多公司广泛用于测试、生产环境。
|
|||||||
|
|
||||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||||
|
|
||||||
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
|
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续较长的一段时间。
|
||||||
|
|
||||||
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
||||||
|
|
||||||
|
### 关于 v2 的一些说明
|
||||||
|
|
||||||
|
v2 版本的复杂度和难度比我们预期的要高得多。我只能利用零散的时间进行开发,而且由于上下文经常被打断,效率极低。由于这种情况可能会持续一段时间,我们仍然会在当前版本上进行一些优化和迭代,直到我们有更多空闲时间来推进大版本的重构,或者也有可能放弃一次性的重构,而是采用渐进的方式在当前版本上逐步做一些可能会导致不兼容的修改。
|
||||||
|
|
||||||
|
v2 的构想是基于我多年在云原生领域,特别是在 K8s 和 ServiceMesh 方面的工作经验和思考。它的核心是一个现代化的四层和七层代理,类似于 envoy。这个代理本身高度可扩展,不仅可以用于实现内网穿透的功能,还可以应用于更多领域。在这个高度可扩展的内核基础上,我们将实现 frp v1 中的所有功能,并且能够以一种更加优雅的方式实现原先架构中无法实现或不易实现的功能。同时,我们将保持高效的开发和迭代能力。
|
||||||
|
|
||||||
|
除此之外,我希望 frp 本身也成为一个高度可扩展的系统和平台,就像我们可以基于 K8s 提供一系列扩展能力一样。在 K8s 上,我们可以根据企业需求进行定制化开发,例如使用 CRD、controller 模式、webhook、CSI 和 CNI 等。在 frp v1 中,我们引入了服务端插件的概念,实现了一些简单的扩展性。但是,它实际上依赖于简单的 HTTP 协议,并且需要用户自己启动独立的进程和管理。这种方式远远不够灵活和方便,而且现实世界的需求千差万别,我们不能期望一个由少数人维护的非营利性开源项目能够满足所有人的需求。
|
||||||
|
|
||||||
|
最后,我们意识到像配置管理、权限验证、证书管理和管理 API 等模块的当前设计并不够现代化。尽管我们可能在 v1 版本中进行一些优化,但确保兼容性是一个令人头疼的问题,需要投入大量精力来解决。
|
||||||
|
|
||||||
|
非常感谢您对 frp 的支持。
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org)。
|
||||||
|
|
||||||
## 为 frp 做贡献
|
## 为 frp 做贡献
|
||||||
|
|
||||||
@ -70,28 +82,29 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
|
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
|
||||||
* 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。
|
* 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。
|
||||||
|
|
||||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
**提醒:和项目相关的问题请在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
||||||
|
|
||||||
## 捐助
|
## 关联项目
|
||||||
|
|
||||||
|
* [gofrp/plugin](https://github.com/gofrp/plugin) - frp 插件仓库,收录了基于 frp 扩展机制实现的各种插件,满足各种场景下的定制化需求。
|
||||||
|
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - 基于 ssh 协议实现的 frp 客户端的精简版本(最低约 3.5MB 左右),支持常用的部分功能,适用于资源有限的设备。
|
||||||
|
|
||||||
|
## 赞助
|
||||||
|
|
||||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||||
|
|
||||||
### GitHub Sponsors
|
### Sponsors
|
||||||
|
|
||||||
|
长期赞助可以帮助我们保持项目的持续发展。
|
||||||
|
|
||||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||||
|
|
||||||
|
国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。
|
||||||
|
|
||||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||||
|
|
||||||
### 知识星球
|
### 知识星球
|
||||||
|
|
||||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何 frp 使用方面的帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 支付宝扫码捐赠
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 微信支付捐赠
|
|
||||||
|
|
||||||

|
|
||||||
|
10
Release.md
@ -1,8 +1,4 @@
|
|||||||
### New
|
## Features
|
||||||
|
|
||||||
* Support go http pprof.
|
* Support for YAML merge functionality (anchors and references with dot-prefixed fields) in strict configuration mode without requiring `--strict-config=false` parameter.
|
||||||
|
* Support for proxy protocol in UDP proxies to preserve real client IP addresses.
|
||||||
### Improve
|
|
||||||
|
|
||||||
* Change underlying TCP connection keepalive interval to 2 hours.
|
|
||||||
* Create new connection to server for `sudp` visitor when needed, to avoid frequent reconnections.
|
|
42
assets/frpc/static/index-bLBhaJo8.js
Normal file
1
assets/frpc/static/index-iuf46MlF.css
Normal file
@ -1 +1,15 @@
|
|||||||
<!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-bLBhaJo8.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="./index-iuf46MlF.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}}([]);
|
|
84
assets/frps/static/index-82-40HIG.js
Normal file
1
assets/frps/static/index-rzPDshRD.css
Normal file
@ -1 +1,15 @@
|
|||||||
<!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-82-40HIG.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="./index-rzPDshRD.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}}([]);
|
|
@ -1,82 +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 (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/pprof"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
httpServerReadTimeout = 60 * time.Second
|
|
||||||
httpServerWriteTimeout = 60 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
|
||||||
// url router
|
|
||||||
router := mux.NewRouter()
|
|
||||||
|
|
||||||
router.HandleFunc("/healthz", svr.healthz)
|
|
||||||
|
|
||||||
// debug
|
|
||||||
if svr.cfg.PprofEnable {
|
|
||||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
||||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
||||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
||||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
||||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
subRouter := router.NewRoute().Subrouter()
|
|
||||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
|
||||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
|
||||||
|
|
||||||
// api, see admin_api.go
|
|
||||||
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
|
||||||
subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
|
||||||
subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
|
||||||
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
|
||||||
|
|
||||||
// view
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: address,
|
|
||||||
Handler: router,
|
|
||||||
ReadTimeout: httpServerReadTimeout,
|
|
||||||
WriteTimeout: httpServerWriteTimeout,
|
|
||||||
}
|
|
||||||
if address == "" {
|
|
||||||
address = ":http"
|
|
||||||
}
|
|
||||||
ln, err := net.Listen("tcp", address)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go server.Serve(ln)
|
|
||||||
return
|
|
||||||
}
|
|
@ -15,17 +15,23 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
@ -33,52 +39,93 @@ type GeneralResponse struct {
|
|||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
|
||||||
|
helper.Router.HandleFunc("/healthz", svr.healthz)
|
||||||
|
subRouter := helper.Router.NewRoute().Subrouter()
|
||||||
|
|
||||||
|
subRouter.Use(helper.AuthMiddleware.Middleware)
|
||||||
|
|
||||||
|
// api, see admin_api.go
|
||||||
|
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/stop", svr.apiStop).Methods("POST")
|
||||||
|
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
|
||||||
|
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||||
|
subRouter.PathPrefix("/static/").Handler(
|
||||||
|
netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
|
||||||
|
).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// /healthz
|
// /healthz
|
||||||
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/reload
|
// GET /api/reload
|
||||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
strictConfigMode := false
|
||||||
|
strictStr := r.URL.Query().Get("strictConfig")
|
||||||
|
if strictStr != "" {
|
||||||
|
strictConfigMode, _ = strconv.ParseBool(strictStr)
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("api request [/api/reload]")
|
log.Infof("api request [/api/reload]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("api response [/api/reload], code [%d]", res.Code)
|
log.Infof("api response [/api/reload], code [%d]", res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(svr.cfgFile)
|
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.configFilePath, strictConfigMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
log.Warnf("reload frpc proxy config error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs); err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warnf("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
|
if err := svr.UpdateAllConfigurer(proxyCfgs, visitorCfgs); err != nil {
|
||||||
res.Code = 500
|
res.Code = 500
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
log.Warnf("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("success reload conf")
|
log.Infof("success reload conf")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusResp struct {
|
// POST /api/stop
|
||||||
TCP []ProxyStatusResp `json:"tcp"`
|
func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
|
||||||
UDP []ProxyStatusResp `json:"udp"`
|
res := GeneralResponse{Code: 200}
|
||||||
HTTP []ProxyStatusResp `json:"http"`
|
|
||||||
HTTPS []ProxyStatusResp `json:"https"`
|
log.Infof("api request [/api/stop]")
|
||||||
STCP []ProxyStatusResp `json:"stcp"`
|
defer func() {
|
||||||
XTCP []ProxyStatusResp `json:"xtcp"`
|
log.Infof("api response [/api/stop], code [%d]", res.Code)
|
||||||
SUDP []ProxyStatusResp `json:"sudp"`
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go svr.GracefulClose(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StatusResp map[string][]ProxyStatusResp
|
||||||
|
|
||||||
type ProxyStatusResp struct {
|
type ProxyStatusResp struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@ -89,12 +136,6 @@ type ProxyStatusResp struct {
|
|||||||
RemoteAddr string `json:"remote_addr"`
|
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 {
|
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
|
||||||
psr := ProxyStatusResp{
|
psr := ProxyStatusResp{
|
||||||
Name: status.Name,
|
Name: status.Name,
|
||||||
@ -102,157 +143,97 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
Status: status.Phase,
|
Status: status.Phase,
|
||||||
Err: status.Err,
|
Err: status.Err,
|
||||||
}
|
}
|
||||||
switch cfg := status.Cfg.(type) {
|
baseCfg := status.Cfg.GetBaseConfig()
|
||||||
case *config.TCPProxyConf:
|
if baseCfg.LocalPort != 0 {
|
||||||
if cfg.LocalPort != 0 {
|
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
}
|
||||||
}
|
psr.Plugin = baseCfg.Plugin.Type
|
||||||
psr.Plugin = cfg.Plugin
|
|
||||||
if status.Err != "" {
|
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
|
|
||||||
psr.RemoteAddr = status.RemoteAddr
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
case *config.HTTPSProxyConf:
|
if slices.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||||
if cfg.LocalPort != 0 {
|
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
|
||||||
}
|
}
|
||||||
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
|
return psr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/status
|
// GET /api/status
|
||||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
||||||
var (
|
var (
|
||||||
buf []byte
|
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]")
|
log.Infof("http request [/api/status]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [/api/status]")
|
log.Infof("http response [/api/status]")
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ = json.Marshal(&res)
|
||||||
w.Write(buf)
|
_, _ = w.Write(buf)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
svr.ctlMu.RLock()
|
||||||
for _, status := range ps {
|
ctl := svr.ctl
|
||||||
switch status.Type {
|
svr.ctlMu.RUnlock()
|
||||||
case "tcp":
|
if ctl == nil {
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
res := GeneralResponse{Code: 200}
|
|
||||||
|
|
||||||
log.Info("Http get request [/api/config]")
|
|
||||||
defer func() {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if svr.cfgFile == "" {
|
|
||||||
res.Code = 400
|
|
||||||
res.Msg = "frpc has no config file path"
|
|
||||||
log.Warn("%s", res.Msg)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
ps := ctl.pm.GetAllProxyStatus()
|
||||||
|
for _, status := range ps {
|
||||||
|
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.common.ServerAddr))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arrs := range res {
|
||||||
|
if len(arrs) <= 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slices.SortFunc(arrs, func(a, b ProxyStatusResp) int {
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/config
|
||||||
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Infof("http get request [/api/config]")
|
||||||
|
defer func() {
|
||||||
|
log.Infof("http get response [/api/config], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if svr.configFilePath == "" {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "frpc has no config file path"
|
||||||
|
log.Warnf("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := os.ReadFile(svr.configFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Warn("load frpc config file error: %s", res.Msg)
|
log.Warnf("load frpc config file error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
res.Msg = string(content)
|
||||||
rows := strings.Split(string(content), "\n")
|
|
||||||
newRows := make([]string, 0, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
row = strings.TrimSpace(row)
|
|
||||||
if strings.HasPrefix(row, "token") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newRows = append(newRows, row)
|
|
||||||
}
|
|
||||||
res.Msg = strings.Join(newRows, "\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT api/config
|
// PUT /api/config
|
||||||
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Info("Http put request [/api/config]")
|
log.Infof("http put request [/api/config]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
log.Infof("http put response [/api/config], code [%d]", res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -261,63 +242,21 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||||
log.Warn("%s", res.Msg)
|
log.Warnf("%s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = "body can't be empty"
|
res.Msg = "body can't be empty"
|
||||||
log.Warn("%s", res.Msg)
|
log.Warnf("%s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get token from origin content
|
if err := os.WriteFile(svr.configFilePath, body, 0o600); err != nil {
|
||||||
token := ""
|
|
||||||
b, err := os.ReadFile(svr.cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
res.Code = 400
|
|
||||||
res.Msg = err.Error()
|
|
||||||
log.Warn("load frpc config file error: %s", res.Msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
content := string(b)
|
|
||||||
|
|
||||||
for _, row := range strings.Split(content, "\n") {
|
|
||||||
row = strings.TrimSpace(row)
|
|
||||||
if strings.HasPrefix(row, "token") {
|
|
||||||
token = row
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpRows := make([]string, 0)
|
|
||||||
for _, row := range strings.Split(string(body), "\n") {
|
|
||||||
row = strings.TrimSpace(row)
|
|
||||||
if strings.HasPrefix(row, "token") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tmpRows = append(tmpRows, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
newRows := make([]string, 0)
|
|
||||||
if token != "" {
|
|
||||||
for _, row := range tmpRows {
|
|
||||||
newRows = append(newRows, row)
|
|
||||||
if strings.HasPrefix(row, "[common]") {
|
|
||||||
newRows = append(newRows, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newRows = tmpRows
|
|
||||||
}
|
|
||||||
content = strings.Join(newRows, "\n")
|
|
||||||
|
|
||||||
err = os.WriteFile(svr.cfgFile, []byte(content), 0644)
|
|
||||||
if err != nil {
|
|
||||||
res.Code = 500
|
res.Code = 500
|
||||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||||
log.Warn("%s", res.Msg)
|
log.Warnf("%s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
227
client/connector.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
// 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 client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libnet "github.com/fatedier/golib/net"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
quic "github.com/quic-go/quic-go"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector is an interface for establishing connections to the server.
|
||||||
|
type Connector interface {
|
||||||
|
Open() error
|
||||||
|
Connect() (net.Conn, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultConnectorImpl is the default implementation of Connector for normal frpc.
|
||||||
|
type defaultConnectorImpl struct {
|
||||||
|
ctx context.Context
|
||||||
|
cfg *v1.ClientCommonConfig
|
||||||
|
|
||||||
|
muxSession *fmux.Session
|
||||||
|
quicConn quic.Connection
|
||||||
|
closeOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnector(ctx context.Context, cfg *v1.ClientCommonConfig) Connector {
|
||||||
|
return &defaultConnectorImpl{
|
||||||
|
ctx: ctx,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens an underlying connection to the server.
|
||||||
|
// The underlying connection is either a TCP connection or a QUIC connection.
|
||||||
|
// After the underlying connection is established, you can call Connect() to get a stream.
|
||||||
|
// If TCPMux isn't enabled, the underlying connection is nil, you will get a new real TCP connection every time you call Connect().
|
||||||
|
func (c *defaultConnectorImpl) Open() error {
|
||||||
|
xl := xlog.FromContextSafe(c.ctx)
|
||||||
|
|
||||||
|
// special for quic
|
||||||
|
if strings.EqualFold(c.cfg.Transport.Protocol, "quic") {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
var err error
|
||||||
|
sn := c.cfg.Transport.TLS.ServerName
|
||||||
|
if sn == "" {
|
||||||
|
sn = c.cfg.ServerAddr
|
||||||
|
}
|
||||||
|
if lo.FromPtr(c.cfg.Transport.TLS.Enable) {
|
||||||
|
tlsConfig, err = transport.NewClientTLSConfig(
|
||||||
|
c.cfg.Transport.TLS.CertFile,
|
||||||
|
c.cfg.Transport.TLS.KeyFile,
|
||||||
|
c.cfg.Transport.TLS.TrustedCaFile,
|
||||||
|
sn)
|
||||||
|
} else {
|
||||||
|
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("fail to build tls configuration, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
|
||||||
|
conn, err := quic.DialAddr(
|
||||||
|
c.ctx,
|
||||||
|
net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
|
||||||
|
tlsConfig, &quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(c.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(c.cfg.Transport.QUIC.MaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(c.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.quicConn = conn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.FromPtr(c.cfg.Transport.TCPMux) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := c.realConnect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = time.Duration(c.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
|
||||||
|
fmuxCfg.LogOutput = io.Discard
|
||||||
|
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||||
|
session, err := fmux.Client(conn, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.muxSession = session
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect returns a stream from the underlying connection, or a new TCP connection if TCPMux isn't enabled.
|
||||||
|
func (c *defaultConnectorImpl) Connect() (net.Conn, error) {
|
||||||
|
if c.quicConn != nil {
|
||||||
|
stream, err := c.quicConn.OpenStreamSync(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netpkg.QuicStreamToNetConn(stream, c.quicConn), nil
|
||||||
|
} else if c.muxSession != nil {
|
||||||
|
stream, err := c.muxSession.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stream, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.realConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *defaultConnectorImpl) realConnect() (net.Conn, error) {
|
||||||
|
xl := xlog.FromContextSafe(c.ctx)
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
var err error
|
||||||
|
tlsEnable := lo.FromPtr(c.cfg.Transport.TLS.Enable)
|
||||||
|
if c.cfg.Transport.Protocol == "wss" {
|
||||||
|
tlsEnable = true
|
||||||
|
}
|
||||||
|
if tlsEnable {
|
||||||
|
sn := c.cfg.Transport.TLS.ServerName
|
||||||
|
if sn == "" {
|
||||||
|
sn = c.cfg.ServerAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err = transport.NewClientTLSConfig(
|
||||||
|
c.cfg.Transport.TLS.CertFile,
|
||||||
|
c.cfg.Transport.TLS.KeyFile,
|
||||||
|
c.cfg.Transport.TLS.TrustedCaFile,
|
||||||
|
sn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("fail to build tls configuration, err: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyType, addr, auth, err := libnet.ParseProxyURL(c.cfg.Transport.ProxyURL)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("fail to parse proxy url")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dialOptions := []libnet.DialOption{}
|
||||||
|
protocol := c.cfg.Transport.Protocol
|
||||||
|
switch protocol {
|
||||||
|
case "websocket":
|
||||||
|
protocol = "tcp"
|
||||||
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, "")}))
|
||||||
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{
|
||||||
|
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||||
|
}))
|
||||||
|
dialOptions = append(dialOptions, libnet.WithTLSConfig(tlsConfig))
|
||||||
|
case "wss":
|
||||||
|
protocol = "tcp"
|
||||||
|
dialOptions = append(dialOptions, libnet.WithTLSConfigAndPriority(100, tlsConfig))
|
||||||
|
// Make sure that if it is wss, the websocket hook is executed after the tls hook.
|
||||||
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
|
||||||
|
default:
|
||||||
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{
|
||||||
|
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||||
|
}))
|
||||||
|
dialOptions = append(dialOptions, libnet.WithTLSConfig(tlsConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.cfg.Transport.ConnectServerLocalIP != "" {
|
||||||
|
dialOptions = append(dialOptions, libnet.WithLocalAddr(c.cfg.Transport.ConnectServerLocalIP))
|
||||||
|
}
|
||||||
|
dialOptions = append(dialOptions,
|
||||||
|
libnet.WithProtocol(protocol),
|
||||||
|
libnet.WithTimeout(time.Duration(c.cfg.Transport.DialServerTimeout)*time.Second),
|
||||||
|
libnet.WithKeepAlive(time.Duration(c.cfg.Transport.DialServerKeepAlive)*time.Second),
|
||||||
|
libnet.WithProxy(proxyType, addr),
|
||||||
|
libnet.WithProxyAuth(auth),
|
||||||
|
)
|
||||||
|
conn, err := libnet.DialContext(
|
||||||
|
c.ctx,
|
||||||
|
net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
|
||||||
|
dialOptions...,
|
||||||
|
)
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *defaultConnectorImpl) Close() error {
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
if c.quicConn != nil {
|
||||||
|
_ = c.quicConn.CloseWithError(0, "")
|
||||||
|
}
|
||||||
|
if c.muxSession != nil {
|
||||||
|
_ = c.muxSession.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
@ -16,152 +16,142 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"runtime/debug"
|
"sync/atomic"
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
|
"github.com/fatedier/frp/client/visitor"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/wait"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
"github.com/fatedier/golib/control/shutdown"
|
|
||||||
"github.com/fatedier/golib/crypto"
|
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SessionContext struct {
|
||||||
|
// The client common configuration.
|
||||||
|
Common *v1.ClientCommonConfig
|
||||||
|
|
||||||
|
// Unique ID obtained from frps.
|
||||||
|
// It should be attached to the login message when reconnecting.
|
||||||
|
RunID string
|
||||||
|
// Underlying control connection. Once conn is closed, the msgDispatcher and the entire Control will exit.
|
||||||
|
Conn net.Conn
|
||||||
|
// Indicates whether the connection is encrypted.
|
||||||
|
ConnEncrypted bool
|
||||||
|
// Sets authentication based on selected method
|
||||||
|
AuthSetter auth.Setter
|
||||||
|
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
||||||
|
Connector Connector
|
||||||
|
// Virtual net controller
|
||||||
|
VnetController *vnet.Controller
|
||||||
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
// uniq id got from frps, attach it in loginMsg
|
|
||||||
runID string
|
|
||||||
|
|
||||||
// manage all proxies
|
|
||||||
pxyCfgs map[string]config.ProxyConf
|
|
||||||
pm *proxy.Manager
|
|
||||||
|
|
||||||
// manage all visitors
|
|
||||||
vm *VisitorManager
|
|
||||||
|
|
||||||
// control connection
|
|
||||||
conn net.Conn
|
|
||||||
|
|
||||||
// tcp stream multiplexing, if enabled
|
|
||||||
session *fmux.Session
|
|
||||||
|
|
||||||
// put a message in this channel to send it over control connection to server
|
|
||||||
sendCh chan (msg.Message)
|
|
||||||
|
|
||||||
// read from this channel to get the next message sent by server
|
|
||||||
readCh chan (msg.Message)
|
|
||||||
|
|
||||||
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
|
||||||
closedCh chan struct{}
|
|
||||||
|
|
||||||
closedDoneCh chan struct{}
|
|
||||||
|
|
||||||
// last time got the Pong message
|
|
||||||
lastPong time.Time
|
|
||||||
|
|
||||||
// The client configuration
|
|
||||||
clientCfg config.ClientCommonConf
|
|
||||||
|
|
||||||
readerShutdown *shutdown.Shutdown
|
|
||||||
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
|
// service context
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
xl *xlog.Logger
|
||||||
|
|
||||||
// sets authentication based on selected method
|
// session context
|
||||||
authSetter auth.Setter
|
sessionCtx *SessionContext
|
||||||
|
|
||||||
|
// manage all proxies
|
||||||
|
pm *proxy.Manager
|
||||||
|
|
||||||
|
// manage all visitors
|
||||||
|
vm *visitor.Manager
|
||||||
|
|
||||||
|
doneCh chan struct{}
|
||||||
|
|
||||||
|
// of time.Time, last time got the Pong message
|
||||||
|
lastPong atomic.Value
|
||||||
|
|
||||||
|
// The role of msgTransporter is similar to HTTP2.
|
||||||
|
// It allows multiple messages to be sent simultaneously on the same control connection.
|
||||||
|
// The server's response messages will be dispatched to the corresponding waiting goroutines based on the laneKey and message type.
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
|
// msgDispatcher is a wrapper for control connection.
|
||||||
|
// It provides a channel for sending messages, and you can register handlers to process messages based on their respective types.
|
||||||
|
msgDispatcher *msg.Dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(ctx context.Context, runID string, conn net.Conn, session *fmux.Session,
|
func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, error) {
|
||||||
clientCfg config.ClientCommonConf,
|
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
|
||||||
serverUDPPort int,
|
|
||||||
authSetter auth.Setter) *Control {
|
|
||||||
|
|
||||||
// new xlog instance
|
// new xlog instance
|
||||||
ctl := &Control{
|
ctl := &Control{
|
||||||
runID: runID,
|
ctx: ctx,
|
||||||
conn: conn,
|
xl: xlog.FromContextSafe(ctx),
|
||||||
session: session,
|
sessionCtx: sessionCtx,
|
||||||
pxyCfgs: pxyCfgs,
|
doneCh: make(chan struct{}),
|
||||||
sendCh: make(chan msg.Message, 100),
|
|
||||||
readCh: make(chan msg.Message, 100),
|
|
||||||
closedCh: make(chan struct{}),
|
|
||||||
closedDoneCh: make(chan struct{}),
|
|
||||||
clientCfg: clientCfg,
|
|
||||||
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.lastPong.Store(time.Now())
|
||||||
|
|
||||||
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
|
if sessionCtx.ConnEncrypted {
|
||||||
ctl.vm.Reload(visitorCfgs)
|
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, []byte(sessionCtx.Common.Auth.Token))
|
||||||
return ctl
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctl.msgDispatcher = msg.NewDispatcher(cryptoRW)
|
||||||
|
} else {
|
||||||
|
ctl.msgDispatcher = msg.NewDispatcher(sessionCtx.Conn)
|
||||||
|
}
|
||||||
|
ctl.registerMsgHandlers()
|
||||||
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
||||||
|
|
||||||
|
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
|
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
|
||||||
|
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
|
return ctl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) Run() {
|
func (ctl *Control) Run(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) {
|
||||||
go ctl.worker()
|
go ctl.worker()
|
||||||
|
|
||||||
// start all proxies
|
// start all proxies
|
||||||
ctl.pm.Reload(ctl.pxyCfgs)
|
ctl.pm.UpdateAll(proxyCfgs)
|
||||||
|
|
||||||
// start all visitors
|
// start all visitors
|
||||||
go ctl.vm.Run()
|
ctl.vm.UpdateAll(visitorCfgs)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
func (ctl *Control) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||||
|
ctl.pm.SetInWorkConnCallback(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
workConn, err := ctl.connectServer()
|
workConn, err := ctl.connectServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
xl.Warnf("start new connection to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &msg.NewWorkConn{
|
m := &msg.NewWorkConn{
|
||||||
RunID: ctl.runID,
|
RunID: ctl.sessionCtx.RunID,
|
||||||
}
|
}
|
||||||
if err = ctl.authSetter.SetNewWorkConn(m); err != nil {
|
if err = ctl.sessionCtx.AuthSetter.SetNewWorkConn(m); err != nil {
|
||||||
xl.Warn("error during NewWorkConn authentication: %v", err)
|
xl.Warnf("error during NewWorkConn authentication: %v", err)
|
||||||
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = msg.WriteMsg(workConn, m); err != nil {
|
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||||
xl.Warn("work connection write to server error: %v", err)
|
xl.Warnf("work connection write to server error: %v", err)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var startMsg msg.StartWorkConn
|
var startMsg msg.StartWorkConn
|
||||||
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
||||||
xl.Error("work connection closed before response StartWorkConn message: %v", err)
|
xl.Tracef("work connection closed before response StartWorkConn message: %v", err)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if startMsg.Error != "" {
|
if startMsg.Error != "" {
|
||||||
xl.Error("StartWorkConn contains error: %s", startMsg.Error)
|
xl.Errorf("StartWorkConn contains error: %s", startMsg.Error)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -170,18 +160,49 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
|||||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
func (ctl *Control) handleNewProxyResp(m msg.Message) {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
|
inMsg := m.(*msg.NewProxyResp)
|
||||||
// Server will return NewProxyResp message to each NewProxy message.
|
// Server will return NewProxyResp message to each NewProxy message.
|
||||||
// Start a new proxy handler if no error got
|
// Start a new proxy handler if no error got
|
||||||
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
xl.Warnf("[%s] start error: %v", inMsg.ProxyName, err)
|
||||||
} else {
|
} else {
|
||||||
xl.Info("[%s] start proxy success", inMsg.ProxyName)
|
xl.Infof("[%s] start proxy success", inMsg.ProxyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) handleNatHoleResp(m msg.Message) {
|
||||||
|
xl := ctl.xl
|
||||||
|
inMsg := m.(*msg.NatHoleResp)
|
||||||
|
|
||||||
|
// Dispatch the NatHoleResp message to the related proxy.
|
||||||
|
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||||
|
if !ok {
|
||||||
|
xl.Tracef("dispatch NatHoleResp message to related proxy error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) handlePong(m msg.Message) {
|
||||||
|
xl := ctl.xl
|
||||||
|
inMsg := m.(*msg.Pong)
|
||||||
|
|
||||||
|
if inMsg.Error != "" {
|
||||||
|
xl.Errorf("pong message contains error: %s", inMsg.Error)
|
||||||
|
ctl.closeSession()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctl.lastPong.Store(time.Now())
|
||||||
|
xl.Debugf("receive heartbeat from server")
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeSession closes the control connection.
|
||||||
|
func (ctl *Control) closeSession() {
|
||||||
|
ctl.sessionCtx.Conn.Close()
|
||||||
|
ctl.sessionCtx.Connector.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) Close() error {
|
func (ctl *Control) Close() error {
|
||||||
return ctl.GracefulClose(0)
|
return ctl.GracefulClose(0)
|
||||||
}
|
}
|
||||||
@ -192,237 +213,82 @@ func (ctl *Control) GracefulClose(d time.Duration) error {
|
|||||||
|
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
|
|
||||||
ctl.conn.Close()
|
ctl.closeSession()
|
||||||
if ctl.session != nil {
|
|
||||||
ctl.session.Close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClosedDoneCh returns a channel which will be closed after all resources are released
|
// Done returns a channel that will be closed after all resources are released
|
||||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
func (ctl *Control) Done() <-chan struct{} {
|
||||||
return ctl.closedDoneCh
|
return ctl.doneCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// connectServer return a new connection to frps
|
// connectServer return a new connection to frps
|
||||||
func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
func (ctl *Control) connectServer() (net.Conn, error) {
|
||||||
xl := ctl.xl
|
return ctl.sessionCtx.Connector.Connect()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyType, addr, auth, err := libdial.ParseProxyURL(ctl.clientCfg.HTTPProxy)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("fail to parse proxy url")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dialOptions := []libdial.DialOption{}
|
|
||||||
protocol := ctl.clientCfg.Protocol
|
|
||||||
if protocol == "websocket" {
|
|
||||||
protocol = "tcp"
|
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
|
||||||
}
|
|
||||||
if ctl.clientCfg.ConnectServerLocalIP != "" {
|
|
||||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(ctl.clientCfg.ConnectServerLocalIP))
|
|
||||||
}
|
|
||||||
dialOptions = append(dialOptions,
|
|
||||||
libdial.WithProtocol(protocol),
|
|
||||||
libdial.WithTimeout(time.Duration(ctl.clientCfg.DialServerTimeout)*time.Second),
|
|
||||||
libdial.WithKeepAlive(time.Duration(ctl.clientCfg.DialServerKeepAlive)*time.Second),
|
|
||||||
libdial.WithProxy(proxyType, addr),
|
|
||||||
libdial.WithProxyAuth(auth),
|
|
||||||
libdial.WithTLSConfig(tlsConfig),
|
|
||||||
libdial.WithAfterHook(libdial.AfterHook{
|
|
||||||
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, ctl.clientCfg.DisableCustomTLSFirstByte),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
conn, err = libdial.Dial(
|
|
||||||
net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort)),
|
|
||||||
dialOptions...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("start new connection to server error: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reader read all messages from frps and send to readCh
|
func (ctl *Control) registerMsgHandlers() {
|
||||||
func (ctl *Control) reader() {
|
ctl.msgDispatcher.RegisterHandler(&msg.ReqWorkConn{}, msg.AsyncHandler(ctl.handleReqWorkConn))
|
||||||
xl := ctl.xl
|
ctl.msgDispatcher.RegisterHandler(&msg.NewProxyResp{}, ctl.handleNewProxyResp)
|
||||||
defer func() {
|
ctl.msgDispatcher.RegisterHandler(&msg.NatHoleResp{}, ctl.handleNatHoleResp)
|
||||||
if err := recover(); err != nil {
|
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
||||||
xl.Error("panic error: %v", err)
|
|
||||||
xl.Error(string(debug.Stack()))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer ctl.readerShutdown.Done()
|
|
||||||
defer close(ctl.closedCh)
|
|
||||||
|
|
||||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
|
|
||||||
for {
|
|
||||||
m, err := msg.ReadMsg(encReader)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
xl.Debug("read from control connection EOF")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
xl.Warn("read error: %v", err)
|
|
||||||
ctl.conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctl.readCh <- m
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writer writes messages got from sendCh to frps
|
// heartbeatWorker sends heartbeat to server and check heartbeat timeout.
|
||||||
func (ctl *Control) writer() {
|
func (ctl *Control) heartbeatWorker() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer ctl.writerShutdown.Done()
|
|
||||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("crypto new writer error: %v", err)
|
|
||||||
ctl.conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
m, ok := <-ctl.sendCh
|
|
||||||
if !ok {
|
|
||||||
xl.Info("control writer is closing")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
||||||
xl.Warn("write message to control connection error: %v", err)
|
// Send heartbeat to server.
|
||||||
return
|
sendHeartBeat := func() (bool, error) {
|
||||||
}
|
xl.Debugf("send heartbeat to server")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// msgHandler handles all channel events and do corresponding operations.
|
|
||||||
func (ctl *Control) msgHandler() {
|
|
||||||
xl := ctl.xl
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
xl.Error("panic error: %v", err)
|
|
||||||
xl.Error(string(debug.Stack()))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer ctl.msgHandlerShutdown.Done()
|
|
||||||
|
|
||||||
var hbSendCh <-chan time.Time
|
|
||||||
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
|
||||||
// Just keep it here to keep compatible with old version frps.
|
|
||||||
if ctl.clientCfg.HeartbeatInterval > 0 {
|
|
||||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
|
||||||
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 <-hbSendCh:
|
|
||||||
// send heartbeat to server
|
|
||||||
xl.Debug("send heartbeat to server")
|
|
||||||
pingMsg := &msg.Ping{}
|
pingMsg := &msg.Ping{}
|
||||||
if err := ctl.authSetter.SetPing(pingMsg); err != nil {
|
if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil {
|
||||||
xl.Warn("error during ping authentication: %v", err)
|
xl.Warnf("error during ping authentication: %v, skip sending ping message", err)
|
||||||
return
|
return false, err
|
||||||
}
|
|
||||||
ctl.sendCh <- pingMsg
|
|
||||||
case <-hbCheckCh:
|
|
||||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
|
||||||
xl.Warn("heartbeat timeout")
|
|
||||||
// let reader() stop
|
|
||||||
ctl.conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case rawMsg, ok := <-ctl.readCh:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.ReqWorkConn:
|
|
||||||
go ctl.HandleReqWorkConn(m)
|
|
||||||
case *msg.NewProxyResp:
|
|
||||||
ctl.HandleNewProxyResp(m)
|
|
||||||
case *msg.Pong:
|
|
||||||
if m.Error != "" {
|
|
||||||
xl.Error("Pong contains error: %s", m.Error)
|
|
||||||
ctl.conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctl.lastPong = time.Now()
|
|
||||||
xl.Debug("receive heartbeat from server")
|
|
||||||
}
|
}
|
||||||
|
_ = ctl.msgDispatcher.Send(pingMsg)
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go wait.BackoffUntil(sendHeartBeat,
|
||||||
|
wait.NewFastBackoffManager(wait.FastBackoffOptions{
|
||||||
|
Duration: time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second,
|
||||||
|
InitDurationIfFail: time.Second,
|
||||||
|
Factor: 2.0,
|
||||||
|
Jitter: 0.1,
|
||||||
|
MaxDuration: time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second,
|
||||||
|
}),
|
||||||
|
true, ctl.doneCh,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check heartbeat timeout.
|
||||||
|
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 {
|
||||||
|
go wait.Until(func() {
|
||||||
|
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
||||||
|
xl.Warnf("heartbeat timeout")
|
||||||
|
ctl.closeSession()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}, time.Second, ctl.doneCh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If controler is notified by closedCh, reader and writer and handler will exit
|
|
||||||
func (ctl *Control) worker() {
|
func (ctl *Control) worker() {
|
||||||
go ctl.msgHandler()
|
go ctl.heartbeatWorker()
|
||||||
go ctl.reader()
|
go ctl.msgDispatcher.Run()
|
||||||
go ctl.writer()
|
|
||||||
|
|
||||||
select {
|
<-ctl.msgDispatcher.Done()
|
||||||
case <-ctl.closedCh:
|
ctl.closeSession()
|
||||||
// close related channels and wait until other goroutines done
|
|
||||||
close(ctl.readCh)
|
|
||||||
ctl.readerShutdown.WaitDone()
|
|
||||||
ctl.msgHandlerShutdown.WaitDone()
|
|
||||||
|
|
||||||
close(ctl.sendCh)
|
ctl.pm.Close()
|
||||||
ctl.writerShutdown.WaitDone()
|
ctl.vm.Close()
|
||||||
|
close(ctl.doneCh)
|
||||||
ctl.pm.Close()
|
|
||||||
ctl.vm.Close()
|
|
||||||
|
|
||||||
close(ctl.closedDoneCh)
|
|
||||||
if ctl.session != nil {
|
|
||||||
ctl.session.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
func (ctl *Control) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||||
ctl.vm.Reload(visitorCfgs)
|
ctl.vm.UpdateAll(visitorCfgs)
|
||||||
ctl.pm.Reload(pxyCfgs)
|
ctl.pm.UpdateAll(proxyCfgs)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,9 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Type int
|
var ErrPayloadType = errors.New("error payload type")
|
||||||
|
|
||||||
const (
|
type Handler func(payload any) error
|
||||||
EvStartProxy Type = iota
|
|
||||||
EvCloseProxy
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPayloadType = errors.New("error payload type")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handler func(evType Type, payload interface{}) error
|
|
||||||
|
|
||||||
type StartProxyPayload struct {
|
type StartProxyPayload struct {
|
||||||
NewProxyMsg *msg.NewProxy
|
NewProxyMsg *msg.NewProxy
|
||||||
|
@ -21,14 +21,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ErrHealthCheckType = errors.New("error health check type")
|
||||||
ErrHealthCheckType = errors.New("error health check type")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
checkType string
|
checkType string
|
||||||
@ -40,8 +40,8 @@ type Monitor struct {
|
|||||||
addr string
|
addr string
|
||||||
|
|
||||||
// For http
|
// For http
|
||||||
url string
|
url string
|
||||||
|
header http.Header
|
||||||
failedTimes uint64
|
failedTimes uint64
|
||||||
statusOK bool
|
statusOK bool
|
||||||
statusNormalFn func()
|
statusNormalFn func()
|
||||||
@ -51,28 +51,41 @@ type Monitor struct {
|
|||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(ctx context.Context, checkType string,
|
func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
|
||||||
intervalS int, timeoutS int, maxFailedTimes int,
|
statusNormalFn func(), statusFailedFn func(),
|
||||||
addr string, url string,
|
) *Monitor {
|
||||||
statusNormalFn func(), statusFailedFn func()) *Monitor {
|
if cfg.IntervalSeconds <= 0 {
|
||||||
|
cfg.IntervalSeconds = 10
|
||||||
if intervalS <= 0 {
|
|
||||||
intervalS = 10
|
|
||||||
}
|
}
|
||||||
if timeoutS <= 0 {
|
if cfg.TimeoutSeconds <= 0 {
|
||||||
timeoutS = 3
|
cfg.TimeoutSeconds = 3
|
||||||
}
|
}
|
||||||
if maxFailedTimes <= 0 {
|
if cfg.MaxFailed <= 0 {
|
||||||
maxFailedTimes = 1
|
cfg.MaxFailed = 1
|
||||||
}
|
}
|
||||||
newctx, cancel := context.WithCancel(ctx)
|
newctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
var url string
|
||||||
|
if cfg.Type == "http" && cfg.Path != "" {
|
||||||
|
s := "http://" + addr
|
||||||
|
if !strings.HasPrefix(cfg.Path, "/") {
|
||||||
|
s += "/"
|
||||||
|
}
|
||||||
|
url = s + cfg.Path
|
||||||
|
}
|
||||||
|
header := make(http.Header)
|
||||||
|
for _, h := range cfg.HTTPHeaders {
|
||||||
|
header.Set(h.Name, h.Value)
|
||||||
|
}
|
||||||
|
|
||||||
return &Monitor{
|
return &Monitor{
|
||||||
checkType: checkType,
|
checkType: cfg.Type,
|
||||||
interval: time.Duration(intervalS) * time.Second,
|
interval: time.Duration(cfg.IntervalSeconds) * time.Second,
|
||||||
timeout: time.Duration(timeoutS) * time.Second,
|
timeout: time.Duration(cfg.TimeoutSeconds) * time.Second,
|
||||||
maxFailedTimes: maxFailedTimes,
|
maxFailedTimes: cfg.MaxFailed,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
url: url,
|
url: url,
|
||||||
|
header: header,
|
||||||
statusOK: false,
|
statusOK: false,
|
||||||
statusNormalFn: statusNormalFn,
|
statusNormalFn: statusNormalFn,
|
||||||
statusFailedFn: statusFailedFn,
|
statusFailedFn: statusFailedFn,
|
||||||
@ -105,17 +118,17 @@ func (monitor *Monitor) checkWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
xl.Trace("do one health check success")
|
xl.Tracef("do one health check success")
|
||||||
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
||||||
xl.Info("health check status change to success")
|
xl.Infof("health check status change to success")
|
||||||
monitor.statusOK = true
|
monitor.statusOK = true
|
||||||
monitor.statusNormalFn()
|
monitor.statusNormalFn()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
xl.Warn("do one health check failed: %v", err)
|
xl.Warnf("do one health check failed: %v", err)
|
||||||
monitor.failedTimes++
|
monitor.failedTimes++
|
||||||
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
||||||
xl.Warn("health check status change to failed")
|
xl.Warnf("health check status change to failed")
|
||||||
monitor.statusOK = false
|
monitor.statusOK = false
|
||||||
monitor.statusFailedFn()
|
monitor.statusFailedFn()
|
||||||
}
|
}
|
||||||
@ -152,16 +165,18 @@ func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (monitor *Monitor) doHTTPCheck(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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
req.Header = monitor.header
|
||||||
|
req.Host = monitor.header.Get("Host")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
io.Copy(io.Discard, resp.Body)
|
_, _ = io.Copy(io.Discard, resp.Body)
|
||||||
|
|
||||||
if resp.StatusCode/100 != 2 {
|
if resp.StatusCode/100 != 2 {
|
||||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||||
|
47
client/proxy/general_tcp.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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 (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pxyConfs := []v1.ProxyConfigurer{
|
||||||
|
&v1.TCPProxyConfig{},
|
||||||
|
&v1.HTTPProxyConfig{},
|
||||||
|
&v1.HTTPSProxyConfig{},
|
||||||
|
&v1.STCPProxyConfig{},
|
||||||
|
&v1.TCPMuxProxyConfig{},
|
||||||
|
}
|
||||||
|
for _, cfg := range pxyConfs {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneralTCPProxy is a general implementation of Proxy interface for TCP protocol.
|
||||||
|
// If the default GeneralTCPProxy cannot meet the requirements, you can customize
|
||||||
|
// the implementation of the Proxy interface.
|
||||||
|
type GeneralTCPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy {
|
||||||
|
return &GeneralTCPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
}
|
||||||
|
}
|
@ -15,795 +15,208 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
libnet "github.com/fatedier/golib/net"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
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"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
|
||||||
pp "github.com/pires/go-proxyproto"
|
|
||||||
"golang.org/x/time/rate"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
||||||
|
|
||||||
|
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, v1.ProxyConfigurer) Proxy) {
|
||||||
|
proxyFactoryRegistry[proxyConfType] = factory
|
||||||
|
}
|
||||||
|
|
||||||
// Proxy defines how to handle work connections for different proxy type.
|
// Proxy defines how to handle work connections for different proxy type.
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
Run() error
|
Run() error
|
||||||
|
|
||||||
// InWorkConn accept work connections registered to server.
|
// InWorkConn accept work connections registered to server.
|
||||||
InWorkConn(net.Conn, *msg.StartWorkConn)
|
InWorkConn(net.Conn, *msg.StartWorkConn)
|
||||||
|
SetInWorkConnCallback(func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) /* continue */ bool)
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
func NewProxy(
|
||||||
|
ctx context.Context,
|
||||||
|
pxyConf v1.ProxyConfigurer,
|
||||||
|
clientCfg *v1.ClientCommonConfig,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
|
) (pxy Proxy) {
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
||||||
if limitBytes > 0 {
|
if limitBytes > 0 && pxyConf.GetBaseConfig().Transport.BandwidthLimitMode == types.BandwidthLimitModeClient {
|
||||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
clientCfg: clientCfg,
|
baseCfg: pxyConf.GetBaseConfig(),
|
||||||
serverUDPPort: serverUDPPort,
|
clientCfg: clientCfg,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
xl: xlog.FromContextSafe(ctx),
|
msgTransporter: msgTransporter,
|
||||||
ctx: ctx,
|
vnetController: vnetController,
|
||||||
|
xl: xlog.FromContextSafe(ctx),
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
|
||||||
case *config.TCPProxyConf:
|
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
|
||||||
pxy = &TCPProxy{
|
if factory == nil {
|
||||||
BaseProxy: &baseProxy,
|
return nil
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.TCPMuxProxyConf:
|
|
||||||
pxy = &TCPMuxProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.UDPProxyConf:
|
|
||||||
pxy = &UDPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.HTTPProxyConf:
|
|
||||||
pxy = &HTTPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.HTTPSProxyConf:
|
|
||||||
pxy = &HTTPSProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.STCPProxyConf:
|
|
||||||
pxy = &STCPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.XTCPProxyConf:
|
|
||||||
pxy = &XTCPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.SUDPProxyConf:
|
|
||||||
pxy = &SUDPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return factory(&baseProxy, pxyConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
closed bool
|
baseCfg *v1.ProxyBaseConfig
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
serverUDPPort int
|
msgTransporter transport.MessageTransporter
|
||||||
limiter *rate.Limiter
|
vnetController *vnet.Controller
|
||||||
|
limiter *rate.Limiter
|
||||||
|
// proxyPlugin is used to handle connections instead of dialing to local service.
|
||||||
|
// It's only validate for TCP protocol now.
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) /* continue */ bool
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
xl *xlog.Logger
|
xl *xlog.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP
|
func (pxy *BaseProxy) Run() error {
|
||||||
type TCPProxy struct {
|
if pxy.baseCfg.Plugin.Type != "" {
|
||||||
*BaseProxy
|
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
|
||||||
|
Name: pxy.baseCfg.Name,
|
||||||
cfg *config.TCPProxyConf
|
VnetController: pxy.vnetController,
|
||||||
proxyPlugin plugin.Plugin
|
}, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
pxy.proxyPlugin = p
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP Multiplexer
|
|
||||||
type TCPMuxProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.TCPMuxProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPMuxProxy) 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 *TCPMuxProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP
|
|
||||||
type HTTPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.HTTPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPProxy) 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 *HTTPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPS
|
|
||||||
type HTTPSProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.HTTPSProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPSProxy) 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 *HTTPSProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPSProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// STCP
|
|
||||||
type STCPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.STCPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *STCPProxy) 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 *STCPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
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
|
|
||||||
host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
|
|
||||||
}
|
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
|
||||||
|
|
||||||
port, err := strconv.ParseInt(portStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pxy.sendDetectMsg(host, 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 = io.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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP
|
func (pxy *BaseProxy) Close() {
|
||||||
type UDPProxy struct {
|
if pxy.proxyPlugin != nil {
|
||||||
*BaseProxy
|
pxy.proxyPlugin.Close()
|
||||||
|
|
||||||
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) {
|
func (pxy *BaseProxy) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||||
xl := pxy.xl
|
pxy.inWorkConnCallback = cb
|
||||||
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
|
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
var err error
|
if pxy.inWorkConnCallback != nil {
|
||||||
if pxy.limiter != nil {
|
if !pxy.inWorkConnCallback(pxy.baseCfg, conn, m) {
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseCompression {
|
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
|
||||||
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.
|
// Common handler for tcp work connections.
|
||||||
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
|
||||||
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) {
|
xl := pxy.xl
|
||||||
xl := xlog.FromContextSafe(ctx)
|
baseCfg := pxy.baseCfg
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
remote = workConn
|
remote = workConn
|
||||||
if limiter != nil {
|
if pxy.limiter != nil {
|
||||||
remote = frpIo.WrapReadWriteCloser(limit.NewReader(workConn, limiter), limit.NewWriter(workConn, limiter), func() error {
|
remote = libio.WrapReadWriteCloser(limit.NewReader(workConn, pxy.limiter), limit.NewWriter(workConn, pxy.limiter), func() error {
|
||||||
return workConn.Close()
|
return workConn.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
xl.Tracef("handle tcp work connection, useEncryption: %t, useCompression: %t",
|
||||||
baseInfo.UseEncryption, baseInfo.UseCompression)
|
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
|
||||||
if baseInfo.UseEncryption {
|
if baseCfg.Transport.UseEncryption {
|
||||||
remote, err = frpIo.WithEncryption(remote, encKey)
|
remote, err = libio.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if baseInfo.UseCompression {
|
var compressionResourceRecycleFn func()
|
||||||
remote = frpIo.WithCompression(remote)
|
if baseCfg.Transport.UseCompression {
|
||||||
|
remote, compressionResourceRecycleFn = libio.WithCompressionFromPool(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to send proxy protocol info
|
// check if we need to send proxy protocol info
|
||||||
var extraInfo []byte
|
var connInfo plugin.ConnectionInfo
|
||||||
if baseInfo.ProxyProtocolVersion != "" {
|
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
if m.DstAddr == "" {
|
||||||
if m.DstAddr == "" {
|
m.DstAddr = "127.0.0.1"
|
||||||
m.DstAddr = "127.0.0.1"
|
|
||||||
}
|
|
||||||
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
|
|
||||||
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
|
|
||||||
h := &pp.Header{
|
|
||||||
Command: pp.PROXY,
|
|
||||||
SourceAddr: srcAddr,
|
|
||||||
DestinationAddr: dstAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(m.SrcAddr, ".") {
|
|
||||||
h.TransportProtocol = pp.TCPv4
|
|
||||||
} else {
|
|
||||||
h.TransportProtocol = pp.TCPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
if baseInfo.ProxyProtocolVersion == "v1" {
|
|
||||||
h.Version = 1
|
|
||||||
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
|
||||||
h.Version = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
h.WriteTo(buf)
|
|
||||||
extraInfo = buf.Bytes()
|
|
||||||
}
|
}
|
||||||
|
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
|
||||||
|
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
|
||||||
|
connInfo.SrcAddr = srcAddr
|
||||||
|
connInfo.DstAddr = dstAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyPlugin != nil {
|
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
// if plugin is set, let plugin handle connections first
|
// Use the common proxy protocol builder function
|
||||||
xl.Debug("handle by plugin: %s", proxyPlugin.Name())
|
header := netpkg.BuildProxyProtocolHeaderStruct(connInfo.SrcAddr, connInfo.DstAddr, baseCfg.Transport.ProxyProtocolVersion)
|
||||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
connInfo.ProxyProtocolHeader = header
|
||||||
xl.Debug("handle by plugin finished")
|
}
|
||||||
|
connInfo.Conn = remote
|
||||||
|
connInfo.UnderlyingConn = workConn
|
||||||
|
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
// if plugin is set, let plugin handle connection first
|
||||||
|
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||||
|
pxy.proxyPlugin.Handle(pxy.ctx, &connInfo)
|
||||||
|
xl.Debugf("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localConn, err := libdial.Dial(
|
localConn, err := libnet.Dial(
|
||||||
net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort)),
|
net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort)),
|
||||||
libdial.WithTimeout(10*time.Second),
|
libnet.WithTimeout(10*time.Second),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
xl.Errorf("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
|
|
||||||
if len(extraInfo) > 0 {
|
if connInfo.ProxyProtocolHeader != nil {
|
||||||
localConn.Write(extraInfo)
|
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||||
|
workConn.Close()
|
||||||
|
xl.Errorf("write proxy protocol header to local conn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, remote)
|
_, _, errs := libio.Join(localConn, remote)
|
||||||
xl.Debug("join connections closed")
|
xl.Debugf("join connections closed")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
xl.Tracef("join connections errors: %v", errs)
|
||||||
|
}
|
||||||
|
if compressionResourceRecycleFn != nil {
|
||||||
|
compressionResourceRecycleFn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,63 @@
|
|||||||
|
// 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
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/samber/lo"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/frp/client/event"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
sendCh chan (msg.Message)
|
proxies map[string]*Wrapper
|
||||||
proxies map[string]*Wrapper
|
msgTransporter transport.MessageTransporter
|
||||||
|
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
|
|
||||||
// The UDP port that the server is listening on
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager {
|
func NewManager(
|
||||||
|
ctx context.Context,
|
||||||
|
clientCfg *v1.ClientCommonConfig,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
sendCh: msgSendCh,
|
proxies: make(map[string]*Wrapper),
|
||||||
proxies: make(map[string]*Wrapper),
|
msgTransporter: msgTransporter,
|
||||||
closed: false,
|
vnetController: vnetController,
|
||||||
clientCfg: clientCfg,
|
closed: false,
|
||||||
serverUDPPort: serverUDPPort,
|
clientCfg: clientCfg,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +76,10 @@ func (pm *Manager) StartProxy(name string, remoteAddr string, serverRespErr stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pm *Manager) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||||
|
pm.inWorkConnCallback = cb
|
||||||
|
}
|
||||||
|
|
||||||
func (pm *Manager) Close() {
|
func (pm *Manager) Close() {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
@ -75,7 +100,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 any) error {
|
||||||
var m msg.Message
|
var m msg.Message
|
||||||
switch e := payload.(type) {
|
switch e := payload.(type) {
|
||||||
case *event.StartProxyPayload:
|
case *event.StartProxyPayload:
|
||||||
@ -86,10 +111,7 @@ func (pm *Manager) HandleEvent(evType event.Type, payload interface{}) error {
|
|||||||
return event.ErrPayloadType
|
return event.ErrPayloadType
|
||||||
}
|
}
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
return pm.msgTransporter.Send(m)
|
||||||
pm.sendCh <- m
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||||
@ -102,38 +124,49 @@ func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
|||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
func (pm *Manager) GetProxyStatus(name string) (*WorkingStatus, bool) {
|
||||||
|
pm.mu.RLock()
|
||||||
|
defer pm.mu.RUnlock()
|
||||||
|
if pxy, ok := pm.proxies[name]; ok {
|
||||||
|
return pxy.GetStatus(), true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||||
xl := xlog.FromContextSafe(pm.ctx)
|
xl := xlog.FromContextSafe(pm.ctx)
|
||||||
|
proxyCfgsMap := lo.KeyBy(proxyCfgs, func(c v1.ProxyConfigurer) string {
|
||||||
|
return c.GetBaseConfig().Name
|
||||||
|
})
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
delPxyNames := make([]string, 0)
|
delPxyNames := make([]string, 0)
|
||||||
for name, pxy := range pm.proxies {
|
for name, pxy := range pm.proxies {
|
||||||
del := false
|
del := false
|
||||||
cfg, ok := pxyCfgs[name]
|
cfg, ok := proxyCfgsMap[name]
|
||||||
if !ok {
|
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
|
||||||
del = true
|
del = true
|
||||||
} else {
|
|
||||||
if !pxy.Cfg.Compare(cfg) {
|
|
||||||
del = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if del {
|
if del {
|
||||||
delPxyNames = append(delPxyNames, name)
|
delPxyNames = append(delPxyNames, name)
|
||||||
delete(pm.proxies, name)
|
delete(pm.proxies, name)
|
||||||
|
|
||||||
pxy.Stop()
|
pxy.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(delPxyNames) > 0 {
|
if len(delPxyNames) > 0 {
|
||||||
xl.Info("proxy removed: %v", delPxyNames)
|
xl.Infof("proxy removed: %s", delPxyNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for name, cfg := range pxyCfgs {
|
for _, cfg := range proxyCfgs {
|
||||||
|
name := cfg.GetBaseConfig().Name
|
||||||
if _, ok := pm.proxies[name]; !ok {
|
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.vnetController)
|
||||||
|
if pm.inWorkConnCallback != nil {
|
||||||
|
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||||
|
}
|
||||||
pm.proxies[name] = pxy
|
pm.proxies[name] = pxy
|
||||||
addPxyNames = append(addPxyNames, name)
|
addPxyNames = append(addPxyNames, name)
|
||||||
|
|
||||||
@ -141,6 +174,6 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(addPxyNames) > 0 {
|
if len(addPxyNames) > 0 {
|
||||||
xl.Info("proxy added: %v", addPxyNames)
|
xl.Infof("proxy added: %s", addPxyNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,37 @@
|
|||||||
|
// 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
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/fatedier/frp/client/event"
|
||||||
"github.com/fatedier/frp/client/health"
|
"github.com/fatedier/frp/client/health"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -27,17 +44,17 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
statusCheckInterval time.Duration = 3 * time.Second
|
statusCheckInterval = 3 * time.Second
|
||||||
waitResponseTimeout = 20 * time.Second
|
waitResponseTimeout = 20 * time.Second
|
||||||
startErrTimeout = 30 * time.Second
|
startErrTimeout = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkingStatus struct {
|
type WorkingStatus struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Phase string `json:"status"`
|
Phase string `json:"status"`
|
||||||
Err string `json:"err"`
|
Err string `json:"err"`
|
||||||
Cfg config.ProxyConf `json:"cfg"`
|
Cfg v1.ProxyConfigurer `json:"cfg"`
|
||||||
|
|
||||||
// Got from server.
|
// Got from server.
|
||||||
RemoteAddr string `json:"remote_addr"`
|
RemoteAddr string `json:"remote_addr"`
|
||||||
@ -56,6 +73,10 @@ type Wrapper struct {
|
|||||||
// event handler
|
// event handler
|
||||||
handler event.Handler
|
handler event.Handler
|
||||||
|
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
// vnet controller
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
health uint32
|
health uint32
|
||||||
lastSendStartMsg time.Time
|
lastSendStartMsg time.Time
|
||||||
lastStartErr time.Time
|
lastStartErr time.Time
|
||||||
@ -67,35 +88,48 @@ type Wrapper struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper {
|
func NewWrapper(
|
||||||
baseInfo := cfg.GetBaseInfo()
|
ctx context.Context,
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
cfg v1.ProxyConfigurer,
|
||||||
|
clientCfg *v1.ClientCommonConfig,
|
||||||
|
eventHandler event.Handler,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
|
) *Wrapper {
|
||||||
|
baseInfo := cfg.GetBaseConfig()
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
||||||
pw := &Wrapper{
|
pw := &Wrapper{
|
||||||
WorkingStatus: WorkingStatus{
|
WorkingStatus: WorkingStatus{
|
||||||
Name: baseInfo.ProxyName,
|
Name: baseInfo.Name,
|
||||||
Type: baseInfo.ProxyType,
|
Type: baseInfo.Type,
|
||||||
Phase: ProxyPhaseNew,
|
Phase: ProxyPhaseNew,
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
},
|
},
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
healthNotifyCh: make(chan struct{}),
|
healthNotifyCh: make(chan struct{}),
|
||||||
handler: eventHandler,
|
handler: eventHandler,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
xl: xl,
|
xl: xl,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseInfo.HealthCheckType != "" {
|
if baseInfo.HealthCheck.Type != "" && baseInfo.LocalPort > 0 {
|
||||||
pw.health = 1 // means failed
|
pw.health = 1 // means failed
|
||||||
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
|
||||||
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
|
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
|
||||||
baseInfo.HealthCheckURL, pw.statusNormalCallback, pw.statusFailedCallback)
|
pw.statusNormalCallback, pw.statusFailedCallback)
|
||||||
xl.Trace("enable health check monitor")
|
xl.Tracef("enable health check monitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
|
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pw *Wrapper) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||||
|
pw.pxy.SetInWorkConnCallback(cb)
|
||||||
|
}
|
||||||
|
|
||||||
func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||||
pw.mu.Lock()
|
pw.mu.Lock()
|
||||||
defer pw.mu.Unlock()
|
defer pw.mu.Unlock()
|
||||||
@ -108,7 +142,7 @@ func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
|||||||
pw.Phase = ProxyPhaseStartErr
|
pw.Phase = ProxyPhaseStartErr
|
||||||
pw.Err = respErr
|
pw.Err = respErr
|
||||||
pw.lastStartErr = time.Now()
|
pw.lastStartErr = time.Now()
|
||||||
return fmt.Errorf(pw.Err)
|
return fmt.Errorf("%s", pw.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pw.pxy.Run(); err != nil {
|
if err := pw.pxy.Run(); err != nil {
|
||||||
@ -145,7 +179,7 @@ func (pw *Wrapper) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pw *Wrapper) close() {
|
func (pw *Wrapper) close() {
|
||||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
_ = pw.handler(&event.CloseProxyPayload{
|
||||||
CloseProxyMsg: &msg.CloseProxy{
|
CloseProxyMsg: &msg.CloseProxy{
|
||||||
ProxyName: pw.Name,
|
ProxyName: pw.Name,
|
||||||
},
|
},
|
||||||
@ -168,13 +202,13 @@ func (pw *Wrapper) checkWorker() {
|
|||||||
(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
||||||
(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
||||||
|
|
||||||
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart)
|
xl.Tracef("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart)
|
||||||
pw.Phase = ProxyPhaseWaitStart
|
pw.Phase = ProxyPhaseWaitStart
|
||||||
|
|
||||||
var newProxyMsg msg.NewProxy
|
var newProxyMsg msg.NewProxy
|
||||||
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
||||||
pw.lastSendStartMsg = now
|
pw.lastSendStartMsg = now
|
||||||
pw.handler(event.EvStartProxy, &event.StartProxyPayload{
|
_ = pw.handler(&event.StartProxyPayload{
|
||||||
NewProxyMsg: &newProxyMsg,
|
NewProxyMsg: &newProxyMsg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -183,7 +217,7 @@ func (pw *Wrapper) checkWorker() {
|
|||||||
pw.mu.Lock()
|
pw.mu.Lock()
|
||||||
if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart {
|
if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart {
|
||||||
pw.close()
|
pw.close()
|
||||||
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed)
|
xl.Tracef("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed)
|
||||||
pw.Phase = ProxyPhaseCheckFailed
|
pw.Phase = ProxyPhaseCheckFailed
|
||||||
}
|
}
|
||||||
pw.mu.Unlock()
|
pw.mu.Unlock()
|
||||||
@ -201,25 +235,25 @@ func (pw *Wrapper) checkWorker() {
|
|||||||
func (pw *Wrapper) statusNormalCallback() {
|
func (pw *Wrapper) statusNormalCallback() {
|
||||||
xl := pw.xl
|
xl := pw.xl
|
||||||
atomic.StoreUint32(&pw.health, 0)
|
atomic.StoreUint32(&pw.health, 0)
|
||||||
errors.PanicToError(func() {
|
_ = errors.PanicToError(func() {
|
||||||
select {
|
select {
|
||||||
case pw.healthNotifyCh <- struct{}{}:
|
case pw.healthNotifyCh <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
xl.Info("health check success")
|
xl.Infof("health check success")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *Wrapper) statusFailedCallback() {
|
func (pw *Wrapper) statusFailedCallback() {
|
||||||
xl := pw.xl
|
xl := pw.xl
|
||||||
atomic.StoreUint32(&pw.health, 1)
|
atomic.StoreUint32(&pw.health, 1)
|
||||||
errors.PanicToError(func() {
|
_ = errors.PanicToError(func() {
|
||||||
select {
|
select {
|
||||||
case pw.healthNotifyCh <- struct{}{}:
|
case pw.healthNotifyCh <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
xl.Info("health check failed")
|
xl.Infof("health check failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
|
func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
|
||||||
@ -228,7 +262,7 @@ func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
|
|||||||
pxy := pw.pxy
|
pxy := pw.pxy
|
||||||
pw.mu.RUnlock()
|
pw.mu.RUnlock()
|
||||||
if pxy != nil && pw.Phase == ProxyPhaseRunning {
|
if pxy != nil && pw.Phase == ProxyPhaseRunning {
|
||||||
xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
xl.Debugf("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
go pxy.InWorkConn(workConn, m)
|
go pxy.InWorkConn(workConn, m)
|
||||||
} else {
|
} else {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
|
209
client/proxy/sudp.go
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SUDPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *v1.SUDPProxyConfig
|
||||||
|
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||||
|
unwrapped, ok := cfg.(*v1.SUDPProxyConfig)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &SUDPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: unwrapped,
|
||||||
|
closeCh: make(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, _ *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
xl.Infof("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 = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pxy.cfg.Transport.UseEncryption {
|
||||||
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.Transport.UseCompression {
|
||||||
|
rwc = libio.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
conn = netpkg.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.Tracef("frpc sudp proxy is closed")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
var udpMsg msg.UDPPacket
|
||||||
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
|
xl.Warnf("read from workConn for sudp error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
readCh <- &udpMsg
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Warnf("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.Infof("writer goroutine for sudp work connection closed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
for rawMsg := range sendCh {
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
xl.Tracef("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.Tracef("frpc send ping message to frpc visitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
|
xl.Errorf("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.Warnf("heartbeat goroutine for sudp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
xl.Tracef("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), pxy.cfg.Transport.ProxyProtocolVersion)
|
||||||
|
}
|
177
client/proxy/udp.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *v1.UDPProxyConfig
|
||||||
|
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
readCh chan *msg.UDPPacket
|
||||||
|
|
||||||
|
// include msg.UDPPacket and msg.Ping
|
||||||
|
sendCh chan msg.Message
|
||||||
|
workConn net.Conn
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||||
|
unwrapped, ok := cfg.(*v1.UDPProxyConfig)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &UDPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: unwrapped,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, _ *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
xl.Infof("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
// close resources related with old workConn
|
||||||
|
pxy.Close()
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
var err error
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pxy.cfg.Transport.UseEncryption {
|
||||||
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.Transport.UseCompression {
|
||||||
|
rwc = libio.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
conn = netpkg.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.Warnf("read from workConn for udp error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
xl.Tracef("get udp package from workConn: %s", udpMsg.Content)
|
||||||
|
readCh <- &udpMsg
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Infof("reader goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
|
defer func() {
|
||||||
|
xl.Infof("writer goroutine for udp work connection closed")
|
||||||
|
}()
|
||||||
|
var errRet error
|
||||||
|
for rawMsg := range sendCh {
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
xl.Tracef("send udp package to workConn: %s", m.Content)
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Tracef("send ping message to udp workConn")
|
||||||
|
}
|
||||||
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
|
xl.Errorf("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.Tracef("heartbeat goroutine for udp work connection closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||||
|
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||||
|
go heartbeatFn(pxy.sendCh)
|
||||||
|
|
||||||
|
// Call Forwarder with proxy protocol version (empty string means no proxy protocol)
|
||||||
|
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
|
||||||
|
}
|
199
client/proxy/xtcp.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
"github.com/quic-go/quic-go"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XTCPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *v1.XTCPProxyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||||
|
unwrapped, ok := cfg.(*v1.XTCPProxyConfig)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &XTCPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: unwrapped,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Errorf("xtcp read from workConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("nathole prepare start")
|
||||||
|
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("nathole prepare error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Infof("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.Name,
|
||||||
|
Sid: natHoleSidMsg.Sid,
|
||||||
|
MappedAddrs: prepareResult.Addrs,
|
||||||
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("nathole exchange info start")
|
||||||
|
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("nathole exchange info error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Infof("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.Secretkey))
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warnf("make hole error: %v", err)
|
||||||
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
|
Sid: natHoleRespMsg.Sid,
|
||||||
|
Success: false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listenConn = newListenConn
|
||||||
|
xl.Infof("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.Warnf("dial udp error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
|
remote, err := netpkg.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||||
|
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||||
|
fmuxCfg.LogOutput = io.Discard
|
||||||
|
session, err := fmux.Server(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("create mux session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
muxConn, err := session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("accept connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Warnf("create tls config error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
||||||
|
&quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(pxy.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(pxy.clientCfg.Transport.QUIC.MaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(pxy.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("dial quic error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// only accept one connection from raddr
|
||||||
|
c, err := quicListener.Accept(pxy.ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("quic accept connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
stream, err := c.AcceptStream(pxy.ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Debugf("quic accept stream error: %v", err)
|
||||||
|
_ = c.CloseWithError(0, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go pxy.HandleTCPWorkConnection(netpkg.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||||
|
}
|
||||||
|
}
|
@ -16,284 +16,266 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/golib/crypto"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/samber/lo"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
"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"
|
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
|
||||||
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
|
"github.com/fatedier/frp/pkg/util/wait"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service is a client service.
|
func init() {
|
||||||
type Service struct {
|
crypto.DefaultSalt = "frp"
|
||||||
// uniq id got from frps, attach it in loginMsg
|
// Disable quic-go's receive buffer warning.
|
||||||
runID string
|
os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "true")
|
||||||
|
// Disable quic-go's ECN support by default. It may cause issues on certain operating systems.
|
||||||
|
if os.Getenv("QUIC_GO_DISABLE_ECN") == "" {
|
||||||
|
os.Setenv("QUIC_GO_DISABLE_ECN", "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// manager control connection with server
|
type cancelErr struct {
|
||||||
ctl *Control
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e cancelErr) Error() string {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceOptions contains options for creating a new client service.
|
||||||
|
type ServiceOptions struct {
|
||||||
|
Common *v1.ClientCommonConfig
|
||||||
|
ProxyCfgs []v1.ProxyConfigurer
|
||||||
|
VisitorCfgs []v1.VisitorConfigurer
|
||||||
|
|
||||||
|
// ConfigFilePath is the path to the configuration file used to initialize.
|
||||||
|
// If it is empty, it means that the configuration file is not used for initialization.
|
||||||
|
// It may be initialized using command line parameters or called directly.
|
||||||
|
ConfigFilePath string
|
||||||
|
|
||||||
|
// ClientSpec is the client specification that control the client behavior.
|
||||||
|
ClientSpec *msg.ClientSpec
|
||||||
|
|
||||||
|
// ConnectorCreator is a function that creates a new connector to make connections to the server.
|
||||||
|
// The Connector shields the underlying connection details, whether it is through TCP or QUIC connection,
|
||||||
|
// and regardless of whether multiplexing is used.
|
||||||
|
//
|
||||||
|
// If it is not set, the default frpc connector will be used.
|
||||||
|
// By using a custom Connector, it can be used to implement a VirtualClient, which connects to frps
|
||||||
|
// through a pipe instead of a real physical connection.
|
||||||
|
ConnectorCreator func(context.Context, *v1.ClientCommonConfig) Connector
|
||||||
|
|
||||||
|
// HandleWorkConnCb is a callback function that is called when a new work connection is created.
|
||||||
|
//
|
||||||
|
// If it is not set, the default frpc implementation will be used.
|
||||||
|
HandleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// setServiceOptionsDefault sets the default values for ServiceOptions.
|
||||||
|
func setServiceOptionsDefault(options *ServiceOptions) {
|
||||||
|
if options.Common != nil {
|
||||||
|
options.Common.Complete()
|
||||||
|
}
|
||||||
|
if options.ConnectorCreator == nil {
|
||||||
|
options.ConnectorCreator = NewConnector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service is the client service that connects to frps and provides proxy services.
|
||||||
|
type Service struct {
|
||||||
ctlMu sync.RWMutex
|
ctlMu sync.RWMutex
|
||||||
|
// manager control connection with server
|
||||||
|
ctl *Control
|
||||||
|
// Uniq id got from frps, it will be attached to loginMsg.
|
||||||
|
runID string
|
||||||
|
|
||||||
// Sets authentication based on selected method
|
// Sets authentication based on selected method
|
||||||
authSetter auth.Setter
|
authSetter auth.Setter
|
||||||
|
|
||||||
cfg config.ClientCommonConf
|
// web server for admin UI and apis
|
||||||
pxyCfgs map[string]config.ProxyConf
|
webServer *httppkg.Server
|
||||||
visitorCfgs map[string]config.VisitorConf
|
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
cfgMu sync.RWMutex
|
cfgMu sync.RWMutex
|
||||||
|
common *v1.ClientCommonConfig
|
||||||
|
proxyCfgs []v1.ProxyConfigurer
|
||||||
|
visitorCfgs []v1.VisitorConfigurer
|
||||||
|
clientSpec *msg.ClientSpec
|
||||||
|
|
||||||
// The configuration file used to initialize this client, or an empty
|
// The configuration file used to initialize this client, or an empty
|
||||||
// string if no configuration file was used.
|
// string if no configuration file was used.
|
||||||
cfgFile string
|
configFilePath string
|
||||||
|
|
||||||
// This is configured by the login response from frps
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
exit uint32 // 0 means not exit
|
|
||||||
|
|
||||||
// service context
|
// service context
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
// call cancel to stop service
|
// call cancel to stop service
|
||||||
cancel context.CancelFunc
|
cancel context.CancelCauseFunc
|
||||||
|
gracefulShutdownDuration time.Duration
|
||||||
|
|
||||||
|
connectorCreator func(context.Context, *v1.ClientCommonConfig) Connector
|
||||||
|
handleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
|
func NewService(options ServiceOptions) (*Service, error) {
|
||||||
|
setServiceOptionsDefault(&options)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
var webServer *httppkg.Server
|
||||||
svr = &Service{
|
if options.Common.WebServer.Port > 0 {
|
||||||
authSetter: auth.NewAuthSetter(cfg.ClientConfig),
|
ws, err := httppkg.NewServer(options.Common.WebServer)
|
||||||
cfg: cfg,
|
|
||||||
cfgFile: cfgFile,
|
|
||||||
pxyCfgs: pxyCfgs,
|
|
||||||
visitorCfgs: visitorCfgs,
|
|
||||||
exit: 0,
|
|
||||||
ctx: xlog.NewContext(ctx, xlog.New()),
|
|
||||||
cancel: cancel,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svr *Service) GetController() *Control {
|
|
||||||
svr.ctlMu.RLock()
|
|
||||||
defer svr.ctlMu.RUnlock()
|
|
||||||
return svr.ctl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svr *Service) Run() error {
|
|
||||||
xl := xlog.FromContextSafe(svr.ctx)
|
|
||||||
|
|
||||||
// login to frps
|
|
||||||
for {
|
|
||||||
conn, session, err := svr.login()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("login to server failed: %v", err)
|
return nil, err
|
||||||
|
|
||||||
// if login_fail_exit is true, just exit this program
|
|
||||||
// otherwise sleep a while and try again to connect to server
|
|
||||||
if svr.cfg.LoginFailExit {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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.Run()
|
|
||||||
svr.ctlMu.Lock()
|
|
||||||
svr.ctl = ctl
|
|
||||||
svr.ctlMu.Unlock()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
webServer = ws
|
||||||
|
}
|
||||||
|
s := &Service{
|
||||||
|
ctx: context.Background(),
|
||||||
|
authSetter: auth.NewAuthSetter(options.Common.Auth),
|
||||||
|
webServer: webServer,
|
||||||
|
common: options.Common,
|
||||||
|
configFilePath: options.ConfigFilePath,
|
||||||
|
proxyCfgs: options.ProxyCfgs,
|
||||||
|
visitorCfgs: options.VisitorCfgs,
|
||||||
|
clientSpec: options.ClientSpec,
|
||||||
|
connectorCreator: options.ConnectorCreator,
|
||||||
|
handleWorkConnCb: options.HandleWorkConnCb,
|
||||||
|
}
|
||||||
|
if webServer != nil {
|
||||||
|
webServer.RouteRegister(s.registerRouteHandlers)
|
||||||
|
}
|
||||||
|
if options.Common.VirtualNet.Address != "" {
|
||||||
|
s.vnetController = vnet.NewController(options.Common.VirtualNet)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) Run(ctx context.Context) error {
|
||||||
|
ctx, cancel := context.WithCancelCause(ctx)
|
||||||
|
svr.ctx = xlog.NewContext(ctx, xlog.FromContextSafe(ctx))
|
||||||
|
svr.cancel = cancel
|
||||||
|
|
||||||
|
// set custom DNSServer
|
||||||
|
if svr.common.DNSServer != "" {
|
||||||
|
netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if svr.vnetController != nil {
|
||||||
|
if err := svr.vnetController.Init(); err != nil {
|
||||||
|
log.Errorf("init virtual network controller error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
log.Infof("virtual network controller start...")
|
||||||
|
if err := svr.vnetController.Run(); err != nil {
|
||||||
|
log.Warnf("virtual network controller exit with error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if svr.webServer != nil {
|
||||||
|
go func() {
|
||||||
|
log.Infof("admin server listen on %s", svr.webServer.Address())
|
||||||
|
if err := svr.webServer.Run(); err != nil {
|
||||||
|
log.Warnf("admin server exit with error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// first login to frps
|
||||||
|
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
|
||||||
|
if svr.ctl == nil {
|
||||||
|
cancelCause := cancelErr{}
|
||||||
|
_ = errors.As(context.Cause(svr.ctx), &cancelCause)
|
||||||
|
return fmt.Errorf("login to the server failed: %v. With loginFailExit enabled, no additional retries will be attempted", cancelCause.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go svr.keepControllerWorking()
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
if svr.cfg.AdminPort != 0 {
|
|
||||||
// Init admin server assets
|
|
||||||
assets.Load(svr.cfg.AssetsDir)
|
|
||||||
|
|
||||||
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
|
|
||||||
err := svr.RunAdminServer(address)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("run admin server error: %v", err)
|
|
||||||
}
|
|
||||||
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
|
||||||
}
|
|
||||||
<-svr.ctx.Done()
|
<-svr.ctx.Done()
|
||||||
|
svr.stop()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) keepControllerWorking() {
|
func (svr *Service) keepControllerWorking() {
|
||||||
xl := xlog.FromContextSafe(svr.ctx)
|
<-svr.ctl.Done()
|
||||||
maxDelayTime := 20 * time.Second
|
|
||||||
delayTime := time.Second
|
|
||||||
|
|
||||||
// if frpc reconnect frps, we need to limit retry times in 1min
|
// There is a situation where the login is successful but due to certain reasons,
|
||||||
// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
|
// the control immediately exits. It is necessary to limit the frequency of reconnection in this case.
|
||||||
// when exceed 1min, we will reset delay and counts
|
// The interval for the first three retries in 1 minute will be very short, and then it will increase exponentially.
|
||||||
cutoffTime := time.Now().Add(time.Minute)
|
// The maximum interval is 20 seconds.
|
||||||
reconnectDelay := time.Second
|
wait.BackoffUntil(func() (bool, error) {
|
||||||
reconnectCounts := 1
|
// loopLoginUntilSuccess is another layer of loop that will continuously attempt to
|
||||||
|
// login to the server until successful.
|
||||||
for {
|
svr.loopLoginUntilSuccess(20*time.Second, false)
|
||||||
<-svr.ctl.ClosedDoneCh()
|
if svr.ctl != nil {
|
||||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
<-svr.ctl.Done()
|
||||||
return
|
return false, errors.New("control is closed and try another loop")
|
||||||
}
|
}
|
||||||
|
// If the control is nil, it means that the login failed and the service is also closed.
|
||||||
// the first three retry with no delay
|
return false, nil
|
||||||
if reconnectCounts > 3 {
|
}, wait.NewFastBackoffManager(
|
||||||
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
wait.FastBackoffOptions{
|
||||||
xl.Info("wait %v to reconnect", reconnectDelay)
|
Duration: time.Second,
|
||||||
reconnectDelay *= 2
|
Factor: 2,
|
||||||
} else {
|
Jitter: 0.1,
|
||||||
util.RandomSleep(time.Second, 0, 0.5)
|
MaxDuration: 20 * time.Second,
|
||||||
}
|
FastRetryCount: 3,
|
||||||
reconnectCounts++
|
FastRetryDelay: 200 * time.Millisecond,
|
||||||
|
FastRetryWindow: time.Minute,
|
||||||
now := time.Now()
|
FastRetryJitter: 0.5,
|
||||||
if now.After(cutoffTime) {
|
},
|
||||||
// reset
|
), true, svr.ctx.Done())
|
||||||
cutoffTime = now.Add(time.Minute)
|
|
||||||
reconnectDelay = time.Second
|
|
||||||
reconnectCounts = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
xl.Info("try to reconnect to server...")
|
|
||||||
conn, session, 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 = 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.Run()
|
|
||||||
svr.ctlMu.Lock()
|
|
||||||
if svr.ctl != nil {
|
|
||||||
svr.ctl.Close()
|
|
||||||
}
|
|
||||||
svr.ctl = ctl
|
|
||||||
svr.ctlMu.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// login creates a connection to frps and registers it self as a client
|
// login creates a connection to frps and registers it self as a client
|
||||||
// conn: control connection
|
// conn: control connection
|
||||||
// session: if it's not nil, using tcp mux
|
// 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, connector Connector, err error) {
|
||||||
xl := xlog.FromContextSafe(svr.ctx)
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
var tlsConfig *tls.Config
|
connector = svr.connectorCreator(svr.ctx, svr.common)
|
||||||
if svr.cfg.TLSEnable {
|
if err = connector.Open(); err != nil {
|
||||||
sn := svr.cfg.TLSServerName
|
return nil, nil, err
|
||||||
if sn == "" {
|
|
||||||
sn = svr.cfg.ServerAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyType, addr, auth, err := libdial.ParseProxyURL(svr.cfg.HTTPProxy)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("fail to parse proxy url")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dialOptions := []libdial.DialOption{}
|
|
||||||
protocol := svr.cfg.Protocol
|
|
||||||
if protocol == "websocket" {
|
|
||||||
protocol = "tcp"
|
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
|
||||||
}
|
|
||||||
if svr.cfg.ConnectServerLocalIP != "" {
|
|
||||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(svr.cfg.ConnectServerLocalIP))
|
|
||||||
}
|
|
||||||
dialOptions = append(dialOptions,
|
|
||||||
libdial.WithProtocol(protocol),
|
|
||||||
libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
|
|
||||||
libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
|
|
||||||
libdial.WithProxy(proxyType, addr),
|
|
||||||
libdial.WithProxyAuth(auth),
|
|
||||||
libdial.WithTLSConfig(tlsConfig),
|
|
||||||
libdial.WithAfterHook(libdial.AfterHook{
|
|
||||||
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, svr.cfg.DisableCustomTLSFirstByte),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
conn, err = libdial.Dial(
|
|
||||||
net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)),
|
|
||||||
dialOptions...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
connector.Close()
|
||||||
if session != nil {
|
|
||||||
session.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if svr.cfg.TCPMux {
|
conn, err = connector.Connect()
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
if err != nil {
|
||||||
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
|
return
|
||||||
fmuxCfg.LogOutput = io.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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loginMsg := &msg.Login{
|
loginMsg := &msg.Login{
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
PoolCount: svr.cfg.PoolCount,
|
PoolCount: svr.common.Transport.PoolCount,
|
||||||
User: svr.cfg.User,
|
User: svr.common.User,
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
RunID: svr.runID,
|
RunID: svr.runID,
|
||||||
Metas: svr.cfg.Metas,
|
Metas: svr.common.Metadatas,
|
||||||
|
}
|
||||||
|
if svr.clientSpec != nil {
|
||||||
|
loginMsg.ClientSpec = *svr.clientSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add auth
|
// Add auth
|
||||||
@ -306,34 +288,98 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var loginRespMsg msg.LoginResp
|
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 {
|
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.SetReadDeadline(time.Time{})
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
if loginRespMsg.Error != "" {
|
if loginRespMsg.Error != "" {
|
||||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||||
xl.Error("%s", loginRespMsg.Error)
|
xl.Errorf("%s", loginRespMsg.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svr.runID = loginRespMsg.RunID
|
svr.runID = loginRespMsg.RunID
|
||||||
xl.ResetPrefixes()
|
xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
||||||
xl.AppendPrefix(svr.runID)
|
|
||||||
|
|
||||||
svr.serverUDPPort = loginRespMsg.ServerUDPPort
|
xl.Infof("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||||
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginExit bool) {
|
||||||
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
|
|
||||||
|
loginFunc := func() (bool, error) {
|
||||||
|
xl.Infof("try to connect to server...")
|
||||||
|
conn, connector, err := svr.login()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("connect to server error: %v", err)
|
||||||
|
if firstLoginExit {
|
||||||
|
svr.cancel(cancelErr{Err: err})
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.cfgMu.RLock()
|
||||||
|
proxyCfgs := svr.proxyCfgs
|
||||||
|
visitorCfgs := svr.visitorCfgs
|
||||||
|
svr.cfgMu.RUnlock()
|
||||||
|
|
||||||
|
connEncrypted := svr.clientSpec == nil || svr.clientSpec.Type != "ssh-tunnel"
|
||||||
|
|
||||||
|
sessionCtx := &SessionContext{
|
||||||
|
Common: svr.common,
|
||||||
|
RunID: svr.runID,
|
||||||
|
Conn: conn,
|
||||||
|
ConnEncrypted: connEncrypted,
|
||||||
|
AuthSetter: svr.authSetter,
|
||||||
|
Connector: connector,
|
||||||
|
VnetController: svr.vnetController,
|
||||||
|
}
|
||||||
|
ctl, err := NewControl(svr.ctx, sessionCtx)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Errorf("new control error: %v", err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
||||||
|
|
||||||
|
ctl.Run(proxyCfgs, visitorCfgs)
|
||||||
|
// close and replace previous control
|
||||||
|
svr.ctlMu.Lock()
|
||||||
|
if svr.ctl != nil {
|
||||||
|
svr.ctl.Close()
|
||||||
|
}
|
||||||
|
svr.ctl = ctl
|
||||||
|
svr.ctlMu.Unlock()
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to reconnect to server until success
|
||||||
|
wait.BackoffUntil(loginFunc, wait.NewFastBackoffManager(
|
||||||
|
wait.FastBackoffOptions{
|
||||||
|
Duration: time.Second,
|
||||||
|
Factor: 2,
|
||||||
|
Jitter: 0.1,
|
||||||
|
MaxDuration: maxInterval,
|
||||||
|
}), true, svr.ctx.Done())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||||
svr.cfgMu.Lock()
|
svr.cfgMu.Lock()
|
||||||
svr.pxyCfgs = pxyCfgs
|
svr.proxyCfgs = proxyCfgs
|
||||||
svr.visitorCfgs = visitorCfgs
|
svr.visitorCfgs = visitorCfgs
|
||||||
svr.cfgMu.Unlock()
|
svr.cfgMu.Unlock()
|
||||||
|
|
||||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
svr.ctlMu.RLock()
|
||||||
|
ctl := svr.ctl
|
||||||
|
svr.ctlMu.RUnlock()
|
||||||
|
|
||||||
|
if ctl != nil {
|
||||||
|
return svr.ctl.UpdateAllConfigurer(proxyCfgs, visitorCfgs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Close() {
|
func (svr *Service) Close() {
|
||||||
@ -341,9 +387,44 @@ func (svr *Service) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) GracefulClose(d time.Duration) {
|
func (svr *Service) GracefulClose(d time.Duration) {
|
||||||
atomic.StoreUint32(&svr.exit, 1)
|
svr.gracefulShutdownDuration = d
|
||||||
if svr.ctl != nil {
|
svr.cancel(nil)
|
||||||
svr.ctl.GracefulClose(d)
|
}
|
||||||
}
|
|
||||||
svr.cancel()
|
func (svr *Service) stop() {
|
||||||
|
svr.ctlMu.Lock()
|
||||||
|
defer svr.ctlMu.Unlock()
|
||||||
|
if svr.ctl != nil {
|
||||||
|
svr.ctl.GracefulClose(svr.gracefulShutdownDuration)
|
||||||
|
svr.ctl = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) getProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||||
|
svr.ctlMu.RLock()
|
||||||
|
ctl := svr.ctl
|
||||||
|
svr.ctlMu.RUnlock()
|
||||||
|
|
||||||
|
if ctl == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return ctl.pm.GetProxyStatus(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) StatusExporter() StatusExporter {
|
||||||
|
return &statusExporterImpl{
|
||||||
|
getProxyStatusFunc: svr.getProxyStatus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusExporter interface {
|
||||||
|
GetProxyStatus(name string) (*proxy.WorkingStatus, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusExporterImpl struct {
|
||||||
|
getProxyStatusFunc func(name string) (*proxy.WorkingStatus, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *statusExporterImpl) GetProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||||
|
return s.getProxyStatusFunc(name)
|
||||||
}
|
}
|
||||||
|
@ -1,567 +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"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"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", 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.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", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(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 = io.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", 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.ctl.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.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)
|
|
||||||
}
|
|
139
client/visitor/stcp.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"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 *v1.STCPVisitorConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) Run() (err error) {
|
||||||
|
if sv.cfg.BindPort > 0 {
|
||||||
|
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.internalConnWorker()
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) Close() {
|
||||||
|
sv.BaseVisitor.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) internalConnWorker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.internalLn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("stcp internal listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
xl.Debugf("get a new stcp user connection")
|
||||||
|
visitorConn, err := sv.helper.ConnectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
RunID: sv.helper.RunID(),
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.Transport.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.Transport.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("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.Warnf("get newVisitorConnRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
xl.Warnf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.Transport.UseEncryption {
|
||||||
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.Transport.UseCompression {
|
||||||
|
var recycleFn func()
|
||||||
|
remote, recycleFn = libio.WithCompressionFromPool(remote)
|
||||||
|
defer recycleFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
libio.Join(userConn, remote)
|
||||||
|
}
|
264
client/visitor/sudp.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// 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"
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
netpkg "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 *v1.SUDPVisitorConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Infof("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.Infof("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
xl.Infof("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorConn, err = sv.getNewVisitorConn()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("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.Debugf("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.Warnf("read from workconn for user udp conn error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Debugf("frpc visitor get ping message from frpc")
|
||||||
|
continue
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
sv.readCh <- m
|
||||||
|
xl.Tracef("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Infof("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.Warnf("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Tracef("send udp package to workConn: %s", firstPacket.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case udpMsg, ok := <-sv.sendCh:
|
||||||
|
if !ok {
|
||||||
|
xl.Infof("sender goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||||
|
xl.Warnf("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Tracef("send udp package to workConn: %s", udpMsg.Content)
|
||||||
|
case <-closeCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnReaderFn(workConn)
|
||||||
|
go workConnSenderFn(workConn)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
xl.Infof("sudp worker is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
visitorConn, err := sv.helper.ConnectServer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
RunID: sv.helper.RunID(),
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.Transport.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.Transport.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.Transport.UseEncryption {
|
||||||
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.Transport.UseCompression {
|
||||||
|
remote = libio.WithCompression(remote)
|
||||||
|
}
|
||||||
|
return netpkg.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) Close() {
|
||||||
|
sv.mu.Lock()
|
||||||
|
defer sv.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(sv.checkCloseCh)
|
||||||
|
}
|
||||||
|
sv.BaseVisitor.Close()
|
||||||
|
if sv.udpConn != nil {
|
||||||
|
sv.udpConn.Close()
|
||||||
|
}
|
||||||
|
close(sv.readCh)
|
||||||
|
close(sv.sendCh)
|
||||||
|
}
|
132
client/visitor/visitor.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
plugin "github.com/fatedier/frp/pkg/plugin/visitor"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper wraps some functions for visitor to use.
|
||||||
|
type Helper interface {
|
||||||
|
// ConnectServer directly connects to the frp server.
|
||||||
|
ConnectServer() (net.Conn, error)
|
||||||
|
// TransferConn transfers the connection to another visitor.
|
||||||
|
TransferConn(string, net.Conn) error
|
||||||
|
// MsgTransporter returns the message transporter that is used to send and receive messages
|
||||||
|
// to the frp server through the controller.
|
||||||
|
MsgTransporter() transport.MessageTransporter
|
||||||
|
// VNetController returns the vnet controller that is used to manage the virtual network.
|
||||||
|
VNetController() *vnet.Controller
|
||||||
|
// RunID returns the run id of current controller.
|
||||||
|
RunID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
|
type Visitor interface {
|
||||||
|
Run() error
|
||||||
|
AcceptConn(conn net.Conn) error
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitor(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg v1.VisitorConfigurer,
|
||||||
|
clientCfg *v1.ClientCommonConfig,
|
||||||
|
helper Helper,
|
||||||
|
) (Visitor, error) {
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
||||||
|
ctx = xlog.NewContext(ctx, xl)
|
||||||
|
var visitor Visitor
|
||||||
|
baseVisitor := BaseVisitor{
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
helper: helper,
|
||||||
|
ctx: ctx,
|
||||||
|
internalLn: netpkg.NewInternalListener(),
|
||||||
|
}
|
||||||
|
if cfg.GetBaseConfig().Plugin.Type != "" {
|
||||||
|
p, err := plugin.Create(
|
||||||
|
cfg.GetBaseConfig().Plugin.Type,
|
||||||
|
plugin.PluginContext{
|
||||||
|
Name: cfg.GetBaseConfig().Name,
|
||||||
|
Ctx: ctx,
|
||||||
|
VnetController: helper.VNetController(),
|
||||||
|
HandleConn: func(conn net.Conn) {
|
||||||
|
_ = baseVisitor.AcceptConn(conn)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cfg.GetBaseConfig().Plugin.VisitorPluginOptions,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
baseVisitor.plugin = p
|
||||||
|
}
|
||||||
|
switch cfg := cfg.(type) {
|
||||||
|
case *v1.STCPVisitorConfig:
|
||||||
|
visitor = &STCPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *v1.XTCPVisitorConfig:
|
||||||
|
visitor = &XTCPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
startTunnelCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
case *v1.SUDPVisitorConfig:
|
||||||
|
visitor = &SUDPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
checkCloseCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visitor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitor struct {
|
||||||
|
clientCfg *v1.ClientCommonConfig
|
||||||
|
helper Helper
|
||||||
|
l net.Listener
|
||||||
|
internalLn *netpkg.InternalListener
|
||||||
|
plugin plugin.Plugin
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseVisitor) AcceptConn(conn net.Conn) error {
|
||||||
|
return v.internalLn.PutConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseVisitor) Close() {
|
||||||
|
if v.l != nil {
|
||||||
|
v.l.Close()
|
||||||
|
}
|
||||||
|
if v.internalLn != nil {
|
||||||
|
v.internalLn.Close()
|
||||||
|
}
|
||||||
|
if v.plugin != nil {
|
||||||
|
v.plugin.Close()
|
||||||
|
}
|
||||||
|
}
|
220
client/visitor/visitor_manager.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// Copyright 2018 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"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
clientCfg *v1.ClientCommonConfig
|
||||||
|
cfgs map[string]v1.VisitorConfigurer
|
||||||
|
visitors map[string]Visitor
|
||||||
|
helper Helper
|
||||||
|
|
||||||
|
checkInterval time.Duration
|
||||||
|
keepVisitorsRunningOnce sync.Once
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
stopCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(
|
||||||
|
ctx context.Context,
|
||||||
|
runID string,
|
||||||
|
clientCfg *v1.ClientCommonConfig,
|
||||||
|
connectServer func() (net.Conn, error),
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
|
) *Manager {
|
||||||
|
m := &Manager{
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
cfgs: make(map[string]v1.VisitorConfigurer),
|
||||||
|
visitors: make(map[string]Visitor),
|
||||||
|
checkInterval: 10 * time.Second,
|
||||||
|
ctx: ctx,
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
m.helper = &visitorHelperImpl{
|
||||||
|
connectServerFn: connectServer,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
|
transferConnFn: m.TransferConn,
|
||||||
|
runID: runID,
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// keepVisitorsRunning checks all visitors' status periodically, if some visitor is not running, start it.
|
||||||
|
// It will only start after Reload is called and a new visitor is added.
|
||||||
|
func (vm *Manager) keepVisitorsRunning() {
|
||||||
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(vm.checkInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-vm.stopCh:
|
||||||
|
xl.Tracef("gracefully shutdown visitor manager")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
vm.mu.Lock()
|
||||||
|
for _, cfg := range vm.cfgs {
|
||||||
|
name := cfg.GetBaseConfig().Name
|
||||||
|
if _, exist := vm.visitors[name]; !exist {
|
||||||
|
xl.Infof("try to start visitor [%s]", name)
|
||||||
|
_ = vm.startVisitor(cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vm.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *Manager) Close() {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
for _, v := range vm.visitors {
|
||||||
|
v.Close()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-vm.stopCh:
|
||||||
|
default:
|
||||||
|
close(vm.stopCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold lock before calling this function.
|
||||||
|
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
||||||
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
|
name := cfg.GetBaseConfig().Name
|
||||||
|
visitor, err := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("new visitor error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = visitor.Run()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("start error: %v", err)
|
||||||
|
} else {
|
||||||
|
vm.visitors[name] = visitor
|
||||||
|
xl.Infof("start visitor success")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) {
|
||||||
|
if len(cfgs) > 0 {
|
||||||
|
// Only start keepVisitorsRunning goroutine once and only when there is at least one visitor.
|
||||||
|
vm.keepVisitorsRunningOnce.Do(func() {
|
||||||
|
go vm.keepVisitorsRunning()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
|
cfgsMap := lo.KeyBy(cfgs, func(c v1.VisitorConfigurer) string {
|
||||||
|
return c.GetBaseConfig().Name
|
||||||
|
})
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
delNames := make([]string, 0)
|
||||||
|
for name, oldCfg := range vm.cfgs {
|
||||||
|
del := false
|
||||||
|
cfg, ok := cfgsMap[name]
|
||||||
|
if !ok || !reflect.DeepEqual(oldCfg, cfg) {
|
||||||
|
del = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if del {
|
||||||
|
delNames = append(delNames, name)
|
||||||
|
delete(vm.cfgs, name)
|
||||||
|
if visitor, ok := vm.visitors[name]; ok {
|
||||||
|
visitor.Close()
|
||||||
|
}
|
||||||
|
delete(vm.visitors, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(delNames) > 0 {
|
||||||
|
xl.Infof("visitor removed: %v", delNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
addNames := make([]string, 0)
|
||||||
|
for _, cfg := range cfgs {
|
||||||
|
name := cfg.GetBaseConfig().Name
|
||||||
|
if _, ok := vm.cfgs[name]; !ok {
|
||||||
|
vm.cfgs[name] = cfg
|
||||||
|
addNames = append(addNames, name)
|
||||||
|
_ = vm.startVisitor(cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(addNames) > 0 {
|
||||||
|
xl.Infof("visitor added: %v", addNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferConn transfers a connection to a visitor.
|
||||||
|
func (vm *Manager) TransferConn(name string, conn net.Conn) error {
|
||||||
|
vm.mu.RLock()
|
||||||
|
defer vm.mu.RUnlock()
|
||||||
|
v, ok := vm.visitors[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("visitor [%s] not found", name)
|
||||||
|
}
|
||||||
|
return v.AcceptConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type visitorHelperImpl struct {
|
||||||
|
connectServerFn func() (net.Conn, error)
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
transferConnFn func(name string, conn net.Conn) error
|
||||||
|
runID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) ConnectServer() (net.Conn, error) {
|
||||||
|
return v.connectServerFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) TransferConn(name string, conn net.Conn) error {
|
||||||
|
return v.transferConnFn(name, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) MsgTransporter() transport.MessageTransporter {
|
||||||
|
return v.msgTransporter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) VNetController() *vnet.Controller {
|
||||||
|
return v.vnetController
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) RunID() string {
|
||||||
|
return v.runID
|
||||||
|
}
|
461
client/visitor/xtcp.go
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
quic "github.com/quic-go/quic-go"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
netpkg "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 *v1.XTCPVisitorConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.BindPort > 0 {
|
||||||
|
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.internalConnWorker()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) Close() {
|
||||||
|
sv.mu.Lock()
|
||||||
|
defer sv.mu.Unlock()
|
||||||
|
sv.BaseVisitor.Close()
|
||||||
|
if sv.cancel != nil {
|
||||||
|
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.Warnf("xtcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) internalConnWorker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.internalLn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("xtcp internal 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.Debugf("keepTunnelOpenWorker try to check tunnel...")
|
||||||
|
conn, err := sv.getTunnelConn()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
||||||
|
_ = sv.retryLimiter.Wait(sv.ctx)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Debugf("keepTunnelOpenWorker check success")
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
isConnTransfered := false
|
||||||
|
defer func() {
|
||||||
|
if !isConnTransfered {
|
||||||
|
userConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
xl.Debugf("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.
|
||||||
|
ctx := context.Background()
|
||||||
|
if sv.cfg.FallbackTo != "" {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(sv.cfg.FallbackTimeoutMs)*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
ctx = timeoutCtx
|
||||||
|
}
|
||||||
|
tunnelConn, err := sv.openTunnel(ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("open tunnel error: %v", err)
|
||||||
|
// no fallback, just return
|
||||||
|
if sv.cfg.FallbackTo == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Debugf("try to transfer connection to visitor: %s", sv.cfg.FallbackTo)
|
||||||
|
if err := sv.helper.TransferConn(sv.cfg.FallbackTo, userConn); err != nil {
|
||||||
|
xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isConnTransfered = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
||||||
|
if sv.cfg.Transport.UseEncryption {
|
||||||
|
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.Transport.UseCompression {
|
||||||
|
var recycleFn func()
|
||||||
|
muxConnRWCloser, recycleFn = libio.WithCompressionFromPool(muxConnRWCloser)
|
||||||
|
defer recycleFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
||||||
|
xl.Debugf("join connections closed")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
xl.Tracef("join connections errors: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// openTunnel will open a tunnel connection to the target server.
|
||||||
|
func (sv *XTCPVisitor) openTunnel(ctx context.Context) (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 <-ctx.Done():
|
||||||
|
return nil, 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.Warnf("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)
|
||||||
|
xl.Tracef("makeNatHole start")
|
||||||
|
if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil {
|
||||||
|
xl.Warnf("nathole precheck error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("nathole prepare start")
|
||||||
|
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("nathole prepare error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Infof("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.SecretKey, now),
|
||||||
|
Timestamp: now,
|
||||||
|
MappedAddrs: prepareResult.Addrs,
|
||||||
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("nathole exchange info start")
|
||||||
|
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warnf("nathole exchange info error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Infof("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.SecretKey))
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warnf("make hole error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listenConn = newListenConn
|
||||||
|
xl.Infof("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
|
if err := sv.session.Init(listenConn, raddr); err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warnf("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 := netpkg.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 = 6 * 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(_ 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 *v1.ClientCommonConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQUICTunnelSession(clientCfg *v1.ClientCommonConfig) 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(context.Background(), listenConn, raddr, tlsConfig,
|
||||||
|
&quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(qs.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(qs.clientCfg.Transport.QUIC.MaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(qs.clientCfg.Transport.QUIC.KeepalivePeriod) * 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 netpkg.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
|
||||||
|
}
|
||||||
|
}
|
@ -1,146 +0,0 @@
|
|||||||
// Copyright 2018 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 (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VisitorManager struct {
|
|
||||||
ctl *Control
|
|
||||||
|
|
||||||
cfgs map[string]config.VisitorConf
|
|
||||||
visitors map[string]Visitor
|
|
||||||
|
|
||||||
checkInterval time.Duration
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
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 (vm *VisitorManager) Run() {
|
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
|
||||||
|
|
||||||
ticker := time.NewTicker(vm.checkInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-vm.stopCh:
|
|
||||||
xl.Info("gracefully shutdown visitor manager")
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
vm.mu.Lock()
|
|
||||||
for _, cfg := range vm.cfgs {
|
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
|
||||||
if _, exist := vm.visitors[name]; !exist {
|
|
||||||
xl.Info("try to start visitor [%s]", name)
|
|
||||||
vm.startVisitor(cfg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vm.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hold lock before calling this function.
|
|
||||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
|
||||||
visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
|
|
||||||
err = visitor.Run()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("start error: %v", err)
|
|
||||||
} else {
|
|
||||||
vm.visitors[name] = visitor
|
|
||||||
xl.Info("start visitor success")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
|
||||||
vm.mu.Lock()
|
|
||||||
defer vm.mu.Unlock()
|
|
||||||
|
|
||||||
delNames := make([]string, 0)
|
|
||||||
for name, oldCfg := range vm.cfgs {
|
|
||||||
del := false
|
|
||||||
cfg, ok := cfgs[name]
|
|
||||||
if !ok {
|
|
||||||
del = true
|
|
||||||
} else {
|
|
||||||
if !oldCfg.Compare(cfg) {
|
|
||||||
del = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if del {
|
|
||||||
delNames = append(delNames, name)
|
|
||||||
delete(vm.cfgs, name)
|
|
||||||
if visitor, ok := vm.visitors[name]; ok {
|
|
||||||
visitor.Close()
|
|
||||||
}
|
|
||||||
delete(vm.visitors, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(delNames) > 0 {
|
|
||||||
xl.Info("visitor removed: %v", delNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
addNames := make([]string, 0)
|
|
||||||
for name, cfg := range cfgs {
|
|
||||||
if _, ok := vm.cfgs[name]; !ok {
|
|
||||||
vm.cfgs[name] = cfg
|
|
||||||
addNames = append(addNames, name)
|
|
||||||
vm.startVisitor(cfg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(addNames) > 0 {
|
|
||||||
xl.Info("visitor added: %v", addNames)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vm *VisitorManager) Close() {
|
|
||||||
vm.mu.Lock()
|
|
||||||
defer vm.mu.Unlock()
|
|
||||||
for _, v := range vm.visitors {
|
|
||||||
v.Close()
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-vm.stopCh:
|
|
||||||
default:
|
|
||||||
close(vm.stopCh)
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,18 +15,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/frpc"
|
_ "github.com/fatedier/frp/assets/frpc"
|
||||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||||
|
"github.com/fatedier/frp/pkg/util/system"
|
||||||
"github.com/fatedier/golib/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
system.EnableCompatibilityMode()
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
sub.Execute()
|
sub.Execute()
|
||||||
}
|
}
|
||||||
|
125
cmd/frpc/sub/admin.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rodaine/table"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
var adminAPITimeout = 30 * time.Second
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commands := []struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
handler func(*v1.ClientCommonConfig) error
|
||||||
|
}{
|
||||||
|
{"reload", "Hot-Reload frpc configuration", ReloadHandler},
|
||||||
|
{"status", "Overview of all proxies status", StatusHandler},
|
||||||
|
{"stop", "Stop the running frpc", StopHandler},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmdConfig := range commands {
|
||||||
|
cmd := NewAdminCommand(cmdConfig.name, cmdConfig.description, cmdConfig.handler)
|
||||||
|
cmd.Flags().DurationVar(&adminAPITimeout, "api-timeout", adminAPITimeout, "Timeout for admin API calls")
|
||||||
|
rootCmd.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: name,
|
||||||
|
Short: short,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if cfg.WebServer.Port <= 0 {
|
||||||
|
fmt.Println("web server port should be set if you want to use this feature")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handler(cfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Reload(ctx, strictConfigMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("reload success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
res, err := client.GetAllProxyStatus(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Proxy Status...\n\n")
|
||||||
|
for _, typ := range proxyTypes {
|
||||||
|
arrs := res[string(typ)]
|
||||||
|
if len(arrs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strings.ToUpper(string(typ)))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Stop(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("stop success")
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,90 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(httpCmd)
|
|
||||||
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
|
|
||||||
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")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(httpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpCmd = &cobra.Command{
|
|
||||||
Use: "http",
|
|
||||||
Short: "Run frpc with a single http proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.HTTPProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.HTTPProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
|
||||||
cfg.SubDomain = subDomain
|
|
||||||
cfg.Locations = strings.Split(locations, ",")
|
|
||||||
cfg.HTTPUser = httpUser
|
|
||||||
cfg.HTTPPwd = httpPwd
|
|
||||||
cfg.HostHeaderRewrite = hostHeaderRewrite
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(httpsCmd)
|
|
||||||
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
|
||||||
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(httpsCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpsCmd = &cobra.Command{
|
|
||||||
Use: "https",
|
|
||||||
Short: "Run frpc with a single https proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.HTTPSProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.HTTPSProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
|
||||||
cfg.SubDomain = subDomain
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
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"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
natHoleSTUNServer string
|
||||||
|
natHoleLocalAddr string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
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.LoadClientConfig(cfgFile, strictConfigMode)
|
||||||
|
if err != nil {
|
||||||
|
cfg = &v1.ClientCommonConfig{}
|
||||||
|
cfg.Complete()
|
||||||
|
}
|
||||||
|
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 *v1.ClientCommonConfig) error {
|
||||||
|
if cfg.NatHoleSTUNServer == "" {
|
||||||
|
return fmt.Errorf("nat_hole_stun_server can not be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
121
cmd/frpc/sub/proxy.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// 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"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxyTypes = []v1.ProxyType{
|
||||||
|
v1.ProxyTypeTCP,
|
||||||
|
v1.ProxyTypeUDP,
|
||||||
|
v1.ProxyTypeTCPMUX,
|
||||||
|
v1.ProxyTypeHTTP,
|
||||||
|
v1.ProxyTypeHTTPS,
|
||||||
|
v1.ProxyTypeSTCP,
|
||||||
|
v1.ProxyTypeSUDP,
|
||||||
|
v1.ProxyTypeXTCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
var visitorTypes = []v1.VisitorType{
|
||||||
|
v1.VisitorTypeSTCP,
|
||||||
|
v1.VisitorTypeSUDP,
|
||||||
|
v1.VisitorTypeXTCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, typ := range proxyTypes {
|
||||||
|
c := v1.NewProxyConfigurerByType(typ)
|
||||||
|
if c == nil {
|
||||||
|
panic("proxy type: " + typ + " not support")
|
||||||
|
}
|
||||||
|
clientCfg := v1.ClientCommonConfig{}
|
||||||
|
cmd := NewProxyCommand(string(typ), c, &clientCfg)
|
||||||
|
config.RegisterClientCommonConfigFlags(cmd, &clientCfg)
|
||||||
|
config.RegisterProxyFlags(cmd, c)
|
||||||
|
|
||||||
|
// add sub command for visitor
|
||||||
|
if slices.Contains(visitorTypes, v1.VisitorType(typ)) {
|
||||||
|
vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ))
|
||||||
|
if vc == nil {
|
||||||
|
panic("visitor type: " + typ + " not support")
|
||||||
|
}
|
||||||
|
visitorCmd := NewVisitorCommand(string(typ), vc, &clientCfg)
|
||||||
|
config.RegisterVisitorFlags(visitorCmd, vc)
|
||||||
|
cmd.AddCommand(visitorCmd)
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: name,
|
||||||
|
Short: fmt.Sprintf("Run frpc with a single %s proxy", name),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
clientCfg.Complete()
|
||||||
|
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Complete(clientCfg.User)
|
||||||
|
c.GetBaseConfig().Type = name
|
||||||
|
if err := validation.ValidateProxyConfigurerForClient(c); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := startService(clientCfg, []v1.ProxyConfigurer{c}, nil, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "visitor",
|
||||||
|
Short: fmt.Sprintf("Run frpc with a single %s visitor", name),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
clientCfg.Complete()
|
||||||
|
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Complete(clientCfg)
|
||||||
|
c.GetBaseConfig().Type = name
|
||||||
|
if err := validation.ValidateVisitorConfigurer(c); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := startService(clientCfg, nil, []v1.VisitorConfigurer{c}, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -1,84 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(reloadCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reloadCmd = &cobra.Command{
|
|
||||||
Use: "reload",
|
|
||||||
Short: "Hot-Reload frpc configuration",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = reload(cfg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("frpc reload error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("reload success\n")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func reload(clientCfg config.ClientCommonConf) error {
|
|
||||||
if clientCfg.AdminPort == 0 {
|
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
|
||||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
|
||||||
clientCfg.AdminPwd))
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", authStr)
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode == 200 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
||||||
}
|
|
@ -17,82 +17,37 @@ package sub
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"path/filepath"
|
||||||
"strings"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
"github.com/fatedier/frp/pkg/featuregate"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CfgFileTypeIni = iota
|
|
||||||
CfgFileTypeCmd
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
showVersion bool
|
cfgDir string
|
||||||
|
showVersion bool
|
||||||
serverAddr string
|
strictConfigMode bool
|
||||||
user string
|
|
||||||
protocol string
|
|
||||||
token string
|
|
||||||
logLevel string
|
|
||||||
logFile string
|
|
||||||
logMaxDays int
|
|
||||||
disableLogColor bool
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
tlsEnable bool
|
|
||||||
|
|
||||||
kcpDoneCh chan struct{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors")
|
||||||
kcpDoneCh = make(chan struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
|
||||||
cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
|
||||||
cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
|
||||||
cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
|
||||||
cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
|
||||||
cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
|
||||||
cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@ -104,6 +59,13 @@ var rootCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||||
|
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||||
|
if cfgDir != "" {
|
||||||
|
_ = runMultipleClients(cfgDir)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Do not show command usage here.
|
// Do not show command usage here.
|
||||||
err := runClient(cfgFile)
|
err := runClient(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,101 +76,93 @@ 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() {
|
func Execute() {
|
||||||
|
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSignal(svr *client.Service) {
|
func handleTermSignal(svr *client.Service) {
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal, 1)
|
||||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-ch
|
<-ch
|
||||||
svr.GracefulClose(500 * time.Millisecond)
|
svr.GracefulClose(500 * time.Millisecond)
|
||||||
close(kcpDoneCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
|
||||||
cfg = config.GetDefaultClientConf()
|
|
||||||
|
|
||||||
ipStr, portStr, err := net.SplitHostPort(serverAddr)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("invalid server_addr: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.ServerAddr = ipStr
|
|
||||||
cfg.ServerPort, err = strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("invalid server_addr: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.User = user
|
|
||||||
cfg.Protocol = protocol
|
|
||||||
cfg.LogLevel = logLevel
|
|
||||||
cfg.LogFile = logFile
|
|
||||||
cfg.LogMaxDays = int64(logMaxDays)
|
|
||||||
cfg.DisableLogColor = disableLogColor
|
|
||||||
|
|
||||||
// Only token authentication is supported in cmd mode
|
|
||||||
cfg.ClientConfig = auth.GetDefaultClientConf()
|
|
||||||
cfg.Token = token
|
|
||||||
cfg.TLSEnable = tlsEnable
|
|
||||||
|
|
||||||
cfg.Complete()
|
|
||||||
if err = cfg.Validate(); err != nil {
|
|
||||||
err = fmt.Errorf("Parse config error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClient(cfgFilePath string) error {
|
func runClient(cfgFilePath string) error {
|
||||||
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
|
cfg, proxyCfgs, visitorCfgs, isLegacyFormat, err := config.LoadClientConfig(cfgFilePath, strictConfigMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
if isLegacyFormat {
|
||||||
|
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
|
||||||
|
"please use yaml/json/toml format instead!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.FeatureGates) > 0 {
|
||||||
|
if err := featuregate.SetFromMap(cfg.FeatureGates); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return startService(cfg, proxyCfgs, visitorCfgs, cfgFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startService(
|
func startService(
|
||||||
cfg config.ClientCommonConf,
|
cfg *v1.ClientCommonConfig,
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
proxyCfgs []v1.ProxyConfigurer,
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
visitorCfgs []v1.VisitorConfigurer,
|
||||||
cfgFile string,
|
cfgFile string,
|
||||||
) (err error) {
|
) error {
|
||||||
|
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
||||||
|
|
||||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
if cfgFile != "" {
|
||||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
log.Infof("start frpc service for config file [%s]", cfgFile)
|
||||||
|
defer log.Infof("frpc service for config file [%s] stopped", cfgFile)
|
||||||
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)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
svr, err := client.NewService(client.ServiceOptions{
|
||||||
if errRet != nil {
|
Common: cfg,
|
||||||
err = errRet
|
ProxyCfgs: proxyCfgs,
|
||||||
return
|
VisitorCfgs: visitorCfgs,
|
||||||
|
ConfigFilePath: cfgFile,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture the exit signal if we use kcp.
|
shouldGracefulClose := cfg.Transport.Protocol == "kcp" || cfg.Transport.Protocol == "quic"
|
||||||
if cfg.Protocol == "kcp" {
|
// Capture the exit signal if we use kcp or quic.
|
||||||
go handleSignal(svr)
|
if shouldGracefulClose {
|
||||||
|
go handleTermSignal(svr)
|
||||||
}
|
}
|
||||||
|
return svr.Run(context.Background())
|
||||||
err = svr.Run()
|
|
||||||
if err == nil && cfg.Protocol == "kcp" {
|
|
||||||
<-kcpDoneCh
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
|
|
||||||
"github.com/rodaine/table"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(statusCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusCmd = &cobra.Command{
|
|
||||||
Use: "status",
|
|
||||||
Short: "Overview of all proxies status",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = status(cfg); err != nil {
|
|
||||||
fmt.Printf("frpc get status error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func status(clientCfg config.ClientCommonConf) error {
|
|
||||||
if clientCfg.AdminPort == 0 {
|
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
|
||||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
|
||||||
clientCfg.AdminPwd))
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", authStr)
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res := &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)
|
|
||||||
}
|
|
||||||
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("")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(stcpCmd)
|
|
||||||
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
|
||||||
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")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(stcpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stcpCmd = &cobra.Command{
|
|
||||||
Use: "stcp",
|
|
||||||
Short: "Run frpc with a single stcp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := make(map[string]config.ProxyConf)
|
|
||||||
visitorConfs := make(map[string]config.VisitorConf)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
if role == "server" {
|
|
||||||
cfg := &config.STCPProxyConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.STCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
proxyConfs[cfg.ProxyName] = cfg
|
|
||||||
} else if role == "visitor" {
|
|
||||||
cfg := &config.STCPVisitorConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.STCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
err = cfg.Check()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
visitorConfs[cfg.ProxyName] = cfg
|
|
||||||
} else {
|
|
||||||
fmt.Println("invalid role")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(sudpCmd)
|
|
||||||
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
sudpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
|
||||||
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")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(sudpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sudpCmd = &cobra.Command{
|
|
||||||
Use: "sudp",
|
|
||||||
Short: "Run frpc with a single sudp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := make(map[string]config.ProxyConf)
|
|
||||||
visitorConfs := make(map[string]config.VisitorConf)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
if role == "server" {
|
|
||||||
cfg := &config.SUDPProxyConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.SUDPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
proxyConfs[cfg.ProxyName] = cfg
|
|
||||||
} else if role == "visitor" {
|
|
||||||
cfg := &config.SUDPVisitorConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.SUDPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
err = cfg.Check()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
visitorConfs[cfg.ProxyName] = cfg
|
|
||||||
} else {
|
|
||||||
fmt.Println("invalid role")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(tcpCmd)
|
|
||||||
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
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")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(tcpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tcpCmd = &cobra.Command{
|
|
||||||
Use: "tcp",
|
|
||||||
Short: "Run frpc with a single tcp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.TCPProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.TCPProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.RemotePort = remotePort
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
|
||||||
//
|
|
||||||
// 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"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(tcpMuxCmd)
|
|
||||||
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
|
|
||||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(tcpMuxCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tcpMuxCmd = &cobra.Command{
|
|
||||||
Use: "tcpmux",
|
|
||||||
Short: "Run frpc with a single tcpmux proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.TCPMuxProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.TCPMuxProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
|
||||||
cfg.SubDomain = subDomain
|
|
||||||
cfg.Multiplexer = multiplexer
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(udpCmd)
|
|
||||||
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
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")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(udpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var udpCmd = &cobra.Command{
|
|
||||||
Use: "udp",
|
|
||||||
Short: "Run frpc with a single udp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.UDPProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.UDPProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.RemotePort = remotePort
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -18,9 +18,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -31,7 +32,20 @@ var verifyCmd = &cobra.Command{
|
|||||||
Use: "verify",
|
Use: "verify",
|
||||||
Short: "Verify that the configures is valid",
|
Short: "Verify that the configures is valid",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
_, _, _, err := config.ParseClientConfig(cfgFile)
|
if cfgFile == "" {
|
||||||
|
fmt.Println("frpc: the configuration file is not specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
warning, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
// Copyright 2018 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(xtcpCmd)
|
|
||||||
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
|
||||||
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")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(xtcpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var xtcpCmd = &cobra.Command{
|
|
||||||
Use: "xtcp",
|
|
||||||
Short: "Run frpc with a single xtcp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := make(map[string]config.ProxyConf)
|
|
||||||
visitorConfs := make(map[string]config.VisitorConf)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
if role == "server" {
|
|
||||||
cfg := &config.XTCPProxyConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.XTCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
err = cfg.CheckForCli()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
proxyConfs[cfg.ProxyName] = cfg
|
|
||||||
} else if role == "visitor" {
|
|
||||||
cfg := &config.XTCPVisitorConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.XTCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
err = cfg.Check()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
visitorConfs[cfg.ProxyName] = cfg
|
|
||||||
} else {
|
|
||||||
fmt.Println("invalid role")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -15,18 +15,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/frps"
|
_ "github.com/fatedier/frp/assets/frps"
|
||||||
_ "github.com/fatedier/frp/pkg/metrics"
|
_ "github.com/fatedier/frp/pkg/metrics"
|
||||||
|
"github.com/fatedier/frp/pkg/util/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
system.EnableCompatibilityMode()
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
177
cmd/frps/root.go
@ -15,82 +15,34 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"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/pkg/util/version"
|
||||||
"github.com/fatedier/frp/server"
|
"github.com/fatedier/frp/server"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CfgFileTypeIni = iota
|
|
||||||
CfgFileTypeCmd
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
strictConfigMode bool
|
||||||
|
|
||||||
bindAddr string
|
serverCfg v1.ServerConfig
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause errors")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
config.RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||||
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().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")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&enablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
|
||||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@ -102,111 +54,64 @@ var rootCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg config.ServerCommonConf
|
var (
|
||||||
var err error
|
svrCfg *v1.ServerConfig
|
||||||
|
isLegacyFormat bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
var content []byte
|
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile, strictConfigMode)
|
||||||
content, err = config.GetRenderedConfFromFile(cfgFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if isLegacyFormat {
|
||||||
|
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
|
||||||
|
"please use yaml/json/toml format instead!\n")
|
||||||
}
|
}
|
||||||
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
|
||||||
} else {
|
} else {
|
||||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil)
|
serverCfg.Complete()
|
||||||
}
|
svrCfg = &serverCfg
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runServer(cfg)
|
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := runServer(svrCfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
|
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonConf, err error) {
|
func runServer(cfg *v1.ServerConfig) (err error) {
|
||||||
if fileType == CfgFileTypeIni {
|
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
||||||
cfg, err = config.UnmarshalServerConfFromIni(source)
|
|
||||||
} else if fileType == CfgFileTypeCmd {
|
|
||||||
cfg, err = parseServerCommonCfgFromCmd()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cfg.Complete()
|
|
||||||
err = cfg.Validate()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Parse config error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
|
||||||
cfg = config.GetDefaultServerConf()
|
|
||||||
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
cfg.BindUDPPort = bindUDPPort
|
|
||||||
cfg.KCPBindPort = kcpBindPort
|
|
||||||
cfg.ProxyBindAddr = proxyBindAddr
|
|
||||||
cfg.VhostHTTPPort = vhostHTTPPort
|
|
||||||
cfg.VhostHTTPSPort = vhostHTTPSPort
|
|
||||||
cfg.VhostHTTPTimeout = vhostHTTPTimeout
|
|
||||||
cfg.DashboardAddr = dashboardAddr
|
|
||||||
cfg.DashboardPort = dashboardPort
|
|
||||||
cfg.DashboardUser = dashboardUser
|
|
||||||
cfg.DashboardPwd = dashboardPwd
|
|
||||||
cfg.EnablePrometheus = enablePrometheus
|
|
||||||
cfg.LogFile = logFile
|
|
||||||
cfg.LogLevel = logLevel
|
|
||||||
cfg.LogMaxDays = logMaxDays
|
|
||||||
cfg.SubDomainHost = subDomainHost
|
|
||||||
cfg.TLSOnly = tlsOnly
|
|
||||||
|
|
||||||
// Only token authentication is supported in cmd mode
|
|
||||||
cfg.ServerConfig = auth.GetDefaultServerConf()
|
|
||||||
cfg.Token = token
|
|
||||||
if len(allowPorts) > 0 {
|
|
||||||
// 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)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, port := range ports {
|
|
||||||
cfg.AllowPorts[int(port)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg.MaxPortsPerClient = maxPortsPerClient
|
|
||||||
cfg.DisableLogColor = disableLogColor
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServer(cfg config.ServerCommonConf) (err error) {
|
|
||||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
|
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
log.Info("frps uses config file: %s", cfgFile)
|
log.Infof("frps uses config file: %s", cfgFile)
|
||||||
} else {
|
} else {
|
||||||
log.Info("frps uses command line arguments for config")
|
log.Infof("frps uses command line arguments for config")
|
||||||
}
|
}
|
||||||
|
|
||||||
svr, err := server.NewService(cfg)
|
svr, err := server.NewService(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("frps started successfully")
|
log.Infof("frps started successfully")
|
||||||
svr.Run()
|
svr.Run(context.Background())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -32,21 +33,23 @@ var verifyCmd = &cobra.Command{
|
|||||||
Short: "Verify that the configures is valid",
|
Short: "Verify that the configures is valid",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if cfgFile == "" {
|
if cfgFile == "" {
|
||||||
fmt.Println("no config file is specified")
|
fmt.Println("frps: the configuration file is not specified")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
|
svrCfg, _, err := config.LoadServerConfig(cfgFile, strictConfigMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = parseServerCommonCfg(CfgFileTypeIni, iniContent)
|
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
|
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
[common]
|
|
||||||
server_addr = 127.0.0.1
|
|
||||||
server_port = 7000
|
|
||||||
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
remote_port = 6000
|
|
9
conf/frpc.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
serverAddr = "127.0.0.1"
|
||||||
|
serverPort = 7000
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "test-tcp"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
remotePort = 6000
|
417
conf/frpc_full_example.toml
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
# This configuration file is for reference only. Please do not use this configuration directly to run the program as it may have various issues.
|
||||||
|
|
||||||
|
# your proxy name will be changed to {user}.{proxy}
|
||||||
|
user = "your_name"
|
||||||
|
|
||||||
|
# A literal address or host name for IPv6 must be enclosed
|
||||||
|
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||||
|
# For single serverAddr field, no need square brackets, like serverAddr = "::".
|
||||||
|
serverAddr = "0.0.0.0"
|
||||||
|
serverPort = 7000
|
||||||
|
|
||||||
|
# STUN server to help penetrate NAT hole.
|
||||||
|
# natHoleStunServer = "stun.easyvoip.com:3478"
|
||||||
|
|
||||||
|
# Decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||||
|
# default is true
|
||||||
|
loginFailExit = true
|
||||||
|
|
||||||
|
# console or real logFile path like ./frpc.log
|
||||||
|
log.to = "./frpc.log"
|
||||||
|
# trace, debug, info, warn, error
|
||||||
|
log.level = "info"
|
||||||
|
log.maxDays = 3
|
||||||
|
# disable log colors when log.to is console, default is false
|
||||||
|
log.disablePrintColor = false
|
||||||
|
|
||||||
|
auth.method = "token"
|
||||||
|
# auth.additionalScopes specifies additional scopes to include authentication information.
|
||||||
|
# Optional values are HeartBeats, NewWorkConns.
|
||||||
|
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
|
||||||
|
|
||||||
|
# auth token
|
||||||
|
auth.token = "12345678"
|
||||||
|
|
||||||
|
# oidc.clientID specifies the client ID to use to get a token in OIDC authentication.
|
||||||
|
# auth.oidc.clientID = ""
|
||||||
|
# oidc.clientSecret specifies the client secret to use to get a token in OIDC authentication.
|
||||||
|
# auth.oidc.clientSecret = ""
|
||||||
|
# oidc.audience specifies the audience of the token in OIDC authentication.
|
||||||
|
# auth.oidc.audience = ""
|
||||||
|
# oidc.scope specifies the permissions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
# auth.oidc.scope = ""
|
||||||
|
# oidc.tokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
# It will be used to get an OIDC token.
|
||||||
|
# auth.oidc.tokenEndpointURL = ""
|
||||||
|
|
||||||
|
# oidc.additionalEndpointParams 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.
|
||||||
|
# auth.oidc.additionalEndpointParams.audience = "https://dev.auth.com/api/v2/"
|
||||||
|
# auth.oidc.additionalEndpointParams.var1 = "foobar"
|
||||||
|
|
||||||
|
# Set admin address for control frpc's action by http api such as reload
|
||||||
|
webServer.addr = "127.0.0.1"
|
||||||
|
webServer.port = 7400
|
||||||
|
webServer.user = "admin"
|
||||||
|
webServer.password = "admin"
|
||||||
|
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||||
|
# webServer.assetsDir = "./static"
|
||||||
|
|
||||||
|
# Enable golang pprof handlers in admin listener.
|
||||||
|
webServer.pprofEnable = false
|
||||||
|
|
||||||
|
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||||
|
# transport.dialServerTimeout = 10
|
||||||
|
|
||||||
|
# dialServerKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
# If negative, keep-alive probes are disabled.
|
||||||
|
# transport.dialServerKeepalive = 7200
|
||||||
|
|
||||||
|
# connections will be established in advance, default value is zero
|
||||||
|
transport.poolCount = 5
|
||||||
|
|
||||||
|
# If tcp stream multiplexing is used, default is true, it must be same with frps
|
||||||
|
# transport.tcpMux = true
|
||||||
|
|
||||||
|
# Specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcpMux is enabled.
|
||||||
|
# transport.tcpMuxKeepaliveInterval = 30
|
||||||
|
|
||||||
|
# Communication protocol used to connect to server
|
||||||
|
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||||
|
transport.protocol = "tcp"
|
||||||
|
|
||||||
|
# set client binding ip when connect server, default is empty.
|
||||||
|
# only when protocol = tcp or websocket, the value will be used.
|
||||||
|
transport.connectServerLocalIP = "0.0.0.0"
|
||||||
|
|
||||||
|
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set proxyURL here or in global environment variables
|
||||||
|
# it only works when protocol is tcp
|
||||||
|
# transport.proxyURL = "http://user:passwd@192.168.1.128:8080"
|
||||||
|
# transport.proxyURL = "socks5://user:passwd@192.168.1.128:1080"
|
||||||
|
# transport.proxyURL = "ntlm://user:passwd@192.168.1.128:2080"
|
||||||
|
|
||||||
|
# quic protocol options
|
||||||
|
# transport.quic.keepalivePeriod = 10
|
||||||
|
# transport.quic.maxIdleTimeout = 30
|
||||||
|
# transport.quic.maxIncomingStreams = 100000
|
||||||
|
|
||||||
|
# If tls.enable is true, frpc will connect frps by tls.
|
||||||
|
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||||
|
transport.tls.enable = true
|
||||||
|
|
||||||
|
# transport.tls.certFile = "client.crt"
|
||||||
|
# transport.tls.keyFile = "client.key"
|
||||||
|
# transport.tls.trustedCaFile = "ca.crt"
|
||||||
|
# transport.tls.serverName = "example.com"
|
||||||
|
|
||||||
|
# If the disableCustomTLSFirstByte is set to false, frpc will establish a connection with frps using the
|
||||||
|
# first custom byte when tls is enabled.
|
||||||
|
# Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||||
|
# transport.tls.disableCustomTLSFirstByte = true
|
||||||
|
|
||||||
|
# Heartbeat configure, it's not recommended to modify the default value.
|
||||||
|
# The default value of heartbeatInterval is 10 and heartbeatTimeout is 90. Set negative value
|
||||||
|
# to disable it.
|
||||||
|
# transport.heartbeatInterval = 30
|
||||||
|
# transport.heartbeatTimeout = 90
|
||||||
|
|
||||||
|
# Specify a dns server, so frpc will use this instead of default one
|
||||||
|
# dnsServer = "8.8.8.8"
|
||||||
|
|
||||||
|
# Proxy names you want to start.
|
||||||
|
# Default is empty, means all proxies.
|
||||||
|
# start = ["ssh", "dns"]
|
||||||
|
|
||||||
|
# Specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||||
|
# This parameter should be same between client and server.
|
||||||
|
# It affects the udp and sudp proxy.
|
||||||
|
udpPacketSize = 1500
|
||||||
|
|
||||||
|
# Feature gates allows you to enable or disable experimental features
|
||||||
|
# Format is a map of feature names to boolean values
|
||||||
|
# You can enable specific features:
|
||||||
|
#featureGates = { VirtualNet = true }
|
||||||
|
|
||||||
|
# VirtualNet settings for experimental virtual network capabilities
|
||||||
|
# The virtual network feature requires enabling the VirtualNet feature gate above
|
||||||
|
# virtualNet.address = "100.86.1.1/24"
|
||||||
|
|
||||||
|
# Additional metadatas for client.
|
||||||
|
metadatas.var1 = "abc"
|
||||||
|
metadatas.var2 = "123"
|
||||||
|
|
||||||
|
# Include other config files for proxies.
|
||||||
|
# includes = ["./confd/*.ini"]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
# 'ssh' is the unique proxy name
|
||||||
|
# If global user is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||||
|
name = "ssh"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
# Limit bandwidth for this proxy, unit is KB and MB
|
||||||
|
transport.bandwidthLimit = "1MB"
|
||||||
|
# Where to limit bandwidth, can be 'client' or 'server', default is 'client'
|
||||||
|
transport.bandwidthLimitMode = "client"
|
||||||
|
# If true, traffic of this proxy will be encrypted, default is false
|
||||||
|
transport.useEncryption = false
|
||||||
|
# If true, traffic will be compressed
|
||||||
|
transport.useCompression = false
|
||||||
|
# Remote port listen by frps
|
||||||
|
remotePort = 6001
|
||||||
|
# frps will load balancing connections for proxies in same group
|
||||||
|
loadBalancer.group = "test_group"
|
||||||
|
# group should have same group key
|
||||||
|
loadBalancer.groupKey = "123456"
|
||||||
|
# Enable health check for the backend service, it supports 'tcp' and 'http' now.
|
||||||
|
# frpc will connect local service's port to detect it's healthy status
|
||||||
|
healthCheck.type = "tcp"
|
||||||
|
# Health check connection timeout
|
||||||
|
healthCheck.timeoutSeconds = 3
|
||||||
|
# If continuous failed in 3 times, the proxy will be removed from frps
|
||||||
|
healthCheck.maxFailed = 3
|
||||||
|
# Every 10 seconds will do a health check
|
||||||
|
healthCheck.intervalSeconds = 10
|
||||||
|
# Additional meta info for each proxy. It will be passed to the server-side plugin for use.
|
||||||
|
metadatas.var1 = "abc"
|
||||||
|
metadatas.var2 = "123"
|
||||||
|
# You can add some extra information to the proxy through annotations.
|
||||||
|
# These annotations will be displayed on the frps dashboard.
|
||||||
|
[proxies.annotations]
|
||||||
|
key1 = "value1"
|
||||||
|
"prefix/key2" = "value2"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "ssh_random"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "192.168.31.100"
|
||||||
|
localPort = 22
|
||||||
|
# If remotePort is 0, frps will assign a random port for you
|
||||||
|
remotePort = 0
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "dns"
|
||||||
|
type = "udp"
|
||||||
|
localIP = "114.114.114.114"
|
||||||
|
localPort = 53
|
||||||
|
remotePort = 6002
|
||||||
|
|
||||||
|
# Resolve your domain names to [serverAddr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||||
|
[[proxies]]
|
||||||
|
name = "web01"
|
||||||
|
type = "http"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 80
|
||||||
|
# http username and password are safety certification for http protocol
|
||||||
|
# if not set, you can access this customDomains without certification
|
||||||
|
httpUser = "admin"
|
||||||
|
httpPassword = "admin"
|
||||||
|
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
|
||||||
|
subdomain = "web01"
|
||||||
|
customDomains = ["web01.yourdomain.com"]
|
||||||
|
# locations is only available for http type
|
||||||
|
locations = ["/", "/pic"]
|
||||||
|
# route requests to this service if http basic auto user is abc
|
||||||
|
# routeByHTTPUser = abc
|
||||||
|
hostHeaderRewrite = "example.com"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
responseHeaders.set.foo = "bar"
|
||||||
|
healthCheck.type = "http"
|
||||||
|
# frpc will send a GET http request '/status' to local http service
|
||||||
|
# http service is alive when it return 2xx http response code
|
||||||
|
healthCheck.path = "/status"
|
||||||
|
healthCheck.intervalSeconds = 10
|
||||||
|
healthCheck.maxFailed = 3
|
||||||
|
healthCheck.timeoutSeconds = 3
|
||||||
|
# set health check headers
|
||||||
|
healthCheck.httpHeaders=[
|
||||||
|
{ name = "x-from-where", value = "frp" }
|
||||||
|
]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "web02"
|
||||||
|
type = "https"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 8000
|
||||||
|
subdomain = "web02"
|
||||||
|
customDomains = ["web02.yourdomain.com"]
|
||||||
|
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||||
|
# v1 or v2 or empty
|
||||||
|
transport.proxyProtocolVersion = "v2"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "tcpmuxhttpconnect"
|
||||||
|
type = "tcpmux"
|
||||||
|
multiplexer = "httpconnect"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 10701
|
||||||
|
customDomains = ["tunnel1"]
|
||||||
|
# routeByHTTPUser = "user1"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_unix_domain_socket"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6003
|
||||||
|
# if plugin is defined, localIP and localPort is useless
|
||||||
|
# plugin will handle connections got from frps
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "unix_domain_socket"
|
||||||
|
unixPath = "/var/run/docker.sock"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_http_proxy"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6004
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http_proxy"
|
||||||
|
httpUser = "abc"
|
||||||
|
httpPassword = "abc"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_socks5"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6005
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "socks5"
|
||||||
|
username = "abc"
|
||||||
|
password = "abc"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_static_file"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6006
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "static_file"
|
||||||
|
localPath = "/var/www/blog"
|
||||||
|
stripPrefix = "static"
|
||||||
|
httpUser = "abc"
|
||||||
|
httpPassword = "abc"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_https2http"
|
||||||
|
type = "https"
|
||||||
|
customDomains = ["test.yourdomain.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "https2http"
|
||||||
|
localAddr = "127.0.0.1:80"
|
||||||
|
crtPath = "./server.crt"
|
||||||
|
keyPath = "./server.key"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_https2https"
|
||||||
|
type = "https"
|
||||||
|
customDomains = ["test.yourdomain.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "https2https"
|
||||||
|
localAddr = "127.0.0.1:443"
|
||||||
|
crtPath = "./server.crt"
|
||||||
|
keyPath = "./server.key"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_http2https"
|
||||||
|
type = "http"
|
||||||
|
customDomains = ["test.yourdomain.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http2https"
|
||||||
|
localAddr = "127.0.0.1:443"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_http2http"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6007
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http2http"
|
||||||
|
localAddr = "127.0.0.1:80"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_tls2raw"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6008
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "tls2raw"
|
||||||
|
localAddr = "127.0.0.1:80"
|
||||||
|
crtPath = "./server.crt"
|
||||||
|
keyPath = "./server.key"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "secret_tcp"
|
||||||
|
# If the type is secret tcp, remotePort is useless
|
||||||
|
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||||
|
type = "stcp"
|
||||||
|
# secretKey is used for authentication for visitors
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allowUsers = ["*"]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "p2p_tcp"
|
||||||
|
type = "xtcp"
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allowUsers = ["user1", "user2"]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "vnet-server"
|
||||||
|
type = "stcp"
|
||||||
|
secretKey = "your-secret-key"
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "virtual_net"
|
||||||
|
|
||||||
|
# frpc role visitor -> frps -> frpc role server
|
||||||
|
[[visitors]]
|
||||||
|
name = "secret_tcp_visitor"
|
||||||
|
type = "stcp"
|
||||||
|
# the server name you want to visitor
|
||||||
|
serverName = "secret_tcp"
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
# connect this address to visitor stcp server
|
||||||
|
bindAddr = "127.0.0.1"
|
||||||
|
# bindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
# other visitors. (This is not supported for SUDP now)
|
||||||
|
bindPort = 9000
|
||||||
|
|
||||||
|
[[visitors]]
|
||||||
|
name = "p2p_tcp_visitor"
|
||||||
|
type = "xtcp"
|
||||||
|
# if the server user is not set, it defaults to the current user
|
||||||
|
serverUser = "user1"
|
||||||
|
serverName = "p2p_tcp"
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
bindAddr = "127.0.0.1"
|
||||||
|
# bindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
# other visitors. (This is not supported for SUDP now)
|
||||||
|
bindPort = 9001
|
||||||
|
# when automatic tunnel persistence is required, set it to true
|
||||||
|
keepTunnelOpen = false
|
||||||
|
# effective when keepTunnelOpen is set to true, the number of attempts to punch through per hour
|
||||||
|
maxRetriesAnHour = 8
|
||||||
|
minRetryInterval = 90
|
||||||
|
# fallbackTo = "stcp_visitor"
|
||||||
|
# fallbackTimeoutMs = 500
|
||||||
|
|
||||||
|
[[visitors]]
|
||||||
|
name = "vnet-visitor"
|
||||||
|
type = "stcp"
|
||||||
|
serverName = "vnet-server"
|
||||||
|
secretKey = "your-secret-key"
|
||||||
|
bindPort = -1
|
||||||
|
[visitors.plugin]
|
||||||
|
type = "virtual_net"
|
||||||
|
destinationIP = "100.86.0.1"
|
@ -1,2 +0,0 @@
|
|||||||
[common]
|
|
||||||
bind_port = 7000
|
|
1
conf/frps.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
bindPort = 7000
|
164
conf/frps_full_example.toml
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# This configuration file is for reference only. Please do not use this configuration directly to run the program as it may have various issues.
|
||||||
|
|
||||||
|
# A literal address or host name for IPv6 must be enclosed
|
||||||
|
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||||
|
# For single "bindAddr" field, no need square brackets, like `bindAddr = "::"`.
|
||||||
|
bindAddr = "0.0.0.0"
|
||||||
|
bindPort = 7000
|
||||||
|
|
||||||
|
# udp port used for kcp protocol, it can be same with 'bindPort'.
|
||||||
|
# if not set, kcp is disabled in frps.
|
||||||
|
kcpBindPort = 7000
|
||||||
|
|
||||||
|
# udp port used for quic protocol.
|
||||||
|
# if not set, quic is disabled in frps.
|
||||||
|
# quicBindPort = 7002
|
||||||
|
|
||||||
|
# Specify which address proxy will listen for, default value is same with bindAddr
|
||||||
|
# proxyBindAddr = "127.0.0.1"
|
||||||
|
|
||||||
|
# quic protocol options
|
||||||
|
# transport.quic.keepalivePeriod = 10
|
||||||
|
# transport.quic.maxIdleTimeout = 30
|
||||||
|
# transport.quic.maxIncomingStreams = 100000
|
||||||
|
|
||||||
|
# Heartbeat configure, it's not recommended to modify the default value
|
||||||
|
# The default value of heartbeatTimeout is 90. Set negative value to disable it.
|
||||||
|
# transport.heartbeatTimeout = 90
|
||||||
|
|
||||||
|
# Pool count in each proxy will keep no more than maxPoolCount.
|
||||||
|
transport.maxPoolCount = 5
|
||||||
|
|
||||||
|
# If tcp stream multiplexing is used, default is true
|
||||||
|
# transport.tcpMux = true
|
||||||
|
|
||||||
|
# Specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcpMux is true.
|
||||||
|
# transport.tcpMuxKeepaliveInterval = 30
|
||||||
|
|
||||||
|
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
# If negative, keep-alive probes are disabled.
|
||||||
|
# transport.tcpKeepalive = 7200
|
||||||
|
|
||||||
|
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||||
|
transport.tls.force = false
|
||||||
|
|
||||||
|
# transport.tls.certFile = "server.crt"
|
||||||
|
# transport.tls.keyFile = "server.key"
|
||||||
|
# transport.tls.trustedCaFile = "ca.crt"
|
||||||
|
|
||||||
|
# If you want to support virtual host, you must set the http port for listening (optional)
|
||||||
|
# Note: http port and https port can be same with bindPort
|
||||||
|
vhostHTTPPort = 80
|
||||||
|
vhostHTTPSPort = 443
|
||||||
|
|
||||||
|
# Response header timeout(seconds) for vhost http server, default is 60s
|
||||||
|
# vhostHTTPTimeout = 60
|
||||||
|
|
||||||
|
# tcpmuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||||
|
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
|
# requests on one single port. If it's not - it will listen on this value for
|
||||||
|
# HTTP CONNECT requests. By default, this value is 0.
|
||||||
|
# tcpmuxHTTPConnectPort = 1337
|
||||||
|
|
||||||
|
# If tcpmuxPassthrough is true, frps won't do any update on traffic.
|
||||||
|
# tcpmuxPassthrough = false
|
||||||
|
|
||||||
|
# Configure the web server to enable the dashboard for frps.
|
||||||
|
# dashboard is available only if webServer.port is set.
|
||||||
|
webServer.addr = "127.0.0.1"
|
||||||
|
webServer.port = 7500
|
||||||
|
webServer.user = "admin"
|
||||||
|
webServer.password = "admin"
|
||||||
|
# webServer.tls.certFile = "server.crt"
|
||||||
|
# webServer.tls.keyFile = "server.key"
|
||||||
|
# dashboard assets directory(only for debug mode)
|
||||||
|
# webServer.assetsDir = "./static"
|
||||||
|
|
||||||
|
# Enable golang pprof handlers in dashboard listener.
|
||||||
|
# Dashboard port must be set first
|
||||||
|
webServer.pprofEnable = false
|
||||||
|
|
||||||
|
# enablePrometheus will export prometheus metrics on webServer in /metrics api.
|
||||||
|
enablePrometheus = true
|
||||||
|
|
||||||
|
# console or real logFile path like ./frps.log
|
||||||
|
log.to = "./frps.log"
|
||||||
|
# trace, debug, info, warn, error
|
||||||
|
log.level = "info"
|
||||||
|
log.maxDays = 3
|
||||||
|
# disable log colors when log.to is console, default is false
|
||||||
|
log.disablePrintColor = false
|
||||||
|
|
||||||
|
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||||
|
detailedErrorsToClient = true
|
||||||
|
|
||||||
|
# auth.method specifies what authentication method to use authenticate frpc with frps.
|
||||||
|
# If "token" is specified - token will be read into login message.
|
||||||
|
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
auth.method = "token"
|
||||||
|
|
||||||
|
# auth.additionalScopes specifies additional scopes to include authentication information.
|
||||||
|
# Optional values are HeartBeats, NewWorkConns.
|
||||||
|
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
|
||||||
|
|
||||||
|
# auth token
|
||||||
|
auth.token = "12345678"
|
||||||
|
|
||||||
|
# oidc issuer specifies the issuer to verify OIDC tokens with.
|
||||||
|
auth.oidc.issuer = ""
|
||||||
|
# oidc audience specifies the audience OIDC tokens should contain when validated.
|
||||||
|
auth.oidc.audience = ""
|
||||||
|
# oidc skipExpiryCheck specifies whether to skip checking if the OIDC token is expired.
|
||||||
|
auth.oidc.skipExpiryCheck = false
|
||||||
|
# oidc skipIssuerCheck specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||||
|
auth.oidc.skipIssuerCheck = false
|
||||||
|
|
||||||
|
# userConnTimeout specifies the maximum time to wait for a work connection.
|
||||||
|
# userConnTimeout = 10
|
||||||
|
|
||||||
|
# Only allow frpc to bind ports you list. By default, there won't be any limit.
|
||||||
|
allowPorts = [
|
||||||
|
{ start = 2000, end = 3000 },
|
||||||
|
{ single = 3001 },
|
||||||
|
{ single = 3003 },
|
||||||
|
{ start = 4000, end = 50000 }
|
||||||
|
]
|
||||||
|
|
||||||
|
# Max ports can be used for each client, default value is 0 means no limit
|
||||||
|
maxPortsPerClient = 0
|
||||||
|
|
||||||
|
# If subDomainHost is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||||
|
# When subdomain is test, the host used by routing is test.frps.com
|
||||||
|
subDomainHost = "frps.com"
|
||||||
|
|
||||||
|
# custom 404 page for HTTP requests
|
||||||
|
# custom404Page = "/path/to/404.html"
|
||||||
|
|
||||||
|
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||||
|
# This parameter should be same between client and server.
|
||||||
|
# It affects the udp and sudp proxy.
|
||||||
|
udpPacketSize = 1500
|
||||||
|
|
||||||
|
# Retention time for NAT hole punching strategy data.
|
||||||
|
natholeAnalysisDataReserveHours = 168
|
||||||
|
|
||||||
|
# ssh tunnel gateway
|
||||||
|
# If you want to enable this feature, the bindPort parameter is required, while others are optional.
|
||||||
|
# By default, this feature is disabled. It will be enabled if bindPort is greater than 0.
|
||||||
|
# sshTunnelGateway.bindPort = 2200
|
||||||
|
# sshTunnelGateway.privateKeyFile = "/home/frp-user/.ssh/id_rsa"
|
||||||
|
# sshTunnelGateway.autoGenPrivateKeyPath = ""
|
||||||
|
# sshTunnelGateway.authorizedKeysFile = "/home/frp-user/.ssh/authorized_keys"
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "user-manager"
|
||||||
|
addr = "127.0.0.1:9000"
|
||||||
|
path = "/handler"
|
||||||
|
ops = ["Login"]
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "port-manager"
|
||||||
|
addr = "127.0.0.1:9001"
|
||||||
|
path = "/handler"
|
||||||
|
ops = ["NewProxy"]
|
@ -6,6 +6,9 @@
|
|||||||
server_addr = 0.0.0.0
|
server_addr = 0.0.0.0
|
||||||
server_port = 7000
|
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.
|
# 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_timeout = 10
|
||||||
|
|
||||||
@ -40,6 +43,8 @@ authenticate_new_work_conns = false
|
|||||||
# auth token
|
# auth token
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
|
authentication_method =
|
||||||
|
|
||||||
# oidc_client_id specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
# oidc_client_id specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||||
# By default, this value is "".
|
# By default, this value is "".
|
||||||
oidc_client_id =
|
oidc_client_id =
|
||||||
@ -51,6 +56,9 @@ oidc_client_secret =
|
|||||||
# oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
# oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
oidc_audience =
|
oidc_audience =
|
||||||
|
|
||||||
|
# oidc_scope specifies the permissions 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.
|
# 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 "".
|
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
oidc_token_endpoint_url =
|
oidc_token_endpoint_url =
|
||||||
@ -87,14 +95,20 @@ user = your_name
|
|||||||
login_fail_exit = true
|
login_fail_exit = true
|
||||||
|
|
||||||
# communication protocol used to connect to server
|
# communication protocol used to connect to server
|
||||||
# now it supports tcp, kcp and websocket, default is tcp
|
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||||
protocol = tcp
|
protocol = tcp
|
||||||
|
|
||||||
# set client binding ip when connect server, default is empty.
|
# set client binding ip when connect server, default is empty.
|
||||||
# only when protocol = tcp or websocket, the value will be used.
|
# only when protocol = tcp or websocket, the value will be used.
|
||||||
connect_server_local_ip = 0.0.0.0
|
connect_server_local_ip = 0.0.0.0
|
||||||
|
|
||||||
# if tls_enable is true, frpc will connect frps by tls
|
# 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.
|
||||||
|
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||||
tls_enable = true
|
tls_enable = true
|
||||||
|
|
||||||
# tls_cert_file = client.crt
|
# tls_cert_file = client.crt
|
||||||
@ -105,7 +119,7 @@ tls_enable = true
|
|||||||
# specify a dns server, so frpc will use this instead of default one
|
# specify a dns server, so frpc will use this instead of default one
|
||||||
# dns_server = 8.8.8.8
|
# dns_server = 8.8.8.8
|
||||||
|
|
||||||
# proxy names you want to start seperated by ','
|
# proxy names you want to start separated by ','
|
||||||
# default is empty, means all proxies
|
# default is empty, means all proxies
|
||||||
# start = ssh,dns
|
# start = ssh,dns
|
||||||
|
|
||||||
@ -127,9 +141,10 @@ udp_packet_size = 1500
|
|||||||
# include other config files for proxies.
|
# include other config files for proxies.
|
||||||
# includes = ./confd/*.ini
|
# includes = ./confd/*.ini
|
||||||
|
|
||||||
# By default, frpc will connect frps with first custom byte if tls is enabled.
|
# If the disable_custom_tls_first_byte is set to false, frpc will establish a connection with frps using the
|
||||||
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
|
# first custom byte when tls is enabled.
|
||||||
disable_custom_tls_first_byte = false
|
# Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||||
|
disable_custom_tls_first_byte = true
|
||||||
|
|
||||||
# Enable golang pprof handlers in admin listener.
|
# Enable golang pprof handlers in admin listener.
|
||||||
# Admin port must be set first.
|
# Admin port must be set first.
|
||||||
@ -144,6 +159,8 @@ local_ip = 127.0.0.1
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
# limit bandwidth for this proxy, unit is KB and MB
|
# limit bandwidth for this proxy, unit is KB and MB
|
||||||
bandwidth_limit = 1MB
|
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
|
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
# if true, message will be compressed
|
# if true, message will be compressed
|
||||||
@ -216,6 +233,8 @@ subdomain = web01
|
|||||||
custom_domains = web01.yourdomain.com
|
custom_domains = web01.yourdomain.com
|
||||||
# locations is only available for http type
|
# locations is only available for http type
|
||||||
locations = /,/pic
|
locations = /,/pic
|
||||||
|
# route requests to this service if http basic auto user is abc
|
||||||
|
# route_by_http_user = abc
|
||||||
host_header_rewrite = example.com
|
host_header_rewrite = example.com
|
||||||
# params with prefix "header_" will be used to update http request headers
|
# params with prefix "header_" will be used to update http request headers
|
||||||
header_X-From-Where = frp
|
header_X-From-Where = frp
|
||||||
@ -233,7 +252,7 @@ local_ip = 127.0.0.1
|
|||||||
local_port = 8000
|
local_port = 8000
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
subdomain = web01
|
subdomain = web02
|
||||||
custom_domains = web02.yourdomain.com
|
custom_domains = web02.yourdomain.com
|
||||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||||
# v1 or v2 or empty
|
# v1 or v2 or empty
|
||||||
@ -309,6 +328,9 @@ local_ip = 127.0.0.1
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allow_users = *
|
||||||
|
|
||||||
# user of frpc should be same in both stcp server and stcp visitor
|
# user of frpc should be same in both stcp server and stcp visitor
|
||||||
[secret_tcp_visitor]
|
[secret_tcp_visitor]
|
||||||
@ -320,6 +342,8 @@ server_name = secret_tcp
|
|||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
# connect this address to visitor stcp server
|
# connect this address to visitor stcp server
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
|
# bind_port can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
# other visitors. (This is not supported for SUDP now)
|
||||||
bind_port = 9000
|
bind_port = 9000
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
@ -331,16 +355,30 @@ local_ip = 127.0.0.1
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allow_users = user1, user2
|
||||||
|
|
||||||
[p2p_tcp_visitor]
|
[p2p_tcp_visitor]
|
||||||
role = visitor
|
role = visitor
|
||||||
type = xtcp
|
type = xtcp
|
||||||
|
# if the server user is not set, it defaults to the current user
|
||||||
|
server_user = user1
|
||||||
server_name = p2p_tcp
|
server_name = p2p_tcp
|
||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
|
# bind_port can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
# other visitors. (This is not supported for SUDP now)
|
||||||
bind_port = 9001
|
bind_port = 9001
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = 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
|
||||||
|
# fallback_to = stcp_visitor
|
||||||
|
# fallback_timeout_ms = 500
|
||||||
|
|
||||||
[tcpmuxhttpconnect]
|
[tcpmuxhttpconnect]
|
||||||
type = tcpmux
|
type = tcpmux
|
||||||
@ -348,3 +386,4 @@ multiplexer = httpconnect
|
|||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 10701
|
local_port = 10701
|
||||||
custom_domains = tunnel1
|
custom_domains = tunnel1
|
||||||
|
# route_by_http_user = user1
|
@ -6,13 +6,18 @@
|
|||||||
bind_addr = 0.0.0.0
|
bind_addr = 0.0.0.0
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
|
|
||||||
# udp port to help make udp hole to penetrate nat
|
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
||||||
bind_udp_port = 7001
|
# 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
|
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
|
# specify which address proxy will listen for, default value is same with bind_addr
|
||||||
# proxy_bind_addr = 127.0.0.1
|
# proxy_bind_addr = 127.0.0.1
|
||||||
|
|
||||||
@ -30,6 +35,9 @@ vhost_https_port = 443
|
|||||||
# HTTP CONNECT requests. By default, this value is 0.
|
# HTTP CONNECT requests. By default, this value is 0.
|
||||||
# tcpmux_httpconnect_port = 1337
|
# 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
|
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||||
# dashboard_addr's default value is same with bind_addr
|
# dashboard_addr's default value is same with bind_addr
|
||||||
# dashboard is available only if dashboard_port is set
|
# dashboard is available only if dashboard_port is set
|
||||||
@ -40,6 +48,11 @@ dashboard_port = 7500
|
|||||||
dashboard_user = admin
|
dashboard_user = admin
|
||||||
dashboard_pwd = 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 will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
|
||||||
enable_prometheus = true
|
enable_prometheus = true
|
||||||
|
|
||||||
@ -141,6 +154,9 @@ udp_packet_size = 1500
|
|||||||
# Dashboard port must be set first
|
# Dashboard port must be set first
|
||||||
pprof_enable = false
|
pprof_enable = false
|
||||||
|
|
||||||
|
# Retention time for NAT hole punching strategy data.
|
||||||
|
nat_hole_analysis_data_reserve_hours = 168
|
||||||
|
|
||||||
[plugin.user-manager]
|
[plugin.user-manager]
|
||||||
addr = 127.0.0.1:9000
|
addr = 127.0.0.1:9000
|
||||||
path = /handler
|
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
|
|
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 27 KiB |
BIN
doc/pic/sponsor_daytona.png
Normal file
After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_jetbrains.jpg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
doc/pic/sponsor_lokal.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
doc/pic/sponsor_olares.jpeg
Normal file
After Width: | Height: | Size: 46 KiB |
@ -110,6 +110,8 @@ Create new proxy
|
|||||||
"proxy_type": <string>,
|
"proxy_type": <string>,
|
||||||
"use_encryption": <bool>,
|
"use_encryption": <bool>,
|
||||||
"use_compression": <bool>,
|
"use_compression": <bool>,
|
||||||
|
"bandwidth_limit": <string>,
|
||||||
|
"bandwidth_limit_mode": <string>,
|
||||||
"group": <string>,
|
"group": <string>,
|
||||||
"group_key": <string>,
|
"group_key": <string>,
|
||||||
|
|
||||||
@ -119,7 +121,7 @@ Create new proxy
|
|||||||
// http and https only
|
// http and https only
|
||||||
"custom_domains": []<string>,
|
"custom_domains": []<string>,
|
||||||
"subdomain": <string>,
|
"subdomain": <string>,
|
||||||
"locations": <string>,
|
"locations": []<string>,
|
||||||
"http_user": <string>,
|
"http_user": <string>,
|
||||||
"http_pwd": <string>,
|
"http_pwd": <string>,
|
||||||
"host_header_rewrite": <string>,
|
"host_header_rewrite": <string>,
|
||||||
@ -214,49 +216,50 @@ New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcp
|
|||||||
|
|
||||||
### Server Plugin Configuration
|
### Server Plugin Configuration
|
||||||
|
|
||||||
```ini
|
```toml
|
||||||
# frps.ini
|
# frps.toml
|
||||||
[common]
|
bindPort = 7000
|
||||||
bind_port = 7000
|
|
||||||
|
|
||||||
[plugin.user-manager]
|
[[httpPlugins]]
|
||||||
addr = 127.0.0.1:9000
|
name = "user-manager"
|
||||||
path = /handler
|
addr = "127.0.0.1:9000"
|
||||||
ops = Login
|
path = "/handler"
|
||||||
|
ops = ["Login"]
|
||||||
|
|
||||||
[plugin.port-manager]
|
[[httpPlugins]]
|
||||||
addr = 127.0.0.1:9001
|
name = "port-manager"
|
||||||
path = /handler
|
addr = "127.0.0.1:9001"
|
||||||
ops = NewProxy
|
path = "/handler"
|
||||||
|
ops = ["NewProxy"]
|
||||||
```
|
```
|
||||||
|
|
||||||
- addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = https://127.0.0.1:9001`.
|
- addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = "https://127.0.0.1:9001"`.
|
||||||
- path: http request url path for the POST request.
|
- path: http request url path for the POST request.
|
||||||
- ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
- ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
||||||
- tls_verify: When the schema is https, we verify by default. Set this value to false if you want to skip verification.
|
- tlsVerify: When the schema is https, we verify by default. Set this value to false if you want to skip verification.
|
||||||
|
|
||||||
### Metadata
|
### Metadata
|
||||||
|
|
||||||
Metadata will be sent to the server plugin in each RPC request.
|
Metadata will be sent to the server plugin in each RPC request.
|
||||||
|
|
||||||
There are 2 types of metadata entries - 1 under `[common]` and the other under each proxy configuration.
|
There are 2 types of metadata entries - global one and the other under each proxy configuration.
|
||||||
Metadata entries under `[common]` will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
|
Global metadata entries will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
|
||||||
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
|
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
|
||||||
|
|
||||||
Metadata entries start with `meta_`. This is an example of metadata entries in `[common]` and under the proxy named `[ssh]`:
|
This is an example of metadata entries:
|
||||||
|
|
||||||
```
|
```toml
|
||||||
# frpc.ini
|
# frpc.toml
|
||||||
[common]
|
serverAddr = "127.0.0.1"
|
||||||
server_addr = 127.0.0.1
|
serverPort = 7000
|
||||||
server_port = 7000
|
user = "fake"
|
||||||
user = fake
|
metadatas.token = "fake"
|
||||||
meta_token = fake
|
metadatas.version = "1.0.0"
|
||||||
meta_version = 1.0.0
|
|
||||||
|
|
||||||
[ssh]
|
[[proxies]]
|
||||||
type = tcp
|
name = "ssh"
|
||||||
local_port = 22
|
type = "tcp"
|
||||||
remote_port = 6000
|
localPort = 22
|
||||||
meta_id = 123
|
remotePort = 6000
|
||||||
|
metadatas.id = "123"
|
||||||
```
|
```
|
||||||
|
160
doc/ssh_tunnel_gateway.md
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
### SSH Tunnel Gateway
|
||||||
|
|
||||||
|
*Added in v0.53.0*
|
||||||
|
|
||||||
|
### Concept
|
||||||
|
|
||||||
|
SSH supports reverse proxy capabilities [rfc](https://www.rfc-editor.org/rfc/rfc4254#page-16).
|
||||||
|
|
||||||
|
frp supports listening on an SSH port on the frps side to achieve TCP protocol proxying using the SSH -R protocol. This mode does not rely on frpc.
|
||||||
|
|
||||||
|
SSH reverse tunneling proxying and proxying SSH ports through frp are two different concepts. SSH reverse tunneling proxying is essentially a basic reverse proxying accomplished by connecting to frps via an SSH client when you don't want to use frpc.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frps.toml
|
||||||
|
sshTunnelGateway.bindPort = 0
|
||||||
|
sshTunnelGateway.privateKeyFile = ""
|
||||||
|
sshTunnelGateway.autoGenPrivateKeyPath = ""
|
||||||
|
sshTunnelGateway.authorizedKeysFile = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description | Required |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| bindPort| int | The ssh server port that frps listens on.| Yes |
|
||||||
|
| privateKeyFile | string | Default value is empty. The private key file used by the ssh server. If it is empty, frps will read the private key file under the autoGenPrivateKeyPath path. It can reuse the /home/user/.ssh/id_rsa file on the local machine, or a custom path can be specified.| No |
|
||||||
|
| autoGenPrivateKeyPath | string |Default value is ./.autogen_ssh_key. If the file does not exist or its content is empty, frps will automatically generate RSA private key file content and store it in this file.|No|
|
||||||
|
| authorizedKeysFile | string |Default value is empty. If it is empty, ssh client authentication is not authenticated. If it is not empty, it can implement ssh password-free login authentication. It can reuse the local /home/user/.ssh/authorized_keys file or a custom path can be specified.| No |
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
#### Server-side frps
|
||||||
|
|
||||||
|
Minimal configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
sshTunnelGateway.bindPort = 2200
|
||||||
|
```
|
||||||
|
|
||||||
|
Place the above configuration in frps.toml and run `./frps -c frps.toml`. It will listen on port 2200 and accept SSH reverse proxy requests.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
1. When using the minimal configuration, a `.autogen_ssh_key` private key file will be automatically created in the current working directory. The SSH server of frps will use this private key file for encryption and decryption. Alternatively, you can reuse an existing private key file on your local machine, such as `/home/user/.ssh/id_rsa`.
|
||||||
|
|
||||||
|
2. When running frps in the minimal configuration mode, connecting to frps via SSH does not require authentication. It is strongly recommended to configure a token in frps and specify the token in the SSH command line.
|
||||||
|
|
||||||
|
#### Client-side SSH
|
||||||
|
|
||||||
|
The command format is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -R :80:{local_ip:port} v0@{frps_address} -p {frps_ssh_listen_port} {tcp|http|https|stcp|tcpmux} --remote_port {real_remote_port} --proxy_name {proxy_name} --token {frp_token}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. `--proxy_name` is optional, and if left empty, a random one will be generated.
|
||||||
|
2. The username for logging in to frps is always "v0" and currently has no significance, i.e., `v0@{frps_address}`.
|
||||||
|
3. The server-side proxy listens on the port determined by `--remote_port`.
|
||||||
|
4. `{tcp|http|https|stcp|tcpmux}` supports the complete command parameters, which can be obtained by using `--help`. For example: `ssh -R :80::8080 v0@127.0.0.1 -p 2200 http --help`.
|
||||||
|
5. The token is optional, but for security reasons, it is strongly recommended to configure the token in frps.
|
||||||
|
|
||||||
|
#### TCP Proxy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -R :80:127.0.0.1:8080 v0@{frp_address} -p 2200 tcp --proxy_name "test-tcp" --remote_port 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
This sets up a proxy on frps that listens on port 9090 and proxies local service on port 8080.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
frp (via SSH) (Ctrl+C to quit)
|
||||||
|
|
||||||
|
User:
|
||||||
|
ProxyName: test-tcp
|
||||||
|
Type: tcp
|
||||||
|
RemoteAddress: :9090
|
||||||
|
```
|
||||||
|
|
||||||
|
Equivalent to:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote_port 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
More parameters can be obtained by executing `--help`.
|
||||||
|
|
||||||
|
#### HTTP Proxy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 http --proxy_name "test-http" --custom_domain test-http.frps.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Equivalent to:
|
||||||
|
```bash
|
||||||
|
frpc http --proxy_name "test-http" --custom_domain test-http.frps.com
|
||||||
|
```
|
||||||
|
|
||||||
|
You can access the HTTP service using the following command:
|
||||||
|
|
||||||
|
curl 'http://test-http.frps.com'
|
||||||
|
|
||||||
|
More parameters can be obtained by executing --help.
|
||||||
|
|
||||||
|
#### HTTPS/STCP/TCPMUX Proxy
|
||||||
|
|
||||||
|
To obtain the usage instructions, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 {https|stcp|tcpmux} --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Usage
|
||||||
|
|
||||||
|
#### Reusing the id_rsa File on the Local Machine
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frps.toml
|
||||||
|
sshTunnelGateway.bindPort = 2200
|
||||||
|
sshTunnelGateway.privateKeyFile = "/home/user/.ssh/id_rsa"
|
||||||
|
```
|
||||||
|
|
||||||
|
During the SSH protocol handshake, public keys are exchanged for data encryption. Therefore, the SSH server on the frps side needs to specify a private key file, which can be reused from an existing file on the local machine. If the privateKeyFile field is empty, frps will automatically create an RSA private key file.
|
||||||
|
|
||||||
|
#### Specifying the Auto-Generated Private Key File Path
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frps.toml
|
||||||
|
sshTunnelGateway.bindPort = 2200
|
||||||
|
sshTunnelGateway.autoGenPrivateKeyPath = "/var/frp/ssh-private-key-file"
|
||||||
|
```
|
||||||
|
|
||||||
|
frps will automatically create a private key file and store it at the specified path.
|
||||||
|
|
||||||
|
Note: Changing the private key file in frps can cause SSH client login failures. If you need to log in successfully, you can delete the old records from the `/home/user/.ssh/known_hosts` file.
|
||||||
|
|
||||||
|
#### Using an Existing authorized_keys File for SSH Public Key Authentication
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frps.toml
|
||||||
|
sshTunnelGateway.bindPort = 2200
|
||||||
|
sshTunnelGateway.authorizedKeysFile = "/home/user/.ssh/authorized_keys"
|
||||||
|
```
|
||||||
|
|
||||||
|
The authorizedKeysFile is the file used for SSH public key authentication, which contains the public key information for users, with one key per line.
|
||||||
|
|
||||||
|
If authorizedKeysFile is empty, frps won't perform any authentication for SSH clients. Frps does not support SSH username and password authentication.
|
||||||
|
|
||||||
|
You can reuse an existing `authorized_keys` file on your local machine for client authentication.
|
||||||
|
|
||||||
|
Note: authorizedKeysFile is for user authentication during the SSH login phase, while the token is for frps authentication. These two authentication methods are independent. SSH authentication comes first, followed by frps token authentication. It is strongly recommended to enable at least one of them. If authorizedKeysFile is empty, it is highly recommended to enable token authentication in frps to avoid security risks.
|
||||||
|
|
||||||
|
#### Using a Custom authorized_keys File for SSH Public Key Authentication
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frps.toml
|
||||||
|
sshTunnelGateway.bindPort = 2200
|
||||||
|
sshTunnelGateway.authorizedKeysFile = "/var/frps/custom_authorized_keys_file"
|
||||||
|
```
|
||||||
|
|
||||||
|
Specify the path to a custom `authorized_keys` file.
|
||||||
|
|
||||||
|
Note that changes to the authorizedKeysFile file may result in SSH authentication failures. You may need to re-add the public key information to the authorizedKeysFile.
|
73
doc/virtual_net.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Virtual Network (VirtualNet)
|
||||||
|
|
||||||
|
*Alpha feature added in v0.62.0*
|
||||||
|
|
||||||
|
The VirtualNet feature enables frp to create and manage virtual network connections between clients and visitors through a TUN interface. This allows for IP-level routing between machines, extending frp beyond simple port forwarding to support full network connectivity.
|
||||||
|
|
||||||
|
> **Note**: VirtualNet is an Alpha stage feature and is currently unstable. Its configuration methods and functionality may be adjusted and changed at any time in subsequent versions. Do not use this feature in production environments; it is only recommended for testing and evaluation purposes.
|
||||||
|
|
||||||
|
## Enabling VirtualNet
|
||||||
|
|
||||||
|
Since VirtualNet is currently an alpha feature, you need to enable it with feature gates in your configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml
|
||||||
|
featureGates = { VirtualNet = true }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
To use the virtual network capabilities:
|
||||||
|
|
||||||
|
1. First, configure your frpc with a virtual network address:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml
|
||||||
|
serverAddr = "x.x.x.x"
|
||||||
|
serverPort = 7000
|
||||||
|
featureGates = { VirtualNet = true }
|
||||||
|
|
||||||
|
# Configure the virtual network interface
|
||||||
|
virtualNet.address = "100.86.0.1/24"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. For client proxies, use the `virtual_net` plugin:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml (server side)
|
||||||
|
[[proxies]]
|
||||||
|
name = "vnet-server"
|
||||||
|
type = "stcp"
|
||||||
|
secretKey = "your-secret-key"
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "virtual_net"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. For visitor connections, configure the `virtual_net` visitor plugin:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml (client side)
|
||||||
|
serverAddr = "x.x.x.x"
|
||||||
|
serverPort = 7000
|
||||||
|
featureGates = { VirtualNet = true }
|
||||||
|
|
||||||
|
# Configure the virtual network interface
|
||||||
|
virtualNet.address = "100.86.0.2/24"
|
||||||
|
|
||||||
|
[[visitors]]
|
||||||
|
name = "vnet-visitor"
|
||||||
|
type = "stcp"
|
||||||
|
serverName = "vnet-server"
|
||||||
|
secretKey = "your-secret-key"
|
||||||
|
bindPort = -1
|
||||||
|
[visitors.plugin]
|
||||||
|
type = "virtual_net"
|
||||||
|
destinationIP = "100.86.0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements and Limitations
|
||||||
|
|
||||||
|
- **Permissions**: Creating a TUN interface requires elevated permissions (root/admin)
|
||||||
|
- **Platform Support**: Currently supported on Linux and macOS
|
||||||
|
- **Default Status**: As an alpha feature, VirtualNet is disabled by default
|
||||||
|
- **Configuration**: A valid IP/CIDR must be provided for each endpoint in the virtual network
|
@ -1,14 +1,14 @@
|
|||||||
FROM alpine:3 AS temp
|
FROM golang:1.23 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
|
||||||
|
|
||||||
WORKDIR /app
|
RUN apk add --no-cache tzdata
|
||||||
|
|
||||||
COPY --from=temp /tmp/frpc /usr/bin
|
COPY --from=building /building/bin/frpc /usr/bin/frpc
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/frpc"]
|
ENTRYPOINT ["/usr/bin/frpc"]
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
FROM alpine:3 AS temp
|
FROM golang:1.23 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
|
||||||
|
|
||||||
WORKDIR /app
|
RUN apk add --no-cache tzdata
|
||||||
|
|
||||||
COPY --from=temp /tmp/frps /usr/bin
|
COPY --from=building /building/bin/frps /usr/bin/frps
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/frps"]
|
ENTRYPOINT ["/usr/bin/frps"]
|
||||||
|
107
go.mod
@ -1,33 +1,86 @@
|
|||||||
module github.com/fatedier/frp
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
go 1.16
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
github.com/fatedier/golib v0.5.1
|
||||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac
|
github.com/google/uuid v1.6.0
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/go-playground/validator/v10 v10.6.1
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/onsi/ginkgo/v2 v2.23.4
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/onsi/gomega v1.36.3
|
||||||
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c
|
github.com/pelletier/go-toml/v2 v2.2.0
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/pion/stun/v2 v2.0.0
|
||||||
github.com/onsi/ginkgo v1.16.4
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/onsi/gomega v1.13.0
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/pires/go-proxyproto v0.5.0
|
github.com/quic-go/quic-go v0.48.2
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/rodaine/table v1.2.0
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/samber/lo v1.47.0
|
||||||
github.com/rodaine/table v1.0.1
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/spf13/pflag v1.0.5
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
github.com/tidwall/gjson v1.17.1
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
github.com/vishvananda/netlink v1.3.0
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
github.com/xtaci/kcp-go/v5 v5.6.13
|
||||||
gopkg.in/ini.v1 v1.62.0
|
golang.org/x/crypto v0.37.0
|
||||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
golang.org/x/net v0.39.0
|
||||||
k8s.io/apimachinery v0.21.2
|
golang.org/x/oauth2 v0.28.0
|
||||||
k8s.io/client-go v0.21.2
|
golang.org/x/sync v0.13.0
|
||||||
|
golang.org/x/time v0.5.0
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
|
gopkg.in/ini.v1 v1.67.0
|
||||||
|
k8s.io/apimachinery v0.28.8
|
||||||
|
k8s.io/client-go v0.28.8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
|
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||||
|
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||||
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
|
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||||
|
github.com/pion/transport/v3 v3.0.1 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
|
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
||||||
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
|
golang.org/x/text v0.24.0 // indirect
|
||||||
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository.
|
||||||
|
replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d
|
||||||
|
788
go.sum
@ -1,691 +1,293 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
|
||||||
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
|
||||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
|
||||||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
||||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
|
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
|
||||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
|
||||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
|
||||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
|
||||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
|
|
||||||
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c h1:nqkErwUGfpZZMqj29WZ9U/wz2OpJVDuiokLhE/3Y7IQ=
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
|
|
||||||
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
|
||||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
|
||||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
|
||||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
|
||||||
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
|
|
||||||
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
|
||||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
|
||||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
||||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
|
||||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
|
||||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
|
||||||
|
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||||
|
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
|
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
|
||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
k8s.io/apimachinery v0.28.8/go.mod h1:cBnwIM3fXoRo28SqbV/Ihxf/iviw85KyXOrzxvZQ83U=
|
||||||
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
|
k8s.io/client-go v0.28.8 h1:TE59Tjd87WKvS2FPBTfIKLFX0nQJ4SSHsnDo5IHjgOw=
|
||||||
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
|
k8s.io/client-go v0.28.8/go.mod h1:uDVQ/rPzWpWIy40c6lZ4mUwaEvRWGnpoqSO4FM65P3o=
|
||||||
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
|
||||||
k8s.io/client-go v0.21.2 h1:Q1j4L/iMN4pTw6Y4DWppBoUxgKO8LbffEMVEV00MUp0=
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||||
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||||
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
|
|
||||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
|
||||||
|
63
hack/download.sh
Executable file
@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
OS="$(go env GOOS)"
|
||||||
|
ARCH="$(go env GOARCH)"
|
||||||
|
|
||||||
|
if [ "${TARGET_OS}" ]; then
|
||||||
|
OS="${TARGET_OS}"
|
||||||
|
fi
|
||||||
|
if [ "${TARGET_ARCH}" ]; then
|
||||||
|
ARCH="${TARGET_ARCH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine the latest version by version number ignoring alpha, beta, and rc versions.
|
||||||
|
if [ "${FRP_VERSION}" = "" ] ; then
|
||||||
|
FRP_VERSION="$(curl -sL https://github.com/fatedier/frp/releases | \
|
||||||
|
grep -o 'releases/tag/v[0-9]*.[0-9]*.[0-9]*"' | sort -V | \
|
||||||
|
tail -1 | awk -F'/' '{ print $3}')"
|
||||||
|
FRP_VERSION="${FRP_VERSION%?}"
|
||||||
|
FRP_VERSION="${FRP_VERSION#?}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${FRP_VERSION}" = "" ] ; then
|
||||||
|
printf "Unable to get latest frp version. Set FRP_VERSION env var and re-run. For example: export FRP_VERSION=1.0.0"
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUFFIX=".tar.gz"
|
||||||
|
if [ "${OS}" = "windows" ] ; then
|
||||||
|
SUFFIX=".zip"
|
||||||
|
fi
|
||||||
|
NAME="frp_${FRP_VERSION}_${OS}_${ARCH}${SUFFIX}"
|
||||||
|
DIR_NAME="frp_${FRP_VERSION}_${OS}_${ARCH}"
|
||||||
|
URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${NAME}"
|
||||||
|
|
||||||
|
download_and_extract() {
|
||||||
|
printf "Downloading %s from %s ...\n" "$NAME" "${URL}"
|
||||||
|
if ! curl -o /dev/null -sIf "${URL}"; then
|
||||||
|
printf "\n%s is not found, please specify a valid FRP_VERSION\n" "${URL}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
curl -fsLO "${URL}"
|
||||||
|
filename=$NAME
|
||||||
|
|
||||||
|
if [ "${OS}" = "windows" ]; then
|
||||||
|
unzip "${filename}"
|
||||||
|
else
|
||||||
|
tar -xzf "${filename}"
|
||||||
|
fi
|
||||||
|
rm "${filename}"
|
||||||
|
|
||||||
|
if [ "${TARGET_DIRNAME}" ]; then
|
||||||
|
mv "${DIR_NAME}" "${TARGET_DIRNAME}"
|
||||||
|
DIR_NAME="${TARGET_DIRNAME}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
download_and_extract
|
||||||
|
|
||||||
|
printf ""
|
||||||
|
printf "\nfrp %s Download Complete!\n" "$FRP_VERSION"
|
||||||
|
printf "\n"
|
||||||
|
printf "frp has been successfully downloaded into the %s folder on your system.\n" "$DIR_NAME"
|
||||||
|
printf "\n"
|
@ -1,20 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
|
SCRIPT=$(readlink -f "$0")
|
||||||
|
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
||||||
|
|
||||||
which ginkgo &> /dev/null
|
# Check if ginkgo is available
|
||||||
if [ $? -ne 0 ]; then
|
if ! command -v ginkgo >/dev/null 2>&1; then
|
||||||
echo "ginkgo not found, try to install..."
|
echo "ginkgo not found, try to install..."
|
||||||
go install github.com/onsi/ginkgo/ginkgo@latest
|
go install github.com/onsi/ginkgo/v2/ginkgo@v2.23.4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug=false
|
debug=false
|
||||||
if [ x${DEBUG} == x"true" ]; then
|
if [ "x${DEBUG}" = "xtrue" ]; then
|
||||||
debug=true
|
debug=true
|
||||||
fi
|
fi
|
||||||
logLevel=debug
|
logLevel=debug
|
||||||
if [ x${LOG_LEVEL} != x"" ]; then
|
if [ "${LOG_LEVEL}" ]; then
|
||||||
logLevel=${LOG_LEVEL}
|
logLevel="${LOG_LEVEL}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
frpcPath=${ROOT}/bin/frpc
|
||||||
|
if [ "${FRPC_PATH}" ]; then
|
||||||
|
frpcPath="${FRPC_PATH}"
|
||||||
|
fi
|
||||||
|
frpsPath=${ROOT}/bin/frps
|
||||||
|
if [ "${FRPS_PATH}" ]; then
|
||||||
|
frpsPath="${FRPS_PATH}"
|
||||||
|
fi
|
||||||
|
concurrency="16"
|
||||||
|
if [ "${CONCURRENCY}" ]; then
|
||||||
|
concurrency="${CONCURRENCY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ginkgo -nodes=${concurrency} --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
|
||||||
|
83
package.sh
@ -1,3 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
# compile for version
|
# compile for version
|
||||||
make
|
make
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
@ -14,49 +17,57 @@ make -f ./Makefile.cross-compiles
|
|||||||
rm -rf ./release/packages
|
rm -rf ./release/packages
|
||||||
mkdir -p ./release/packages
|
mkdir -p ./release/packages
|
||||||
|
|
||||||
os_all='linux windows darwin freebsd'
|
os_all='linux windows darwin freebsd openbsd android'
|
||||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
|
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64'
|
||||||
|
extra_all='_ hf'
|
||||||
|
|
||||||
cd ./release
|
cd ./release
|
||||||
|
|
||||||
for os in $os_all; do
|
for os in $os_all; do
|
||||||
for arch in $arch_all; do
|
for arch in $arch_all; do
|
||||||
frp_dir_name="frp_${frp_version}_${os}_${arch}"
|
for extra in $extra_all; do
|
||||||
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
|
suffix="${os}_${arch}"
|
||||||
|
if [ "x${extra}" != x"_" ]; then
|
||||||
|
suffix="${os}_${arch}_${extra}"
|
||||||
|
fi
|
||||||
|
frp_dir_name="frp_${frp_version}_${suffix}"
|
||||||
|
frp_path="./packages/frp_${frp_version}_${suffix}"
|
||||||
|
|
||||||
if [ "x${os}" = x"windows" ]; then
|
if [ "x${os}" = x"windows" ]; then
|
||||||
if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
|
if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
if [ ! -f "./frps_${os}_${arch}.exe" ]; then
|
if [ ! -f "./frps_${os}_${arch}.exe" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
mkdir ${frp_path}
|
mkdir ${frp_path}
|
||||||
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
|
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
|
||||||
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
|
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
|
||||||
else
|
else
|
||||||
if [ ! -f "./frpc_${os}_${arch}" ]; then
|
if [ ! -f "./frpc_${suffix}" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
if [ ! -f "./frps_${os}_${arch}" ]; then
|
if [ ! -f "./frps_${suffix}" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
mkdir ${frp_path}
|
mkdir ${frp_path}
|
||||||
mv ./frpc_${os}_${arch} ${frp_path}/frpc
|
mv ./frpc_${suffix} ${frp_path}/frpc
|
||||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
mv ./frps_${suffix} ${frp_path}/frps
|
||||||
fi
|
fi
|
||||||
cp ../LICENSE ${frp_path}
|
cp ../LICENSE ${frp_path}
|
||||||
cp -rf ../conf/* ${frp_path}
|
cp -f ../conf/frpc.toml ${frp_path}
|
||||||
|
cp -f ../conf/frps.toml ${frp_path}
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
cd ./packages
|
cd ./packages
|
||||||
if [ "x${os}" = x"windows" ]; then
|
if [ "x${os}" = x"windows" ]; then
|
||||||
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
|
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
|
||||||
else
|
else
|
||||||
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
|
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
|
||||||
fi
|
fi
|
||||||
cd ..
|
cd ..
|
||||||
rm -rf ${frp_path}
|
rm -rf ${frp_path}
|
||||||
|
done
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -17,76 +17,25 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseConfig struct {
|
|
||||||
// AuthenticationMethod specifies what authentication method to use to
|
|
||||||
// authenticate frpc with frps. If "token" is specified - token will be
|
|
||||||
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
|
||||||
// token will be issued using OIDC settings. By default, this value is "token".
|
|
||||||
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
|
||||||
// AuthenticateHeartBeats specifies whether to include authentication token in
|
|
||||||
// heartbeats sent to frps. By default, this value is false.
|
|
||||||
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
|
||||||
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
|
||||||
// new work connections sent to frps. By default, this value is false.
|
|
||||||
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultBaseConf() BaseConfig {
|
|
||||||
return BaseConfig{
|
|
||||||
AuthenticationMethod: "token",
|
|
||||||
AuthenticateHeartBeats: false,
|
|
||||||
AuthenticateNewWorkConns: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientConfig struct {
|
|
||||||
BaseConfig `ini:",extends"`
|
|
||||||
OidcClientConfig `ini:",extends"`
|
|
||||||
TokenConfig `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDefaultClientConf() ClientConfig {
|
|
||||||
return ClientConfig{
|
|
||||||
BaseConfig: getDefaultBaseConf(),
|
|
||||||
OidcClientConfig: getDefaultOidcClientConf(),
|
|
||||||
TokenConfig: getDefaultTokenConf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig struct {
|
|
||||||
BaseConfig `ini:",extends"`
|
|
||||||
OidcServerConfig `ini:",extends"`
|
|
||||||
TokenConfig `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDefaultServerConf() ServerConfig {
|
|
||||||
return ServerConfig{
|
|
||||||
BaseConfig: getDefaultBaseConf(),
|
|
||||||
OidcServerConfig: getDefaultOidcServerConf(),
|
|
||||||
TokenConfig: getDefaultTokenConf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Setter interface {
|
type Setter interface {
|
||||||
SetLogin(*msg.Login) error
|
SetLogin(*msg.Login) error
|
||||||
SetPing(*msg.Ping) error
|
SetPing(*msg.Ping) error
|
||||||
SetNewWorkConn(*msg.NewWorkConn) error
|
SetNewWorkConn(*msg.NewWorkConn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
|
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) {
|
||||||
switch cfg.AuthenticationMethod {
|
switch cfg.Method {
|
||||||
case consts.TokenAuthMethod:
|
case v1.AuthMethodToken:
|
||||||
authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
||||||
case consts.OidcAuthMethod:
|
case v1.AuthMethodOIDC:
|
||||||
authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig)
|
authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
|
panic(fmt.Sprintf("wrong method: '%s'", cfg.Method))
|
||||||
}
|
}
|
||||||
|
|
||||||
return authProvider
|
return authProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,13 +45,13 @@ type Verifier interface {
|
|||||||
VerifyNewWorkConn(*msg.NewWorkConn) error
|
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
|
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
||||||
switch cfg.AuthenticationMethod {
|
switch cfg.Method {
|
||||||
case consts.TokenAuthMethod:
|
case v1.AuthMethodToken:
|
||||||
authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
||||||
case consts.OidcAuthMethod:
|
case v1.AuthMethodOIDC:
|
||||||
authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig)
|
tokenVerifier := NewTokenVerifier(cfg.OIDC)
|
||||||
|
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return authVerifier
|
return authVerifier
|
||||||
}
|
}
|
||||||
|
145
pkg/auth/legacy/legacy.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// 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 legacy
|
||||||
|
|
||||||
|
type BaseConfig struct {
|
||||||
|
// AuthenticationMethod specifies what authentication method to use to
|
||||||
|
// authenticate frpc with frps. If "token" is specified - token will be
|
||||||
|
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||||
|
// token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
||||||
|
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||||
|
// heartbeats sent to frps. By default, this value is false.
|
||||||
|
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
||||||
|
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||||
|
// new work connections sent to frps. By default, this value is false.
|
||||||
|
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultBaseConf() BaseConfig {
|
||||||
|
return BaseConfig{
|
||||||
|
AuthenticationMethod: "token",
|
||||||
|
AuthenticateHeartBeats: false,
|
||||||
|
AuthenticateNewWorkConns: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
BaseConfig `ini:",extends"`
|
||||||
|
OidcClientConfig `ini:",extends"`
|
||||||
|
TokenConfig `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultClientConf() ClientConfig {
|
||||||
|
return ClientConfig{
|
||||||
|
BaseConfig: getDefaultBaseConf(),
|
||||||
|
OidcClientConfig: getDefaultOidcClientConf(),
|
||||||
|
TokenConfig: getDefaultTokenConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
BaseConfig `ini:",extends"`
|
||||||
|
OidcServerConfig `ini:",extends"`
|
||||||
|
TokenConfig `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultServerConf() ServerConfig {
|
||||||
|
return ServerConfig{
|
||||||
|
BaseConfig: getDefaultBaseConf(),
|
||||||
|
OidcServerConfig: getDefaultOidcServerConf(),
|
||||||
|
TokenConfig: getDefaultTokenConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcClientConfig struct {
|
||||||
|
// OidcClientID specifies the client ID to use to get a token in OIDC
|
||||||
|
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||||
|
// is "".
|
||||||
|
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
|
||||||
|
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||||
|
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||||
|
// 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 "".
|
||||||
|
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: "",
|
||||||
|
OidcScope: "",
|
||||||
|
OidcTokenEndpointURL: "",
|
||||||
|
OidcAdditionalEndpointParams: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcServerConfig struct {
|
||||||
|
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||||
|
// will be used to load public keys to verify signature and will be compared
|
||||||
|
// with the issuer claim in the OIDC token. It will be used if
|
||||||
|
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
|
||||||
|
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||||
|
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||||
|
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||||
|
// value is "".
|
||||||
|
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||||
|
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||||
|
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||||
|
// value is false.
|
||||||
|
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
|
||||||
|
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||||
|
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||||
|
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||||
|
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultOidcServerConf() OidcServerConfig {
|
||||||
|
return OidcServerConfig{
|
||||||
|
OidcIssuer: "",
|
||||||
|
OidcAudience: "",
|
||||||
|
OidcSkipExpiryCheck: false,
|
||||||
|
OidcSkipIssuerCheck: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenConfig struct {
|
||||||
|
// Token specifies the authorization token used to create keys to be sent
|
||||||
|
// to the server. The server must have a matching token for authorization
|
||||||
|
// to succeed. By default, this value is "".
|
||||||
|
Token string `ini:"token" json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTokenConf() TokenConfig {
|
||||||
|
return TokenConfig{
|
||||||
|
Token: "",
|
||||||
|
}
|
||||||
|
}
|