Compare commits
121 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 |
@ -2,24 +2,15 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.21-node
|
- image: cimg/go:1.23-node
|
||||||
resource_class: large
|
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.20-node
|
|
||||||
resource_class: large
|
|
||||||
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
|
|
||||||
|
2
.github/FUNDING.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [fatedier]
|
github: [fatedier]
|
||||||
custom: ["https://afdian.net/a/fatedier"]
|
custom: ["https://afdian.com/a/fatedier"]
|
||||||
|
16
.github/workflows/build-and-push-image.yml
vendored
@ -2,7 +2,7 @@ name: Build Image and Publish to Dockerhub & GPR
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ created ]
|
types: [ published ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@ -19,15 +19,15 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# environment
|
# environment
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
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@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
# get image tag name
|
# get image tag name
|
||||||
- name: Get Image Tag Name
|
- name: Get Image Tag Name
|
||||||
@ -38,13 +38,13 @@ jobs:
|
|||||||
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to the GPR
|
- name: Login to the GPR
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@ -61,7 +61,7 @@ jobs:
|
|||||||
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
|
||||||
|
|
||||||
- name: Build and push frpc
|
- name: Build and push frpc
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./dockerfiles/Dockerfile-for-frpc
|
file: ./dockerfiles/Dockerfile-for-frpc
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
${{ env.TAG_FRPC_GPR }}
|
${{ env.TAG_FRPC_GPR }}
|
||||||
|
|
||||||
- name: Build and push frps
|
- name: Build and push frps
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./dockerfiles/Dockerfile-for-frps
|
file: ./dockerfiles/Dockerfile-for-frps
|
||||||
|
39
.github/workflows/golangci-lint.yml
vendored
@ -14,28 +14,23 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
- uses: actions/setup-go@v5
|
||||||
go-version: '1.21'
|
with:
|
||||||
- uses: actions/checkout@v3
|
go-version: '1.23'
|
||||||
- name: golangci-lint
|
cache: false
|
||||||
uses: golangci/golangci-lint-action@v3
|
- name: golangci-lint
|
||||||
with:
|
uses: golangci/golangci-lint-action@v8
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
with:
|
||||||
version: v1.55
|
# 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.
|
# Optional: golangci-lint command line arguments.
|
||||||
# args: --issues-exit-code=0
|
# args: --issues-exit-code=0
|
||||||
|
|
||||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
# only-new-issues: true
|
# only-new-issues: true
|
||||||
|
|
||||||
# Optional: if set to true then the all caching functionality will be complete disabled,
|
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||||
# takes precedence over all other caching options.
|
# takes precedence over all other caching options.
|
||||||
# skip-cache: true
|
# skip-cache: true
|
||||||
|
|
||||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
|
||||||
# skip-pkg-cache: true
|
|
||||||
|
|
||||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
|
||||||
# skip-build-cache: true
|
|
||||||
|
8
.github/workflows/goreleaser.yml
vendored
@ -8,21 +8,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.23'
|
||||||
|
|
||||||
- name: Make All
|
- name: Make All
|
||||||
run: |
|
run: |
|
||||||
./package.sh
|
./package.sh
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean --release-notes=./Release.md
|
args: release --clean --release-notes=./Release.md
|
||||||
|
15
.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 * * *"
|
||||||
@ -16,19 +16,20 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
issues: write # for actions/stale to close stale issues
|
issues: write # for actions/stale to close stale issues
|
||||||
pull-requests: write # for actions/stale to close stale PRs
|
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@v8
|
- 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
|
||||||
|
2
.gitignore
vendored
@ -34,6 +34,8 @@ dist/
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
.autogen_ssh_key
|
.autogen_ssh_key
|
||||||
|
client.crt
|
||||||
|
client.key
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
*.swp
|
*.swp
|
||||||
|
226
.golangci.yml
@ -1,149 +1,111 @@
|
|||||||
service:
|
version: "2"
|
||||||
golangci-lint-version: 1.55.x # use the fixed version to not introduce new linters unexpectedly
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
|
||||||
deadline: 20m
|
|
||||||
build-tags:
|
build-tags:
|
||||||
- integ
|
- integ
|
||||||
- integfuzz
|
- integfuzz
|
||||||
# which dirs to skip: they won't be analyzed;
|
|
||||||
# can use regexp here: generated.*, regexp is applied on full path;
|
|
||||||
# default value is empty list, but next dirs are always skipped independently
|
|
||||||
# from this option's value:
|
|
||||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
|
||||||
skip-dirs:
|
|
||||||
- genfiles$
|
|
||||||
- vendor$
|
|
||||||
- bin$
|
|
||||||
|
|
||||||
# which files to skip: they will be analyzed, but issues from them
|
|
||||||
# won't be reported. Default value is empty list, but there is
|
|
||||||
# no need to include all autogenerated files, we confidently recognize
|
|
||||||
# autogenerated files. If it's not please let us know.
|
|
||||||
skip-files:
|
|
||||||
- ".*\\.pb\\.go"
|
|
||||||
- ".*\\.gen\\.go"
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- unused
|
- asciicheck
|
||||||
|
- copyloopvar
|
||||||
- errcheck
|
- errcheck
|
||||||
- exportloopref
|
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
- gosec
|
||||||
- goimports
|
|
||||||
- revive
|
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- lll
|
- lll
|
||||||
|
- makezero
|
||||||
- misspell
|
- misspell
|
||||||
- staticcheck
|
|
||||||
- stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- gci
|
|
||||||
- gosec
|
|
||||||
- asciicheck
|
|
||||||
- prealloc
|
- prealloc
|
||||||
- predeclared
|
- predeclared
|
||||||
- makezero
|
- revive
|
||||||
fast: false
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
linters-settings:
|
- unparam
|
||||||
errcheck:
|
- unused
|
||||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
settings:
|
||||||
# default is false: such cases aren't reported by default.
|
errcheck:
|
||||||
check-type-assertions: false
|
check-type-assertions: false
|
||||||
|
check-blank: false
|
||||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
gocritic:
|
||||||
# default is false: such cases aren't reported by default.
|
disabled-checks:
|
||||||
check-blank: false
|
- exitAfterDefer
|
||||||
govet:
|
gosec:
|
||||||
# report about shadowed variables
|
excludes:
|
||||||
check-shadowing: false
|
- G401
|
||||||
maligned:
|
- G402
|
||||||
# print struct with more effective memory layout or not, false by default
|
- G404
|
||||||
suggest-new: true
|
- G501
|
||||||
misspell:
|
- G115
|
||||||
# Correct spellings using locale preferences for US or UK.
|
severity: low
|
||||||
# Default is to use a neutral variety of English.
|
confidence: low
|
||||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
govet:
|
||||||
locale: US
|
disable:
|
||||||
ignore-words:
|
- shadow
|
||||||
- cancelled
|
lll:
|
||||||
- marshalled
|
line-length: 160
|
||||||
lll:
|
tab-width: 1
|
||||||
# max line length, lines longer will be reported. Default is 120.
|
misspell:
|
||||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
locale: US
|
||||||
line-length: 160
|
ignore-rules:
|
||||||
# tab width in spaces. Default to 1.
|
- cancelled
|
||||||
tab-width: 1
|
- marshalled
|
||||||
gocritic:
|
unparam:
|
||||||
disabled-checks:
|
check-exported: false
|
||||||
- exitAfterDefer
|
exclusions:
|
||||||
unused:
|
generated: lax
|
||||||
check-exported: false
|
presets:
|
||||||
unparam:
|
- comments
|
||||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
- common-false-positives
|
||||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
- legacy
|
||||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
- std-error-handling
|
||||||
# with golangci-lint call it on a directory with the changed file.
|
rules:
|
||||||
check-exported: false
|
- linters:
|
||||||
gci:
|
- errcheck
|
||||||
sections:
|
- maligned
|
||||||
- standard
|
path: _test\.go$|^tests/|^samples/
|
||||||
- default
|
- linters:
|
||||||
- prefix(github.com/fatedier/frp/)
|
- revive
|
||||||
gosec:
|
- staticcheck
|
||||||
severity: "low"
|
text: use underscores in Go names
|
||||||
confidence: "low"
|
- linters:
|
||||||
excludes:
|
- revive
|
||||||
- G102
|
text: unused-parameter
|
||||||
- G112
|
- linters:
|
||||||
- G306
|
- unparam
|
||||||
- G401
|
text: is always false
|
||||||
- G402
|
paths:
|
||||||
- G404
|
- .*\.pb\.go
|
||||||
- G501
|
- .*\.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:
|
issues:
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
max-issues-per-linter: 0
|
||||||
# But independently from this option we use default exclude patterns,
|
|
||||||
# it can be disabled by `exclude-use-default: false`. To list all
|
|
||||||
# excluded by default patterns execute `golangci-lint run --help`
|
|
||||||
# exclude:
|
|
||||||
# - composite literal uses unkeyed fields
|
|
||||||
|
|
||||||
exclude-rules:
|
|
||||||
# Exclude some linters from running on test files.
|
|
||||||
- path: _test\.go$|^tests/|^samples/
|
|
||||||
linters:
|
|
||||||
- errcheck
|
|
||||||
- maligned
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
- stylecheck
|
|
||||||
text: "use underscores in Go names"
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
text: "unused-parameter"
|
|
||||||
- linters:
|
|
||||||
- unparam
|
|
||||||
text: "is always false"
|
|
||||||
|
|
||||||
# Independently from option `exclude` we use default exclude patterns,
|
|
||||||
# it can be disabled by this option. To list all
|
|
||||||
# excluded by default patterns execute `golangci-lint run --help`.
|
|
||||||
# Default value for this option is true.
|
|
||||||
exclude-use-default: true
|
|
||||||
|
|
||||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
|
||||||
max-per-linter: 0
|
|
||||||
|
|
||||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
7
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/*
|
||||||
|
@ -1,23 +1,35 @@
|
|||||||
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:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
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_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
|
||||||
|
103
README.md
@ -2,18 +2,30 @@
|
|||||||
|
|
||||||
[](https://circleci.com/gh/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)
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
frp is an open source project with its ongoing development made possible entirely by the support of our awesome sponsors. If you'd like to join them, please consider [sponsoring frp's development](https://github.com/sponsors/fatedier).
|
||||||
|
|
||||||
<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="350px" 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>
|
</a>
|
||||||
<a> </a>
|
</p>
|
||||||
<a href="https://www.nango.dev?utm_source=github&utm_medium=oss-banner&utm_campaign=fatedier-frp" target="_blank">
|
<p align="center">
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_nango.png">
|
<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-->
|
||||||
@ -76,9 +88,16 @@ frp also offers a P2P connect mode.
|
|||||||
* [URL Routing](#url-routing)
|
* [URL Routing](#url-routing)
|
||||||
* [TCP Port Multiplexing](#tcp-port-multiplexing)
|
* [TCP Port Multiplexing](#tcp-port-multiplexing)
|
||||||
* [Connecting to frps via PROXY](#connecting-to-frps-via-proxy)
|
* [Connecting to frps via PROXY](#connecting-to-frps-via-proxy)
|
||||||
|
* [Port range mapping](#port-range-mapping)
|
||||||
* [Client Plugins](#client-plugins)
|
* [Client Plugins](#client-plugins)
|
||||||
* [Server Manage Plugins](#server-manage-plugins)
|
* [Server Manage Plugins](#server-manage-plugins)
|
||||||
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
||||||
|
* [Virtual Network (VirtualNet)](#virtual-network-virtualnet)
|
||||||
|
* [Feature Gates](#feature-gates)
|
||||||
|
* [Available Feature Gates](#available-feature-gates)
|
||||||
|
* [Enabling Feature Gates](#enabling-feature-gates)
|
||||||
|
* [Feature Lifecycle](#feature-lifecycle)
|
||||||
|
* [Related Projects](#related-projects)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
* [GitHub Sponsors](#github-sponsors)
|
* [GitHub Sponsors](#github-sponsors)
|
||||||
@ -201,11 +220,11 @@ This example implements multiple SSH services exposed through the same port usin
|
|||||||
|
|
||||||
4. To access internal machine A using SSH ProxyCommand, assuming the username is "test":
|
4. To access internal machine A using SSH ProxyCommand, assuming the username is "test":
|
||||||
|
|
||||||
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:machine-a.example.com:22,proxyport=5002' test@machine-a`
|
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' test@machine-a.example.com`
|
||||||
|
|
||||||
5. To access internal machine B, the only difference is the domain name, assuming the username is "test":
|
5. To access internal machine B, the only difference is the domain name, assuming the username is "test":
|
||||||
|
|
||||||
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:machine-b.example.com:22,proxyport=5002' test@machine-b`
|
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' test@machine-b.example.com`
|
||||||
|
|
||||||
### Accessing Internal Web Services with Custom Domains in LAN
|
### Accessing Internal Web Services with Custom Domains in LAN
|
||||||
|
|
||||||
@ -348,7 +367,6 @@ You may substitute `https2https` for the plugin, and point the `localAddr` to a
|
|||||||
# frpc.toml
|
# frpc.toml
|
||||||
serverAddr = "x.x.x.x"
|
serverAddr = "x.x.x.x"
|
||||||
serverPort = 7000
|
serverPort = 7000
|
||||||
vhostHTTPSPort = 443
|
|
||||||
|
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "test_https2http"
|
name = "test_https2http"
|
||||||
@ -526,6 +544,8 @@ Check frp's status and proxies' statistics information by Dashboard.
|
|||||||
Configure a port for dashboard to enable this feature:
|
Configure a port for dashboard to enable this feature:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
# The default value is 127.0.0.1. Change it to 0.0.0.0 when you want to access it from a public network.
|
||||||
|
webServer.addr = "0.0.0.0"
|
||||||
webServer.port = 7500
|
webServer.port = 7500
|
||||||
# dashboard's username and password are both optional
|
# dashboard's username and password are both optional
|
||||||
webServer.user = "admin"
|
webServer.user = "admin"
|
||||||
@ -799,7 +819,7 @@ You can disable this feature by modify `frps.toml` and `frpc.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# frps.toml and frpc.toml, must be same
|
# frps.toml and frpc.toml, must be same
|
||||||
tcpMux = false
|
transport.tcpMux = false
|
||||||
```
|
```
|
||||||
|
|
||||||
### Support KCP Protocol
|
### Support KCP Protocol
|
||||||
@ -978,7 +998,7 @@ The HTTP request will have the `Host` header rewritten to `Host: dev.example.com
|
|||||||
|
|
||||||
### Setting other HTTP Headers
|
### Setting other HTTP Headers
|
||||||
|
|
||||||
Similar to `Host`, You can override other HTTP request headers with proxy type `http`.
|
Similar to `Host`, You can override other HTTP request and response headers with proxy type `http`.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# frpc.toml
|
# frpc.toml
|
||||||
@ -990,21 +1010,22 @@ localPort = 80
|
|||||||
customDomains = ["test.example.com"]
|
customDomains = ["test.example.com"]
|
||||||
hostHeaderRewrite = "dev.example.com"
|
hostHeaderRewrite = "dev.example.com"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
responseHeaders.set.foo = "bar"
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, it will set header `x-from-where: frp` in the HTTP request.
|
In this example, it will set header `x-from-where: frp` in the HTTP request and `foo: bar` in the HTTP response.
|
||||||
|
|
||||||
### Get Real IP
|
### Get Real IP
|
||||||
|
|
||||||
#### HTTP X-Forwarded-For
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
This feature is for http proxy only.
|
This feature is for `http` proxies or proxies with the `https2http` and `https2https` plugins enabled.
|
||||||
|
|
||||||
You can get user's real IP from HTTP request headers `X-Forwarded-For`.
|
You can get user's real IP from HTTP request headers `X-Forwarded-For`.
|
||||||
|
|
||||||
#### Proxy Protocol
|
#### Proxy Protocol
|
||||||
|
|
||||||
frp supports Proxy Protocol to send user's real IP to local services. It support all types except UDP.
|
frp supports Proxy Protocol to send user's real IP to local services.
|
||||||
|
|
||||||
Here is an example for https service:
|
Here is an example for https service:
|
||||||
|
|
||||||
@ -1154,6 +1175,24 @@ serverPort = 7000
|
|||||||
transport.proxyURL = "http://user:pwd@192.168.1.128:8080"
|
transport.proxyURL = "http://user:pwd@192.168.1.128:8080"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Port range mapping
|
||||||
|
|
||||||
|
*Added in v0.56.0*
|
||||||
|
|
||||||
|
We can use the range syntax of Go template combined with the built-in `parseNumberRangePair` function to achieve port range mapping.
|
||||||
|
|
||||||
|
The following example, when run, will create 8 proxies named `test-6000, test-6001 ... test-6007`, each mapping the remote port to the local port.
|
||||||
|
|
||||||
|
```
|
||||||
|
{{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }}
|
||||||
|
[[proxies]]
|
||||||
|
name = "tcp-{{ $v.First }}"
|
||||||
|
type = "tcp"
|
||||||
|
localPort = {{ $v.First }}
|
||||||
|
remotePort = {{ $v.Second }}
|
||||||
|
{{- end }}
|
||||||
|
```
|
||||||
|
|
||||||
### Client Plugins
|
### Client Plugins
|
||||||
|
|
||||||
frpc only forwards requests to local TCP or UDP ports by default.
|
frpc only forwards requests to local TCP or UDP ports by default.
|
||||||
@ -1221,6 +1260,44 @@ frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote
|
|||||||
|
|
||||||
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
For detailed information about configuration and usage, please refer to the [VirtualNet documentation](/doc/virtual_net.md).
|
||||||
|
|
||||||
|
## Feature Gates
|
||||||
|
|
||||||
|
frp supports feature gates to enable or disable experimental features. This allows users to try out new features before they're considered stable.
|
||||||
|
|
||||||
|
### Available Feature Gates
|
||||||
|
|
||||||
|
| Name | Stage | Default | Description |
|
||||||
|
|------|-------|---------|-------------|
|
||||||
|
| VirtualNet | ALPHA | false | Virtual network capabilities for frp |
|
||||||
|
|
||||||
|
### Enabling Feature Gates
|
||||||
|
|
||||||
|
To enable an experimental feature, add the feature gate to your configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
featureGates = { VirtualNet = true }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature Lifecycle
|
||||||
|
|
||||||
|
Features typically go through three stages:
|
||||||
|
1. **ALPHA**: Disabled by default, may be unstable
|
||||||
|
2. **BETA**: May be enabled by default, more stable but still evolving
|
||||||
|
3. **GA (Generally Available)**: Enabled by default, ready for production use
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
|
||||||
|
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - A lightweight version of the frp client (around 3.5MB at minimum) implemented using the ssh protocol, supporting some of the most commonly used features, suitable for devices with limited resources.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Interested in getting involved? We would like to help you!
|
Interested in getting involved? We would like to help you!
|
||||||
|
35
README_zh.md
@ -2,20 +2,32 @@
|
|||||||
|
|
||||||
[](https://circleci.com/gh/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 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者们的支持。如果你愿意加入他们的行列,请考虑 [赞助 frp 的开发](https://github.com/sponsors/fatedier)。
|
||||||
|
|
||||||
<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="350px" 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>
|
</a>
|
||||||
<a> </a>
|
</p>
|
||||||
<a href="https://www.nango.dev?utm_source=github&utm_medium=oss-banner&utm_campaign=fatedier-frp" target="_blank">
|
<p align="center">
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_nango.png">
|
<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-->
|
||||||
@ -70,7 +82,12 @@ 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 左右),支持常用的部分功能,适用于资源有限的设备。
|
||||||
|
|
||||||
## 赞助
|
## 赞助
|
||||||
|
|
||||||
@ -82,7 +99,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
|
|
||||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||||
|
|
||||||
国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
|
国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。
|
||||||
|
|
||||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||||
|
|
||||||
@ -91,7 +108,3 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何 frp 使用方面的帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何 frp 使用方面的帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||||
|
|
||||||

|

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

|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
### Fixes
|
## Features
|
||||||
|
|
||||||
* frpc has a certain chance to panic when login: close of closed channel.
|
* 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.
|
42
assets/frpc/static/index-bLBhaJo8.js
Normal file
1
assets/frpc/static/index-iuf46MlF.css
Normal file
@ -4,13 +4,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frp client admin UI</title>
|
<title>frp client admin UI</title>
|
||||||
<script type="module" crossorigin src="./index-1c7ed8b0.js"></script>
|
<script type="module" crossorigin src="./index-bLBhaJo8.js"></script>
|
||||||
<link rel="stylesheet" href="./index-1e2a7ce0.css">
|
<link rel="stylesheet" crossorigin href="./index-iuf46MlF.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
84
assets/frps/static/index-82-40HIG.js
Normal file
1
assets/frps/static/index-rzPDshRD.css
Normal file
@ -4,13 +4,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frps dashboard</title>
|
<title>frps dashboard</title>
|
||||||
<script type="module" crossorigin src="./index-c322b7dd.js"></script>
|
<script type="module" crossorigin src="./index-82-40HIG.js"></script>
|
||||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
<link rel="stylesheet" crossorigin href="./index-rzPDshRD.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -15,19 +15,17 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
"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"
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
@ -78,9 +76,9 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
strictConfigMode, _ = strconv.ParseBool(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))
|
||||||
@ -91,32 +89,32 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
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
|
return
|
||||||
}
|
}
|
||||||
if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs); err != nil {
|
if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs); 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := svr.UpdateAllConfigurer(proxyCfgs, 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/stop
|
// POST /api/stop
|
||||||
func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
|
func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Info("api request [/api/stop]")
|
log.Infof("api request [/api/stop]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("api response [/api/stop], code [%d]", res.Code)
|
log.Infof("api response [/api/stop], 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))
|
||||||
@ -153,7 +151,7 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
|
|
||||||
if status.Err == "" {
|
if status.Err == "" {
|
||||||
psr.RemoteAddr = status.RemoteAddr
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
if lo.Contains([]string{"tcp", "udp"}, status.Type) {
|
if slices.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,9 +165,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
|||||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
||||||
}()
|
}()
|
||||||
@ -190,8 +188,8 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
|||||||
if len(arrs) <= 1 {
|
if len(arrs) <= 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sort.Slice(arrs, func(i, j int) bool {
|
slices.SortFunc(arrs, func(a, b ProxyStatusResp) int {
|
||||||
return strings.Compare(arrs[i].Name, arrs[j].Name) < 0
|
return cmp.Compare(a.Name, b.Name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,9 +198,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
|||||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Info("Http get request [/api/config]")
|
log.Infof("http get request [/api/config]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
log.Infof("http get 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))
|
||||||
@ -212,7 +210,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
|||||||
if svr.configFilePath == "" {
|
if svr.configFilePath == "" {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = "frpc has no config file path"
|
res.Msg = "frpc has no config file path"
|
||||||
log.Warn("%s", res.Msg)
|
log.Warnf("%s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +218,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
|||||||
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)
|
res.Msg = string(content)
|
||||||
@ -230,9 +228,9 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
|||||||
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))
|
||||||
@ -244,21 +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
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(svr.configFilePath, body, 0o644); err != nil {
|
if err := os.WriteFile(svr.configFilePath, body, 0o600); 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
libnet "github.com/fatedier/golib/net"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
quic "github.com/quic-go/quic-go"
|
quic "github.com/quic-go/quic-go"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connector is a interface for establishing connections to the server.
|
// Connector is an interface for establishing connections to the server.
|
||||||
type Connector interface {
|
type Connector interface {
|
||||||
Open() error
|
Open() error
|
||||||
Connect() (net.Conn, error)
|
Connect() (net.Conn, error)
|
||||||
@ -59,7 +59,7 @@ func NewConnector(ctx context.Context, cfg *v1.ClientCommonConfig) Connector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a underlying connection to the server.
|
// Open opens an underlying connection to the server.
|
||||||
// The underlying connection is either a TCP connection or a QUIC connection.
|
// 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.
|
// 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().
|
// If TCPMux isn't enabled, the underlying connection is nil, you will get a new real TCP connection every time you call Connect().
|
||||||
@ -84,7 +84,7 @@ func (c *defaultConnectorImpl) Open() error {
|
|||||||
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
xl.Warnf("fail to build tls configuration, err: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tlsConfig.NextProtos = []string{"frp"}
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
@ -164,49 +164,49 @@ func (c *defaultConnectorImpl) realConnect() (net.Conn, error) {
|
|||||||
c.cfg.Transport.TLS.TrustedCaFile,
|
c.cfg.Transport.TLS.TrustedCaFile,
|
||||||
sn)
|
sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
xl.Warnf("fail to build tls configuration, err: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyType, addr, auth, err := libdial.ParseProxyURL(c.cfg.Transport.ProxyURL)
|
proxyType, addr, auth, err := libnet.ParseProxyURL(c.cfg.Transport.ProxyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("fail to parse proxy url")
|
xl.Errorf("fail to parse proxy url")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dialOptions := []libdial.DialOption{}
|
dialOptions := []libnet.DialOption{}
|
||||||
protocol := c.cfg.Transport.Protocol
|
protocol := c.cfg.Transport.Protocol
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "websocket":
|
case "websocket":
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, "")}))
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, "")}))
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{
|
||||||
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||||
}))
|
}))
|
||||||
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
dialOptions = append(dialOptions, libnet.WithTLSConfig(tlsConfig))
|
||||||
case "wss":
|
case "wss":
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
dialOptions = append(dialOptions, libdial.WithTLSConfigAndPriority(100, tlsConfig))
|
dialOptions = append(dialOptions, libnet.WithTLSConfigAndPriority(100, tlsConfig))
|
||||||
// Make sure that if it is wss, the websocket hook is executed after the tls hook.
|
// Make sure that if it is wss, the websocket hook is executed after the tls hook.
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
|
||||||
default:
|
default:
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{
|
||||||
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||||
}))
|
}))
|
||||||
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
dialOptions = append(dialOptions, libnet.WithTLSConfig(tlsConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.cfg.Transport.ConnectServerLocalIP != "" {
|
if c.cfg.Transport.ConnectServerLocalIP != "" {
|
||||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(c.cfg.Transport.ConnectServerLocalIP))
|
dialOptions = append(dialOptions, libnet.WithLocalAddr(c.cfg.Transport.ConnectServerLocalIP))
|
||||||
}
|
}
|
||||||
dialOptions = append(dialOptions,
|
dialOptions = append(dialOptions,
|
||||||
libdial.WithProtocol(protocol),
|
libnet.WithProtocol(protocol),
|
||||||
libdial.WithTimeout(time.Duration(c.cfg.Transport.DialServerTimeout)*time.Second),
|
libnet.WithTimeout(time.Duration(c.cfg.Transport.DialServerTimeout)*time.Second),
|
||||||
libdial.WithKeepAlive(time.Duration(c.cfg.Transport.DialServerKeepAlive)*time.Second),
|
libnet.WithKeepAlive(time.Duration(c.cfg.Transport.DialServerKeepAlive)*time.Second),
|
||||||
libdial.WithProxy(proxyType, addr),
|
libnet.WithProxy(proxyType, addr),
|
||||||
libdial.WithProxyAuth(auth),
|
libnet.WithProxyAuth(auth),
|
||||||
)
|
)
|
||||||
conn, err := libdial.DialContext(
|
conn, err := libnet.DialContext(
|
||||||
c.ctx,
|
c.ctx,
|
||||||
net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
|
net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
|
||||||
dialOptions...,
|
dialOptions...,
|
||||||
|
@ -20,8 +20,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/client/visitor"
|
"github.com/fatedier/frp/client/visitor"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
@ -31,6 +29,7 @@ import (
|
|||||||
netpkg "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/wait"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionContext struct {
|
type SessionContext struct {
|
||||||
@ -48,6 +47,8 @@ type SessionContext struct {
|
|||||||
AuthSetter auth.Setter
|
AuthSetter auth.Setter
|
||||||
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
||||||
Connector Connector
|
Connector Connector
|
||||||
|
// Virtual net controller
|
||||||
|
VnetController *vnet.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
@ -101,8 +102,9 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
|
|||||||
ctl.registerMsgHandlers()
|
ctl.registerMsgHandlers()
|
||||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
||||||
|
|
||||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter)
|
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)
|
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
|
||||||
|
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
return ctl, nil
|
return ctl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ 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.Warn("start new connection to server error: %v", err)
|
xl.Warnf("start new connection to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,23 +134,24 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
|||||||
RunID: ctl.sessionCtx.RunID,
|
RunID: ctl.sessionCtx.RunID,
|
||||||
}
|
}
|
||||||
if err = ctl.sessionCtx.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.Trace("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
|
||||||
}
|
}
|
||||||
@ -164,9 +167,9 @@ func (ctl *Control) handleNewProxyResp(m msg.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +180,7 @@ func (ctl *Control) handleNatHoleResp(m msg.Message) {
|
|||||||
// Dispatch the NatHoleResp message to the related proxy.
|
// Dispatch the NatHoleResp message to the related proxy.
|
||||||
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||||
if !ok {
|
if !ok {
|
||||||
xl.Trace("dispatch NatHoleResp message to related proxy error")
|
xl.Tracef("dispatch NatHoleResp message to related proxy error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,12 +189,12 @@ func (ctl *Control) handlePong(m msg.Message) {
|
|||||||
inMsg := m.(*msg.Pong)
|
inMsg := m.(*msg.Pong)
|
||||||
|
|
||||||
if inMsg.Error != "" {
|
if inMsg.Error != "" {
|
||||||
xl.Error("Pong message contains error: %s", inMsg.Error)
|
xl.Errorf("pong message contains error: %s", inMsg.Error)
|
||||||
ctl.closeSession()
|
ctl.closeSession()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctl.lastPong.Store(time.Now())
|
ctl.lastPong.Store(time.Now())
|
||||||
xl.Debug("receive heartbeat from server")
|
xl.Debugf("receive heartbeat from server")
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeSession closes the control connection.
|
// closeSession closes the control connection.
|
||||||
@ -231,19 +234,17 @@ func (ctl *Control) registerMsgHandlers() {
|
|||||||
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
||||||
}
|
}
|
||||||
|
|
||||||
// headerWorker sends heartbeat to server and check heartbeat timeout.
|
// heartbeatWorker sends heartbeat to server and check heartbeat timeout.
|
||||||
func (ctl *Control) heartbeatWorker() {
|
func (ctl *Control) heartbeatWorker() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
|
|
||||||
// TODO(fatedier): Change default value of HeartbeatInterval to -1 if tcpmux is enabled.
|
|
||||||
// Users can still enable heartbeat feature by setting HeartbeatInterval to a positive value.
|
|
||||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
||||||
// send heartbeat to server
|
// Send heartbeat to server.
|
||||||
sendHeartBeat := func() (bool, error) {
|
sendHeartBeat := func() (bool, error) {
|
||||||
xl.Debug("send heartbeat to server")
|
xl.Debugf("send heartbeat to server")
|
||||||
pingMsg := &msg.Ping{}
|
pingMsg := &msg.Ping{}
|
||||||
if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil {
|
if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil {
|
||||||
xl.Warn("error during ping authentication: %v, skip sending ping message", err)
|
xl.Warnf("error during ping authentication: %v, skip sending ping message", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
_ = ctl.msgDispatcher.Send(pingMsg)
|
_ = ctl.msgDispatcher.Send(pingMsg)
|
||||||
@ -262,13 +263,11 @@ func (ctl *Control) heartbeatWorker() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
// Check heartbeat timeout.
|
||||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 &&
|
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 {
|
||||||
!lo.FromPtr(ctl.sessionCtx.Common.Transport.TCPMux) {
|
|
||||||
|
|
||||||
go wait.Until(func() {
|
go wait.Until(func() {
|
||||||
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warnf("heartbeat timeout")
|
||||||
ctl.closeSession()
|
ctl.closeSession()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var ErrPayloadType = errors.New("error payload type")
|
var ErrPayloadType = errors.New("error payload type")
|
||||||
|
|
||||||
type Handler func(payload interface{}) error
|
type Handler func(payload any) error
|
||||||
|
|
||||||
type StartProxyPayload struct {
|
type StartProxyPayload struct {
|
||||||
NewProxyMsg *msg.NewProxy
|
NewProxyMsg *msg.NewProxy
|
||||||
|
@ -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()
|
||||||
@ -73,6 +73,11 @@ func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
|
|||||||
}
|
}
|
||||||
url = s + cfg.Path
|
url = s + cfg.Path
|
||||||
}
|
}
|
||||||
|
header := make(http.Header)
|
||||||
|
for _, h := range cfg.HTTPHeaders {
|
||||||
|
header.Set(h.Name, h.Value)
|
||||||
|
}
|
||||||
|
|
||||||
return &Monitor{
|
return &Monitor{
|
||||||
checkType: cfg.Type,
|
checkType: cfg.Type,
|
||||||
interval: time.Duration(cfg.IntervalSeconds) * time.Second,
|
interval: time.Duration(cfg.IntervalSeconds) * time.Second,
|
||||||
@ -80,6 +85,7 @@ func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
|
|||||||
maxFailedTimes: cfg.MaxFailed,
|
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,
|
||||||
@ -112,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()
|
||||||
}
|
}
|
||||||
@ -163,6 +169,8 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
|
|||||||
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
|
||||||
|
@ -20,13 +20,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
libnet "github.com/fatedier/golib/net"
|
||||||
pp "github.com/pires/go-proxyproto"
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config/types"
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
@ -35,7 +33,9 @@ import (
|
|||||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
||||||
@ -58,6 +58,7 @@ func NewProxy(
|
|||||||
pxyConf v1.ProxyConfigurer,
|
pxyConf v1.ProxyConfigurer,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) (pxy Proxy) {
|
) (pxy Proxy) {
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
||||||
@ -70,6 +71,7 @@ func NewProxy(
|
|||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
xl: xlog.FromContextSafe(ctx),
|
xl: xlog.FromContextSafe(ctx),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
@ -85,6 +87,7 @@ type BaseProxy struct {
|
|||||||
baseCfg *v1.ProxyBaseConfig
|
baseCfg *v1.ProxyBaseConfig
|
||||||
clientCfg *v1.ClientCommonConfig
|
clientCfg *v1.ClientCommonConfig
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
|
vnetController *vnet.Controller
|
||||||
limiter *rate.Limiter
|
limiter *rate.Limiter
|
||||||
// proxyPlugin is used to handle connections instead of dialing to local service.
|
// proxyPlugin is used to handle connections instead of dialing to local service.
|
||||||
// It's only validate for TCP protocol now.
|
// It's only validate for TCP protocol now.
|
||||||
@ -98,7 +101,10 @@ type BaseProxy struct {
|
|||||||
|
|
||||||
func (pxy *BaseProxy) Run() error {
|
func (pxy *BaseProxy) Run() error {
|
||||||
if pxy.baseCfg.Plugin.Type != "" {
|
if pxy.baseCfg.Plugin.Type != "" {
|
||||||
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, pxy.baseCfg.Plugin.ClientPluginOptions)
|
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
|
||||||
|
Name: pxy.baseCfg.Name,
|
||||||
|
VnetController: pxy.vnetController,
|
||||||
|
}, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -141,13 +147,13 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("handle tcp work connection, useEncryption: %t, useCompression: %t",
|
xl.Tracef("handle tcp work connection, useEncryption: %t, useCompression: %t",
|
||||||
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
|
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
|
||||||
if baseCfg.Transport.UseEncryption {
|
if baseCfg.Transport.UseEncryption {
|
||||||
remote, err = libio.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,69 +163,58 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to send proxy protocol info
|
// check if we need to send proxy protocol info
|
||||||
var extraInfo plugin.ExtraInfo
|
var connInfo plugin.ConnectionInfo
|
||||||
if baseCfg.Transport.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 baseCfg.Transport.ProxyProtocolVersion == "v1" {
|
|
||||||
h.Version = 1
|
|
||||||
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
|
||||||
h.Version = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
extraInfo.ProxyProtocolHeader = h
|
|
||||||
}
|
}
|
||||||
|
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 baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
|
// Use the common proxy protocol builder function
|
||||||
|
header := netpkg.BuildProxyProtocolHeaderStruct(connInfo.SrcAddr, connInfo.DstAddr, baseCfg.Transport.ProxyProtocolVersion)
|
||||||
|
connInfo.ProxyProtocolHeader = header
|
||||||
|
}
|
||||||
|
connInfo.Conn = remote
|
||||||
|
connInfo.UnderlyingConn = workConn
|
||||||
|
|
||||||
if pxy.proxyPlugin != nil {
|
if pxy.proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connection first
|
// if plugin is set, let plugin handle connection first
|
||||||
xl.Debug("handle by plugin: %s", pxy.proxyPlugin.Name())
|
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||||
pxy.proxyPlugin.Handle(remote, workConn, &extraInfo)
|
pxy.proxyPlugin.Handle(pxy.ctx, &connInfo)
|
||||||
xl.Debug("handle by plugin finished")
|
xl.Debugf("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localConn, err := libdial.Dial(
|
localConn, err := libnet.Dial(
|
||||||
net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.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", baseCfg.LocalIP, baseCfg.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 extraInfo.ProxyProtocolHeader != nil {
|
if connInfo.ProxyProtocolHeader != nil {
|
||||||
if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("write proxy protocol header to local conn error: %v", err)
|
xl.Errorf("write proxy protocol header to local conn error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errs := libio.Join(localConn, remote)
|
_, _, errs := libio.Join(localConn, remote)
|
||||||
xl.Debug("join connections closed")
|
xl.Debugf("join connections closed")
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
xl.Trace("join connections errors: %v", errs)
|
xl.Tracef("join connections errors: %v", errs)
|
||||||
}
|
}
|
||||||
if compressionResourceRecycleFn != nil {
|
if compressionResourceRecycleFn != nil {
|
||||||
compressionResourceRecycleFn()
|
compressionResourceRecycleFn()
|
||||||
|
@ -28,12 +28,14 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
proxies map[string]*Wrapper
|
proxies map[string]*Wrapper
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -47,10 +49,12 @@ func NewManager(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) *Manager {
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
proxies: make(map[string]*Wrapper),
|
proxies: make(map[string]*Wrapper),
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
closed: false,
|
closed: false,
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@ -96,7 +100,7 @@ func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) HandleEvent(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:
|
||||||
@ -152,14 +156,14 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(delPxyNames) > 0 {
|
if len(delPxyNames) > 0 {
|
||||||
xl.Info("proxy removed: %s", delPxyNames)
|
xl.Infof("proxy removed: %s", delPxyNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for _, cfg := range proxyCfgs {
|
for _, cfg := range proxyCfgs {
|
||||||
name := cfg.GetBaseConfig().Name
|
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.msgTransporter)
|
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||||
if pm.inWorkConnCallback != nil {
|
if pm.inWorkConnCallback != nil {
|
||||||
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||||
}
|
}
|
||||||
@ -170,6 +174,6 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(addPxyNames) > 0 {
|
if len(addPxyNames) > 0 {
|
||||||
xl.Info("proxy added: %s", addPxyNames)
|
xl.Infof("proxy added: %s", addPxyNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,6 +74,8 @@ type Wrapper struct {
|
|||||||
handler event.Handler
|
handler event.Handler
|
||||||
|
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
|
// vnet controller
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
health uint32
|
health uint32
|
||||||
lastSendStartMsg time.Time
|
lastSendStartMsg time.Time
|
||||||
@ -91,6 +94,7 @@ func NewWrapper(
|
|||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
eventHandler event.Handler,
|
eventHandler event.Handler,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) *Wrapper {
|
) *Wrapper {
|
||||||
baseInfo := cfg.GetBaseConfig()
|
baseInfo := cfg.GetBaseConfig()
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
||||||
@ -105,6 +109,7 @@ func NewWrapper(
|
|||||||
healthNotifyCh: make(chan struct{}),
|
healthNotifyCh: make(chan struct{}),
|
||||||
handler: eventHandler,
|
handler: eventHandler,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
xl: xl,
|
xl: xl,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
@ -114,10 +119,10 @@ func NewWrapper(
|
|||||||
addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
|
addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
|
||||||
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
|
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
|
||||||
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, pw.msgTransporter)
|
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,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 {
|
||||||
@ -197,7 +202,7 @@ 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
|
||||||
@ -212,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()
|
||||||
@ -236,7 +241,7 @@ func (pw *Wrapper) statusNormalCallback() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
xl.Info("health check success")
|
xl.Infof("health check success")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *Wrapper) statusFailedCallback() {
|
func (pw *Wrapper) statusFailedCallback() {
|
||||||
@ -248,7 +253,7 @@ func (pw *Wrapper) statusFailedCallback() {
|
|||||||
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) {
|
||||||
@ -257,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()
|
||||||
|
@ -81,7 +81,7 @@ func (pxy *SUDPProxy) Close() {
|
|||||||
|
|
||||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||||
xl := pxy.xl
|
xl := pxy.xl
|
||||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
xl.Infof("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
|
||||||
var rwc io.ReadWriteCloser = conn
|
var rwc io.ReadWriteCloser = conn
|
||||||
var err error
|
var err error
|
||||||
@ -94,7 +94,7 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,21 +133,21 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
// first to check sudp proxy is closed or not
|
// first to check sudp proxy is closed or not
|
||||||
select {
|
select {
|
||||||
case <-pxy.closeCh:
|
case <-pxy.closeCh:
|
||||||
xl.Trace("frpc sudp proxy is closed")
|
xl.Tracef("frpc sudp proxy is closed")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
var udpMsg msg.UDPPacket
|
var udpMsg msg.UDPPacket
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
xl.Warnf("read from workConn for sudp error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errRet := errors.PanicToError(func() {
|
if errRet := errors.PanicToError(func() {
|
||||||
readCh <- &udpMsg
|
readCh <- &udpMsg
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
xl.Warnf("reader goroutine for sudp work connection closed: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,21 +157,21 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
defer func() {
|
defer func() {
|
||||||
closeFn()
|
closeFn()
|
||||||
xl.Info("writer goroutine for sudp work connection closed")
|
xl.Infof("writer goroutine for sudp work connection closed")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var errRet error
|
var errRet error
|
||||||
for rawMsg := range sendCh {
|
for rawMsg := range sendCh {
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.UDPPacket:
|
case *msg.UDPPacket:
|
||||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
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())
|
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
xl.Trace("frpc send ping message to frpc visitor")
|
xl.Tracef("frpc send ping message to frpc visitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
xl.Error("sudp work write error: %v", errRet)
|
xl.Errorf("sudp work write error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,11 +191,11 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
if errRet = errors.PanicToError(func() {
|
if errRet = errors.PanicToError(func() {
|
||||||
sendCh <- &msg.Ping{}
|
sendCh <- &msg.Ping{}
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
xl.Warnf("heartbeat goroutine for sudp work connection closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-pxy.closeCh:
|
case <-pxy.closeCh:
|
||||||
xl.Trace("frpc sudp proxy is closed")
|
xl.Tracef("frpc sudp proxy is closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,5 +205,5 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
go workConnReaderFn(workConn, readCh)
|
go workConnReaderFn(workConn, readCh)
|
||||||
go heartbeatFn(sendCh)
|
go heartbeatFn(sendCh)
|
||||||
|
|
||||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func (pxy *UDPProxy) Close() {
|
|||||||
|
|
||||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||||
xl := pxy.xl
|
xl := pxy.xl
|
||||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
xl.Infof("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
// close resources related with old workConn
|
// close resources related with old workConn
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,32 +125,32 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
for {
|
for {
|
||||||
var udpMsg msg.UDPPacket
|
var udpMsg msg.UDPPacket
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
xl.Warnf("read from workConn for udp error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if errRet := errors.PanicToError(func() {
|
if errRet := errors.PanicToError(func() {
|
||||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
xl.Tracef("get udp package from workConn: %s", udpMsg.Content)
|
||||||
readCh <- &udpMsg
|
readCh <- &udpMsg
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
xl.Infof("reader goroutine for udp work connection closed: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
defer func() {
|
defer func() {
|
||||||
xl.Info("writer goroutine for udp work connection closed")
|
xl.Infof("writer goroutine for udp work connection closed")
|
||||||
}()
|
}()
|
||||||
var errRet error
|
var errRet error
|
||||||
for rawMsg := range sendCh {
|
for rawMsg := range sendCh {
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.UDPPacket:
|
case *msg.UDPPacket:
|
||||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
xl.Tracef("send udp package to workConn: %s", m.Content)
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
xl.Trace("send ping message to udp workConn")
|
xl.Tracef("send ping message to udp workConn")
|
||||||
}
|
}
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
xl.Error("udp work write error: %v", errRet)
|
xl.Errorf("udp work write error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
if errRet = errors.PanicToError(func() {
|
if errRet = errors.PanicToError(func() {
|
||||||
sendCh <- &msg.Ping{}
|
sendCh <- &msg.Ping{}
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
xl.Tracef("heartbeat goroutine for udp work connection closed")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,5 +171,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||||
go heartbeatFn(pxy.sendCh)
|
go heartbeatFn(pxy.sendCh)
|
||||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
@ -59,17 +59,17 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
|||||||
var natHoleSidMsg msg.NatHoleSid
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("xtcp read from workConn error: %v", err)
|
xl.Errorf("xtcp read from workConn error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("nathole prepare start")
|
xl.Tracef("nathole prepare start")
|
||||||
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("nathole prepare error: %v", err)
|
xl.Warnf("nathole prepare error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||||
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||||
defer prepareResult.ListenConn.Close()
|
defer prepareResult.ListenConn.Close()
|
||||||
|
|
||||||
@ -83,14 +83,14 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
|||||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("nathole exchange info start")
|
xl.Tracef("nathole exchange info start")
|
||||||
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("nathole exchange info error: %v", err)
|
xl.Warnf("nathole exchange info error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
xl.Infof("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
|||||||
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey))
|
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listenConn.Close()
|
listenConn.Close()
|
||||||
xl.Warn("make hole error: %v", err)
|
xl.Warnf("make hole error: %v", err)
|
||||||
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
Sid: natHoleRespMsg.Sid,
|
Sid: natHoleRespMsg.Sid,
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -106,7 +106,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
listenConn = newListenConn
|
listenConn = newListenConn
|
||||||
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
xl.Infof("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
Sid: natHoleRespMsg.Sid,
|
Sid: natHoleRespMsg.Sid,
|
||||||
@ -128,14 +128,14 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
|
|||||||
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||||
lConn, err := net.DialUDP("udp", laddr, raddr)
|
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("dial udp error: %v", err)
|
xl.Warnf("dial udp error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer lConn.Close()
|
defer lConn.Close()
|
||||||
|
|
||||||
remote, err := netpkg.NewKCPConnFromUDP(lConn, true, raddr.String())
|
remote, err := netpkg.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("create kcp connection from udp connection error: %v", err)
|
xl.Warnf("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
|
|||||||
fmuxCfg.LogOutput = io.Discard
|
fmuxCfg.LogOutput = io.Discard
|
||||||
session, err := fmux.Server(remote, fmuxCfg)
|
session, err := fmux.Server(remote, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create mux session error: %v", err)
|
xl.Errorf("create mux session error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
@ -153,7 +153,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
|
|||||||
for {
|
for {
|
||||||
muxConn, err := session.Accept()
|
muxConn, err := session.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("accept connection error: %v", err)
|
xl.Errorf("accept connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||||
@ -166,7 +166,7 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
|
|||||||
|
|
||||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("create tls config error: %v", err)
|
xl.Warnf("create tls config error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tlsConfig.NextProtos = []string{"frp"}
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
@ -178,19 +178,19 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("dial quic error: %v", err)
|
xl.Warnf("dial quic error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// only accept one connection from raddr
|
// only accept one connection from raddr
|
||||||
c, err := quicListener.Accept(pxy.ctx)
|
c, err := quicListener.Accept(pxy.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("quic accept connection error: %v", err)
|
xl.Errorf("quic accept connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
stream, err := c.AcceptStream(pxy.ctx)
|
stream, err := c.AcceptStream(pxy.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Debug("quic accept stream error: %v", err)
|
xl.Debugf("quic accept stream error: %v", err)
|
||||||
_ = c.CloseWithError(0, "")
|
_ = c.CloseWithError(0, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -36,10 +37,17 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
"github.com/fatedier/frp/pkg/util/wait"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
|
// Disable quic-go's receive buffer warning.
|
||||||
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type cancelErr struct {
|
type cancelErr struct {
|
||||||
@ -103,6 +111,8 @@ type Service struct {
|
|||||||
// web server for admin UI and apis
|
// web server for admin UI and apis
|
||||||
webServer *httppkg.Server
|
webServer *httppkg.Server
|
||||||
|
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
cfgMu sync.RWMutex
|
cfgMu sync.RWMutex
|
||||||
common *v1.ClientCommonConfig
|
common *v1.ClientCommonConfig
|
||||||
proxyCfgs []v1.ProxyConfigurer
|
proxyCfgs []v1.ProxyConfigurer
|
||||||
@ -149,6 +159,9 @@ func NewService(options ServiceOptions) (*Service, error) {
|
|||||||
if webServer != nil {
|
if webServer != nil {
|
||||||
webServer.RouteRegister(s.registerRouteHandlers)
|
webServer.RouteRegister(s.registerRouteHandlers)
|
||||||
}
|
}
|
||||||
|
if options.Common.VirtualNet.Address != "" {
|
||||||
|
s.vnetController = vnet.NewController(options.Common.VirtualNet)
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +175,28 @@ func (svr *Service) Run(ctx context.Context) error {
|
|||||||
netpkg.SetDefaultDNSAddress(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
|
// first login to frps
|
||||||
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
|
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
|
||||||
if svr.ctl == nil {
|
if svr.ctl == nil {
|
||||||
@ -172,14 +207,6 @@ func (svr *Service) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
go svr.keepControllerWorking()
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
if svr.webServer != nil {
|
|
||||||
go func() {
|
|
||||||
log.Info("admin server listen on %s", svr.webServer.Address())
|
|
||||||
if err := svr.webServer.Run(); err != nil {
|
|
||||||
log.Warn("admin server exit with error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
<-svr.ctx.Done()
|
<-svr.ctx.Done()
|
||||||
svr.stop()
|
svr.stop()
|
||||||
return nil
|
return nil
|
||||||
@ -269,14 +296,14 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
|||||||
|
|
||||||
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.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
||||||
|
|
||||||
xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID)
|
xl.Infof("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,10 +311,10 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
xl := xlog.FromContextSafe(svr.ctx)
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
|
|
||||||
loginFunc := func() (bool, error) {
|
loginFunc := func() (bool, error) {
|
||||||
xl.Info("try to connect to server...")
|
xl.Infof("try to connect to server...")
|
||||||
conn, connector, err := svr.login()
|
conn, connector, err := svr.login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("connect to server error: %v", err)
|
xl.Warnf("connect to server error: %v", err)
|
||||||
if firstLoginExit {
|
if firstLoginExit {
|
||||||
svr.cancel(cancelErr{Err: err})
|
svr.cancel(cancelErr{Err: err})
|
||||||
}
|
}
|
||||||
@ -298,22 +325,22 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
proxyCfgs := svr.proxyCfgs
|
proxyCfgs := svr.proxyCfgs
|
||||||
visitorCfgs := svr.visitorCfgs
|
visitorCfgs := svr.visitorCfgs
|
||||||
svr.cfgMu.RUnlock()
|
svr.cfgMu.RUnlock()
|
||||||
connEncrypted := true
|
|
||||||
if svr.clientSpec != nil && svr.clientSpec.Type == "ssh-tunnel" {
|
connEncrypted := svr.clientSpec == nil || svr.clientSpec.Type != "ssh-tunnel"
|
||||||
connEncrypted = false
|
|
||||||
}
|
|
||||||
sessionCtx := &SessionContext{
|
sessionCtx := &SessionContext{
|
||||||
Common: svr.common,
|
Common: svr.common,
|
||||||
RunID: svr.runID,
|
RunID: svr.runID,
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
ConnEncrypted: connEncrypted,
|
ConnEncrypted: connEncrypted,
|
||||||
AuthSetter: svr.authSetter,
|
AuthSetter: svr.authSetter,
|
||||||
Connector: connector,
|
Connector: connector,
|
||||||
|
VnetController: svr.vnetController,
|
||||||
}
|
}
|
||||||
ctl, err := NewControl(svr.ctx, sessionCtx)
|
ctl, err := NewControl(svr.ctx, sessionCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
xl.Error("NewControl error: %v", err)
|
xl.Errorf("new control error: %v", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
||||||
@ -332,7 +359,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
// try to reconnect to server until success
|
// try to reconnect to server until success
|
||||||
wait.BackoffUntil(loginFunc, wait.NewFastBackoffManager(
|
wait.BackoffUntil(loginFunc, wait.NewFastBackoffManager(
|
||||||
wait.FastBackoffOptions{
|
wait.FastBackoffOptions{
|
||||||
Duration: time.Millisecond,
|
Duration: time.Second,
|
||||||
Factor: 2,
|
Factor: 2,
|
||||||
Jitter: 0.1,
|
Jitter: 0.1,
|
||||||
MaxDuration: maxInterval,
|
MaxDuration: maxInterval,
|
||||||
@ -373,18 +400,31 @@ func (svr *Service) stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(fatedier): Use StatusExporter to provide query interfaces instead of directly using methods from the Service.
|
func (svr *Service) getProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||||
func (svr *Service) GetProxyStatus(name string) (*proxy.WorkingStatus, error) {
|
|
||||||
svr.ctlMu.RLock()
|
svr.ctlMu.RLock()
|
||||||
ctl := svr.ctl
|
ctl := svr.ctl
|
||||||
svr.ctlMu.RUnlock()
|
svr.ctlMu.RUnlock()
|
||||||
|
|
||||||
if ctl == nil {
|
if ctl == nil {
|
||||||
return nil, fmt.Errorf("control is not running")
|
return nil, false
|
||||||
}
|
}
|
||||||
ws, ok := ctl.pm.GetProxyStatus(name)
|
return ctl.pm.GetProxyStatus(name)
|
||||||
if !ok {
|
}
|
||||||
return nil, fmt.Errorf("proxy [%s] is not found", name)
|
|
||||||
}
|
func (svr *Service) StatusExporter() StatusExporter {
|
||||||
return ws, nil
|
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)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,10 @@ func (sv *STCPVisitor) Run() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go sv.internalConnWorker()
|
go sv.internalConnWorker()
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +60,7 @@ func (sv *STCPVisitor) worker() {
|
|||||||
for {
|
for {
|
||||||
conn, err := sv.l.Accept()
|
conn, err := sv.l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("stcp local listener closed")
|
xl.Warnf("stcp local listener closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go sv.handleConn(conn)
|
go sv.handleConn(conn)
|
||||||
@ -68,7 +72,7 @@ func (sv *STCPVisitor) internalConnWorker() {
|
|||||||
for {
|
for {
|
||||||
conn, err := sv.internalLn.Accept()
|
conn, err := sv.internalLn.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("stcp internal listener closed")
|
xl.Warnf("stcp internal listener closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go sv.handleConn(conn)
|
go sv.handleConn(conn)
|
||||||
@ -79,7 +83,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
xl.Debug("get a new stcp user connection")
|
xl.Debugf("get a new stcp user connection")
|
||||||
visitorConn, err := sv.helper.ConnectServer()
|
visitorConn, err := sv.helper.ConnectServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -97,7 +101,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
}
|
}
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
xl.Warnf("send newVisitorConnMsg to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +109,13 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
xl.Warnf("get newVisitorConnRespMsg error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
if newVisitorConnRespMsg.Error != "" {
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
xl.Warnf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +124,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
if sv.cfg.Transport.UseEncryption {
|
if sv.cfg.Transport.UseEncryption {
|
||||||
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func (sv *SUDPVisitor) Run() (err error) {
|
|||||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
|
||||||
xl.Info("sudp start to work, listen on %s", addr)
|
xl.Infof("sudp start to work, listen on %s", addr)
|
||||||
|
|
||||||
go sv.dispatcher()
|
go sv.dispatcher()
|
||||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize))
|
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize))
|
||||||
@ -84,17 +84,17 @@ func (sv *SUDPVisitor) dispatcher() {
|
|||||||
select {
|
select {
|
||||||
case firstPacket = <-sv.sendCh:
|
case firstPacket = <-sv.sendCh:
|
||||||
if firstPacket == nil {
|
if firstPacket == nil {
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
xl.Infof("frpc sudp visitor proxy is closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-sv.checkCloseCh:
|
case <-sv.checkCloseCh:
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
xl.Infof("frpc sudp visitor proxy is closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
visitorConn, err = sv.getNewVisitorConn()
|
visitorConn, err = sv.getNewVisitorConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
xl.Warnf("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ func (sv *SUDPVisitor) dispatcher() {
|
|||||||
|
|
||||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
xl.Debug("starting sudp proxy worker")
|
xl.Debugf("starting sudp proxy worker")
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
@ -134,21 +134,21 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
|||||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
xl.Warnf("read from workconn for user udp conn error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Time{})
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
xl.Debug("frpc visitor get ping message from frpc")
|
xl.Debugf("frpc visitor get ping message from frpc")
|
||||||
continue
|
continue
|
||||||
case *msg.UDPPacket:
|
case *msg.UDPPacket:
|
||||||
if errRet := errors.PanicToError(func() {
|
if errRet := errors.PanicToError(func() {
|
||||||
sv.readCh <- m
|
sv.readCh <- m
|
||||||
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
xl.Tracef("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
xl.Info("reader goroutine for udp work connection closed")
|
xl.Infof("reader goroutine for udp work connection closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,25 +165,25 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
|||||||
var errRet error
|
var errRet error
|
||||||
if firstPacket != nil {
|
if firstPacket != nil {
|
||||||
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
||||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
xl.Warnf("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
xl.Tracef("send udp package to workConn: %s", firstPacket.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case udpMsg, ok := <-sv.sendCh:
|
case udpMsg, ok := <-sv.sendCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
xl.Info("sender goroutine for udp work connection closed")
|
xl.Infof("sender goroutine for udp work connection closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
xl.Warnf("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
xl.Tracef("send udp package to workConn: %s", udpMsg.Content)
|
||||||
case <-closeCh:
|
case <-closeCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
|||||||
go workConnSenderFn(workConn)
|
go workConnSenderFn(workConn)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
xl.Info("sudp worker is closed")
|
xl.Infof("sudp worker is closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||||
@ -235,7 +235,7 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
|||||||
if sv.cfg.Transport.UseEncryption {
|
if sv.cfg.Transport.UseEncryption {
|
||||||
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
plugin "github.com/fatedier/frp/pkg/plugin/visitor"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
netpkg "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper wraps some functions for visitor to use.
|
// Helper wraps some functions for visitor to use.
|
||||||
@ -34,6 +36,8 @@ type Helper interface {
|
|||||||
// MsgTransporter returns the message transporter that is used to send and receive messages
|
// MsgTransporter returns the message transporter that is used to send and receive messages
|
||||||
// to the frp server through the controller.
|
// to the frp server through the controller.
|
||||||
MsgTransporter() transport.MessageTransporter
|
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 returns the run id of current controller.
|
||||||
RunID() string
|
RunID() string
|
||||||
}
|
}
|
||||||
@ -50,14 +54,34 @@ func NewVisitor(
|
|||||||
cfg v1.VisitorConfigurer,
|
cfg v1.VisitorConfigurer,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
helper Helper,
|
helper Helper,
|
||||||
) (visitor Visitor) {
|
) (Visitor, error) {
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
||||||
|
ctx = xlog.NewContext(ctx, xl)
|
||||||
|
var visitor Visitor
|
||||||
baseVisitor := BaseVisitor{
|
baseVisitor := BaseVisitor{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
helper: helper,
|
helper: helper,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: ctx,
|
||||||
internalLn: netpkg.NewInternalListener(),
|
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) {
|
switch cfg := cfg.(type) {
|
||||||
case *v1.STCPVisitorConfig:
|
case *v1.STCPVisitorConfig:
|
||||||
visitor = &STCPVisitor{
|
visitor = &STCPVisitor{
|
||||||
@ -77,7 +101,7 @@ func NewVisitor(
|
|||||||
checkCloseCh: make(chan struct{}),
|
checkCloseCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return visitor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseVisitor struct {
|
type BaseVisitor struct {
|
||||||
@ -85,6 +109,7 @@ type BaseVisitor struct {
|
|||||||
helper Helper
|
helper Helper
|
||||||
l net.Listener
|
l net.Listener
|
||||||
internalLn *netpkg.InternalListener
|
internalLn *netpkg.InternalListener
|
||||||
|
plugin plugin.Plugin
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -101,4 +126,7 @@ func (v *BaseVisitor) Close() {
|
|||||||
if v.internalLn != nil {
|
if v.internalLn != nil {
|
||||||
v.internalLn.Close()
|
v.internalLn.Close()
|
||||||
}
|
}
|
||||||
|
if v.plugin != nil {
|
||||||
|
v.plugin.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -50,6 +51,7 @@ func NewManager(
|
|||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
connectServer func() (net.Conn, error),
|
connectServer func() (net.Conn, error),
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) *Manager {
|
) *Manager {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
@ -62,6 +64,7 @@ func NewManager(
|
|||||||
m.helper = &visitorHelperImpl{
|
m.helper = &visitorHelperImpl{
|
||||||
connectServerFn: connectServer,
|
connectServerFn: connectServer,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
transferConnFn: m.TransferConn,
|
transferConnFn: m.TransferConn,
|
||||||
runID: runID,
|
runID: runID,
|
||||||
}
|
}
|
||||||
@ -79,14 +82,14 @@ func (vm *Manager) keepVisitorsRunning() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-vm.stopCh:
|
case <-vm.stopCh:
|
||||||
xl.Trace("gracefully shutdown visitor manager")
|
xl.Tracef("gracefully shutdown visitor manager")
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
for _, cfg := range vm.cfgs {
|
for _, cfg := range vm.cfgs {
|
||||||
name := cfg.GetBaseConfig().Name
|
name := cfg.GetBaseConfig().Name
|
||||||
if _, exist := vm.visitors[name]; !exist {
|
if _, exist := vm.visitors[name]; !exist {
|
||||||
xl.Info("try to start visitor [%s]", name)
|
xl.Infof("try to start visitor [%s]", name)
|
||||||
_ = vm.startVisitor(cfg)
|
_ = vm.startVisitor(cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,13 +115,17 @@ func (vm *Manager) Close() {
|
|||||||
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
name := cfg.GetBaseConfig().Name
|
name := cfg.GetBaseConfig().Name
|
||||||
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
visitor, err := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("new visitor error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
err = visitor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("start error: %v", err)
|
xl.Warnf("start error: %v", err)
|
||||||
} else {
|
} else {
|
||||||
vm.visitors[name] = visitor
|
vm.visitors[name] = visitor
|
||||||
xl.Info("start visitor success")
|
xl.Infof("start visitor success")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -156,7 +163,7 @@ func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(delNames) > 0 {
|
if len(delNames) > 0 {
|
||||||
xl.Info("visitor removed: %v", delNames)
|
xl.Infof("visitor removed: %v", delNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
addNames := make([]string, 0)
|
addNames := make([]string, 0)
|
||||||
@ -169,7 +176,7 @@ func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(addNames) > 0 {
|
if len(addNames) > 0 {
|
||||||
xl.Info("visitor added: %v", addNames)
|
xl.Infof("visitor added: %v", addNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +194,7 @@ func (vm *Manager) TransferConn(name string, conn net.Conn) error {
|
|||||||
type visitorHelperImpl struct {
|
type visitorHelperImpl struct {
|
||||||
connectServerFn func() (net.Conn, error)
|
connectServerFn func() (net.Conn, error)
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
|
vnetController *vnet.Controller
|
||||||
transferConnFn func(name string, conn net.Conn) error
|
transferConnFn func(name string, conn net.Conn) error
|
||||||
runID string
|
runID string
|
||||||
}
|
}
|
||||||
@ -203,6 +211,10 @@ func (v *visitorHelperImpl) MsgTransporter() transport.MessageTransporter {
|
|||||||
return v.msgTransporter
|
return v.msgTransporter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) VNetController() *vnet.Controller {
|
||||||
|
return v.vnetController
|
||||||
|
}
|
||||||
|
|
||||||
func (v *visitorHelperImpl) RunID() string {
|
func (v *visitorHelperImpl) RunID() string {
|
||||||
return v.runID
|
return v.runID
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,10 @@ func (sv *XTCPVisitor) Run() (err error) {
|
|||||||
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
||||||
go sv.keepTunnelOpenWorker()
|
go sv.keepTunnelOpenWorker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +97,7 @@ func (sv *XTCPVisitor) worker() {
|
|||||||
for {
|
for {
|
||||||
conn, err := sv.l.Accept()
|
conn, err := sv.l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("xtcp local listener closed")
|
xl.Warnf("xtcp local listener closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go sv.handleConn(conn)
|
go sv.handleConn(conn)
|
||||||
@ -105,7 +109,7 @@ func (sv *XTCPVisitor) internalConnWorker() {
|
|||||||
for {
|
for {
|
||||||
conn, err := sv.internalLn.Accept()
|
conn, err := sv.internalLn.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("xtcp internal listener closed")
|
xl.Warnf("xtcp internal listener closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go sv.handleConn(conn)
|
go sv.handleConn(conn)
|
||||||
@ -140,14 +144,14 @@ func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
|||||||
case <-sv.ctx.Done():
|
case <-sv.ctx.Done():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
xl.Debug("keepTunnelOpenWorker try to check tunnel...")
|
xl.Debugf("keepTunnelOpenWorker try to check tunnel...")
|
||||||
conn, err := sv.getTunnelConn()
|
conn, err := sv.getTunnelConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
xl.Warnf("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
||||||
_ = sv.retryLimiter.Wait(sv.ctx)
|
_ = sv.retryLimiter.Wait(sv.ctx)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
xl.Debug("keepTunnelOpenWorker check success")
|
xl.Debugf("keepTunnelOpenWorker check success")
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
@ -157,14 +161,14 @@ func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
|||||||
|
|
||||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
isConnTrasfered := false
|
isConnTransfered := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isConnTrasfered {
|
if !isConnTransfered {
|
||||||
userConn.Close()
|
userConn.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
xl.Debug("get a new xtcp user connection")
|
xl.Debugf("get a new xtcp user connection")
|
||||||
|
|
||||||
// Open a tunnel connection to the server. If there is already a successful hole-punching 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.
|
// it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout.
|
||||||
@ -176,18 +180,18 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
}
|
}
|
||||||
tunnelConn, err := sv.openTunnel(ctx)
|
tunnelConn, err := sv.openTunnel(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("open tunnel error: %v", err)
|
xl.Errorf("open tunnel error: %v", err)
|
||||||
// no fallback, just return
|
// no fallback, just return
|
||||||
if sv.cfg.FallbackTo == "" {
|
if sv.cfg.FallbackTo == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Debug("try to transfer connection to visitor: %s", sv.cfg.FallbackTo)
|
xl.Debugf("try to transfer connection to visitor: %s", sv.cfg.FallbackTo)
|
||||||
if err := sv.helper.TransferConn(sv.cfg.FallbackTo, userConn); err != nil {
|
if err := sv.helper.TransferConn(sv.cfg.FallbackTo, userConn); err != nil {
|
||||||
xl.Error("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isConnTrasfered = true
|
isConnTransfered = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +199,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
if sv.cfg.Transport.UseEncryption {
|
if sv.cfg.Transport.UseEncryption {
|
||||||
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
|
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Errorf("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,9 +210,9 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
||||||
xl.Debug("join connections closed")
|
xl.Debugf("join connections closed")
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
xl.Trace("join connections errors: %v", errs)
|
xl.Tracef("join connections errors: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +243,7 @@ func (sv *XTCPVisitor) openTunnel(ctx context.Context) (conn net.Conn, err error
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrNoTunnelSession {
|
if err != ErrNoTunnelSession {
|
||||||
xl.Warn("get tunnel connection error: %v", err)
|
xl.Warnf("get tunnel connection error: %v", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -268,19 +272,19 @@ func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
|
|||||||
// 4. Create a tunnel session using an underlying UDP connection.
|
// 4. Create a tunnel session using an underlying UDP connection.
|
||||||
func (sv *XTCPVisitor) makeNatHole() {
|
func (sv *XTCPVisitor) makeNatHole() {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
xl.Trace("makeNatHole start")
|
xl.Tracef("makeNatHole start")
|
||||||
if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil {
|
if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil {
|
||||||
xl.Warn("nathole precheck error: %v", err)
|
xl.Warnf("nathole precheck error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("nathole prepare start")
|
xl.Tracef("nathole prepare start")
|
||||||
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("nathole prepare error: %v", err)
|
xl.Warnf("nathole prepare error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||||
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||||
|
|
||||||
listenConn := prepareResult.ListenConn
|
listenConn := prepareResult.ListenConn
|
||||||
@ -298,30 +302,30 @@ func (sv *XTCPVisitor) makeNatHole() {
|
|||||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("nathole exchange info start")
|
xl.Tracef("nathole exchange info start")
|
||||||
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second)
|
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listenConn.Close()
|
listenConn.Close()
|
||||||
xl.Warn("nathole exchange info error: %v", err)
|
xl.Warnf("nathole exchange info error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
xl.Infof("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.SecretKey))
|
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listenConn.Close()
|
listenConn.Close()
|
||||||
xl.Warn("make hole error: %v", err)
|
xl.Warnf("make hole error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
listenConn = newListenConn
|
listenConn = newListenConn
|
||||||
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
xl.Infof("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
if err := sv.session.Init(listenConn, raddr); err != nil {
|
if err := sv.session.Init(listenConn, raddr); err != nil {
|
||||||
listenConn.Close()
|
listenConn.Close()
|
||||||
xl.Warn("init tunnel session error: %v", err)
|
xl.Warnf("init tunnel session error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
_ "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
system.EnableCompatibilityMode()
|
||||||
sub.Execute()
|
sub.Execute()
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,11 @@
|
|||||||
package sub
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rodaine/table"
|
"github.com/rodaine/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -27,24 +29,24 @@ import (
|
|||||||
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var adminAPITimeout = 30 * time.Second
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
commands := []struct {
|
||||||
"reload",
|
name string
|
||||||
"Hot-Reload frpc configuration",
|
description string
|
||||||
ReloadHandler,
|
handler func(*v1.ClientCommonConfig) error
|
||||||
))
|
}{
|
||||||
|
{"reload", "Hot-Reload frpc configuration", ReloadHandler},
|
||||||
|
{"status", "Overview of all proxies status", StatusHandler},
|
||||||
|
{"stop", "Stop the running frpc", StopHandler},
|
||||||
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
for _, cmdConfig := range commands {
|
||||||
"status",
|
cmd := NewAdminCommand(cmdConfig.name, cmdConfig.description, cmdConfig.handler)
|
||||||
"Overview of all proxies status",
|
cmd.Flags().DurationVar(&adminAPITimeout, "api-timeout", adminAPITimeout, "Timeout for admin API calls")
|
||||||
StatusHandler,
|
rootCmd.AddCommand(cmd)
|
||||||
))
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
|
||||||
"stop",
|
|
||||||
"Stop the running frpc",
|
|
||||||
StopHandler,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
||||||
@ -73,7 +75,9 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
|
|||||||
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
if err := client.Reload(strictConfigMode); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Reload(ctx, strictConfigMode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("reload success")
|
fmt.Println("reload success")
|
||||||
@ -83,7 +87,9 @@ func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
|||||||
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
res, err := client.GetAllProxyStatus()
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
res, err := client.GetAllProxyStatus(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -109,7 +115,9 @@ func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
|||||||
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
if err := client.Stop(); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Stop(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("stop success")
|
fmt.Println("stop success")
|
||||||
|
@ -17,8 +17,8 @@ package sub
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
@ -55,7 +55,7 @@ func init() {
|
|||||||
config.RegisterProxyFlags(cmd, c)
|
config.RegisterProxyFlags(cmd, c)
|
||||||
|
|
||||||
// add sub command for visitor
|
// add sub command for visitor
|
||||||
if lo.Contains(visitorTypes, v1.VisitorType(typ)) {
|
if slices.Contains(visitorTypes, v1.VisitorType(typ)) {
|
||||||
vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ))
|
vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ))
|
||||||
if vc == nil {
|
if vc == nil {
|
||||||
panic("visitor type: " + typ + " not support")
|
panic("visitor type: " + typ + " not support")
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
"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"
|
||||||
)
|
)
|
||||||
@ -46,7 +47,7 @@ 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().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", "", false, "strict config parsing mode, unknown fields will cause an error")
|
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@ -97,6 +98,7 @@ func runMultipleClients(cfgDir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -119,6 +121,12 @@ func runClient(cfgFilePath string) error {
|
|||||||
"please use yaml/json/toml format instead!\n")
|
"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)
|
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs)
|
||||||
if warning != nil {
|
if warning != nil {
|
||||||
fmt.Printf("WARNING: %v\n", warning)
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
@ -135,11 +143,11 @@ func startService(
|
|||||||
visitorCfgs []v1.VisitorConfigurer,
|
visitorCfgs []v1.VisitorConfigurer,
|
||||||
cfgFile string,
|
cfgFile string,
|
||||||
) error {
|
) error {
|
||||||
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
|
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
log.Info("start frpc service for config file [%s]", cfgFile)
|
log.Infof("start frpc service for config file [%s]", cfgFile)
|
||||||
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
|
defer log.Infof("frpc service for config file [%s] stopped", cfgFile)
|
||||||
}
|
}
|
||||||
svr, err := client.NewService(client.ServiceOptions{
|
svr, err := client.NewService(client.ServiceOptions{
|
||||||
Common: cfg,
|
Common: cfg,
|
||||||
|
@ -15,13 +15,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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()
|
||||||
Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ var (
|
|||||||
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", "", false, "strict config parsing mode, unknown fields will cause error")
|
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause errors")
|
||||||
|
|
||||||
config.RegisterServerConfigFlags(rootCmd, &serverCfg)
|
config.RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||||
}
|
}
|
||||||
@ -92,25 +92,26 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 runServer(cfg *v1.ServerConfig) (err error) {
|
func runServer(cfg *v1.ServerConfig) (err error) {
|
||||||
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
|
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
||||||
|
|
||||||
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(context.Background())
|
svr.Run(context.Background())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ transport.poolCount = 5
|
|||||||
|
|
||||||
# Specify keep alive interval for tcp mux.
|
# Specify keep alive interval for tcp mux.
|
||||||
# only valid if tcpMux is enabled.
|
# only valid if tcpMux is enabled.
|
||||||
# transport.tcpMuxKeepaliveInterval = 60
|
# transport.tcpMuxKeepaliveInterval = 30
|
||||||
|
|
||||||
# Communication protocol used to connect to server
|
# Communication protocol used to connect to server
|
||||||
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||||
@ -129,6 +129,15 @@ transport.tls.enable = true
|
|||||||
# It affects the udp and sudp proxy.
|
# It affects the udp and sudp proxy.
|
||||||
udpPacketSize = 1500
|
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.
|
# Additional metadatas for client.
|
||||||
metadatas.var1 = "abc"
|
metadatas.var1 = "abc"
|
||||||
metadatas.var2 = "123"
|
metadatas.var2 = "123"
|
||||||
@ -164,11 +173,16 @@ healthCheck.type = "tcp"
|
|||||||
healthCheck.timeoutSeconds = 3
|
healthCheck.timeoutSeconds = 3
|
||||||
# If continuous failed in 3 times, the proxy will be removed from frps
|
# If continuous failed in 3 times, the proxy will be removed from frps
|
||||||
healthCheck.maxFailed = 3
|
healthCheck.maxFailed = 3
|
||||||
# every 10 seconds will do a health check
|
# Every 10 seconds will do a health check
|
||||||
healthCheck.intervalSeconds = 10
|
healthCheck.intervalSeconds = 10
|
||||||
# additional meta info for each proxy
|
# Additional meta info for each proxy. It will be passed to the server-side plugin for use.
|
||||||
metadatas.var1 = "abc"
|
metadatas.var1 = "abc"
|
||||||
metadatas.var2 = "123"
|
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]]
|
[[proxies]]
|
||||||
name = "ssh_random"
|
name = "ssh_random"
|
||||||
@ -204,6 +218,7 @@ locations = ["/", "/pic"]
|
|||||||
# routeByHTTPUser = abc
|
# routeByHTTPUser = abc
|
||||||
hostHeaderRewrite = "example.com"
|
hostHeaderRewrite = "example.com"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
responseHeaders.set.foo = "bar"
|
||||||
healthCheck.type = "http"
|
healthCheck.type = "http"
|
||||||
# frpc will send a GET http request '/status' to local http service
|
# frpc will send a GET http request '/status' to local http service
|
||||||
# http service is alive when it return 2xx http response code
|
# http service is alive when it return 2xx http response code
|
||||||
@ -211,6 +226,10 @@ healthCheck.path = "/status"
|
|||||||
healthCheck.intervalSeconds = 10
|
healthCheck.intervalSeconds = 10
|
||||||
healthCheck.maxFailed = 3
|
healthCheck.maxFailed = 3
|
||||||
healthCheck.timeoutSeconds = 3
|
healthCheck.timeoutSeconds = 3
|
||||||
|
# set health check headers
|
||||||
|
healthCheck.httpHeaders=[
|
||||||
|
{ name = "x-from-where", value = "frp" }
|
||||||
|
]
|
||||||
|
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "web02"
|
name = "web02"
|
||||||
@ -305,6 +324,26 @@ localAddr = "127.0.0.1:443"
|
|||||||
hostHeaderRewrite = "127.0.0.1"
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
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]]
|
[[proxies]]
|
||||||
name = "secret_tcp"
|
name = "secret_tcp"
|
||||||
# If the type is secret tcp, remotePort is useless
|
# If the type is secret tcp, remotePort is useless
|
||||||
@ -328,6 +367,13 @@ localPort = 22
|
|||||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
allowUsers = ["user1", "user2"]
|
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
|
# frpc role visitor -> frps -> frpc role server
|
||||||
[[visitors]]
|
[[visitors]]
|
||||||
name = "secret_tcp_visitor"
|
name = "secret_tcp_visitor"
|
||||||
@ -359,3 +405,13 @@ maxRetriesAnHour = 8
|
|||||||
minRetryInterval = 90
|
minRetryInterval = 90
|
||||||
# fallbackTo = "stcp_visitor"
|
# fallbackTo = "stcp_visitor"
|
||||||
# fallbackTimeoutMs = 500
|
# 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"
|
||||||
|
@ -34,14 +34,14 @@ transport.maxPoolCount = 5
|
|||||||
|
|
||||||
# Specify keep alive interval for tcp mux.
|
# Specify keep alive interval for tcp mux.
|
||||||
# only valid if tcpMux is true.
|
# only valid if tcpMux is true.
|
||||||
# transport.tcpMuxKeepaliveInterval = 60
|
# transport.tcpMuxKeepaliveInterval = 30
|
||||||
|
|
||||||
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
# If negative, keep-alive probes are disabled.
|
# If negative, keep-alive probes are disabled.
|
||||||
# transport.tcpKeepalive = 7200
|
# transport.tcpKeepalive = 7200
|
||||||
|
|
||||||
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||||
tls.force = false
|
transport.tls.force = false
|
||||||
|
|
||||||
# transport.tls.certFile = "server.crt"
|
# transport.tls.certFile = "server.crt"
|
||||||
# transport.tls.keyFile = "server.key"
|
# transport.tls.keyFile = "server.key"
|
||||||
@ -129,7 +129,7 @@ allowPorts = [
|
|||||||
maxPortsPerClient = 0
|
maxPortsPerClient = 0
|
||||||
|
|
||||||
# If subDomainHost is not empty, you can set subdomain when type is http or https in frpc's configure file
|
# If subDomainHost is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||||
# When subdomain is est, the host used by routing is test.frps.com
|
# When subdomain is test, the host used by routing is test.frps.com
|
||||||
subDomainHost = "frps.com"
|
subDomainHost = "frps.com"
|
||||||
|
|
||||||
# custom 404 page for HTTP requests
|
# custom 404 page for HTTP requests
|
||||||
|
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 |
Before Width: | Height: | Size: 12 KiB |
BIN
doc/pic/sponsor_olares.jpeg
Normal file
After Width: | Height: | Size: 46 KiB |
@ -121,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>,
|
||||||
|
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,4 +1,4 @@
|
|||||||
FROM golang:1.21 AS building
|
FROM golang:1.23 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
@ -7,6 +7,8 @@ RUN make frpc
|
|||||||
|
|
||||||
FROM alpine:3
|
FROM alpine:3
|
||||||
|
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
|
|
||||||
COPY --from=building /building/bin/frpc /usr/bin/frpc
|
COPY --from=building /building/bin/frpc /usr/bin/frpc
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/frpc"]
|
ENTRYPOINT ["/usr/bin/frpc"]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.21 AS building
|
FROM golang:1.23 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
@ -7,6 +7,8 @@ RUN make frps
|
|||||||
|
|
||||||
FROM alpine:3
|
FROM alpine:3
|
||||||
|
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
|
|
||||||
COPY --from=building /building/bin/frps /usr/bin/frps
|
COPY --from=building /building/bin/frps /usr/bin/frps
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/frps"]
|
ENTRYPOINT ["/usr/bin/frps"]
|
||||||
|
103
go.mod
@ -1,80 +1,83 @@
|
|||||||
module github.com/fatedier/frp
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
go 1.20
|
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/v3 v3.6.0
|
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.20230725122706-dcbaee8eef40
|
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/google/uuid v1.3.0
|
|
||||||
github.com/gorilla/mux v1.8.0
|
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hashicorp/yamux v0.1.1
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/onsi/ginkgo/v2 v2.11.0
|
github.com/onsi/ginkgo/v2 v2.23.4
|
||||||
github.com/onsi/gomega v1.27.8
|
github.com/onsi/gomega v1.36.3
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0
|
github.com/pelletier/go-toml/v2 v2.2.0
|
||||||
github.com/pion/stun v0.6.1
|
github.com/pion/stun/v2 v2.0.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/prometheus/client_golang v1.16.0
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/quic-go/quic-go v0.37.4
|
github.com/quic-go/quic-go v0.48.2
|
||||||
github.com/rodaine/table v1.1.0
|
github.com/rodaine/table v1.2.0
|
||||||
github.com/samber/lo v1.38.1
|
github.com/samber/lo v1.47.0
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.org/x/crypto v0.15.0
|
github.com/tidwall/gjson v1.17.1
|
||||||
golang.org/x/net v0.17.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
golang.org/x/oauth2 v0.10.0
|
github.com/xtaci/kcp-go/v5 v5.6.13
|
||||||
golang.org/x/sync v0.3.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/net v0.39.0
|
||||||
|
golang.org/x/oauth2 v0.28.0
|
||||||
|
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
|
gopkg.in/ini.v1 v1.67.0
|
||||||
k8s.io/apimachinery v0.27.4
|
k8s.io/apimachinery v0.28.8
|
||||||
k8s.io/client-go v0.27.4
|
k8s.io/client-go v0.28.8
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/go-logr/logr v1.2.4 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.9.15 // indirect
|
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
|
||||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/transport/v2 v2.2.1 // 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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.10.1 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
golang.org/x/mod v0.10.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
||||||
golang.org/x/tools v0.9.3 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.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.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
260
go.sum
@ -1,6 +1,6 @@
|
|||||||
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=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/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/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=
|
||||||
@ -9,125 +9,115 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
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/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/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/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o=
|
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||||
github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc=
|
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
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/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.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/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
||||||
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40 h1:BVdpWT6viE/mpuRa6txNyRNjtHa1Efrii9Du6/gHfJ0=
|
|
||||||
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40/go.mod h1:Lmi9U4VfvdRvonSMh1FgXVy1hCXycVyJk4E9ktokknE=
|
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
|
||||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
||||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
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/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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
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.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
|
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
|
||||||
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
|
github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
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/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||||
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
|
||||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
|
||||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
|
||||||
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||||
|
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||||
|
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
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/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
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.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
|
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||||
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
|
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||||
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
|
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
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=
|
||||||
@ -135,117 +125,134 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||||||
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||||
|
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
|
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
||||||
|
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/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
|
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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
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-20190911031432-227b76d455e7/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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
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-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
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-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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
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-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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
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.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
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-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-20210220032951-036812b2e83c/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
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-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-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-20191204072324-ce4227a45e2e/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
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.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
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-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-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.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
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.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@ -258,12 +265,11 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
@ -271,14 +277,16 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=
|
k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ=
|
||||||
k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
|
k8s.io/apimachinery v0.28.8/go.mod h1:cBnwIM3fXoRo28SqbV/Ihxf/iviw85KyXOrzxvZQ83U=
|
||||||
k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk=
|
k8s.io/client-go v0.28.8 h1:TE59Tjd87WKvS2FPBTfIKLFX0nQJ4SSHsnDo5IHjgOw=
|
||||||
k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc=
|
k8s.io/client-go v0.28.8/go.mod h1:uDVQ/rPzWpWIy40c6lZ4mUwaEvRWGnpoqSO4FM65P3o=
|
||||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
|
||||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
SCRIPT=$(readlink -f "$0")
|
SCRIPT=$(readlink -f "$0")
|
||||||
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
||||||
|
|
||||||
ginkgo_command=$(which ginkgo 2>/dev/null)
|
# Check if ginkgo is available
|
||||||
if [ -z "$ginkgo_command" ]; 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/v2/ginkgo@v2.11.0
|
go install github.com/onsi/ginkgo/v2/ginkgo@v2.23.4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug=false
|
debug=false
|
||||||
@ -26,5 +26,9 @@ frpsPath=${ROOT}/bin/frps
|
|||||||
if [ "${FRPS_PATH}" ]; then
|
if [ "${FRPS_PATH}" ]; then
|
||||||
frpsPath="${FRPS_PATH}"
|
frpsPath="${FRPS_PATH}"
|
||||||
fi
|
fi
|
||||||
|
concurrency="16"
|
||||||
|
if [ "${CONCURRENCY}" ]; then
|
||||||
|
concurrency="${CONCURRENCY}"
|
||||||
|
fi
|
||||||
|
|
||||||
ginkgo -nodes=8 --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
|
ginkgo -nodes=${concurrency} --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
|
||||||
|
84
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,50 +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 riscv64'
|
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 -f ../conf/frpc.toml ${frp_path}
|
cp -f ../conf/frpc.toml ${frp_path}
|
||||||
cp -f ../conf/frps.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
|
||||||
|
|
||||||
|
@ -50,7 +50,8 @@ func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
|||||||
case v1.AuthMethodToken:
|
case v1.AuthMethodToken:
|
||||||
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
||||||
case v1.AuthMethodOIDC:
|
case v1.AuthMethodOIDC:
|
||||||
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
|
tokenVerifier := NewTokenVerifier(cfg.OIDC)
|
||||||
|
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
|
||||||
}
|
}
|
||||||
return authVerifier
|
return authVerifier
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/samber/lo"
|
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
@ -70,7 +70,7 @@ func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,14 +87,18 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TokenVerifier interface {
|
||||||
|
Verify(context.Context, string) (*oidc.IDToken, error)
|
||||||
|
}
|
||||||
|
|
||||||
type OidcAuthConsumer struct {
|
type OidcAuthConsumer struct {
|
||||||
additionalAuthScopes []v1.AuthScope
|
additionalAuthScopes []v1.AuthScope
|
||||||
|
|
||||||
verifier *oidc.IDTokenVerifier
|
verifier TokenVerifier
|
||||||
subjectFromLogin string
|
subjectsFromLogin []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
|
func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
|
||||||
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
|
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -105,9 +109,14 @@ func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCSer
|
|||||||
SkipExpiryCheck: cfg.SkipExpiryCheck,
|
SkipExpiryCheck: cfg.SkipExpiryCheck,
|
||||||
SkipIssuerCheck: cfg.SkipIssuerCheck,
|
SkipIssuerCheck: cfg.SkipIssuerCheck,
|
||||||
}
|
}
|
||||||
|
return provider.Verifier(&verifierConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {
|
||||||
return &OidcAuthConsumer{
|
return &OidcAuthConsumer{
|
||||||
additionalAuthScopes: additionalAuthScopes,
|
additionalAuthScopes: additionalAuthScopes,
|
||||||
verifier: provider.Verifier(&verifierConf),
|
verifier: verifier,
|
||||||
|
subjectsFromLogin: []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +125,9 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid OIDC token in login: %v", err)
|
return fmt.Errorf("invalid OIDC token in login: %v", err)
|
||||||
}
|
}
|
||||||
auth.subjectFromLogin = token.Subject
|
if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
|
||||||
|
auth.subjectsFromLogin = append(auth.subjectsFromLogin, token.Subject)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,17 +136,17 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid OIDC token in ping: %v", err)
|
return fmt.Errorf("invalid OIDC token in ping: %v", err)
|
||||||
}
|
}
|
||||||
if token.Subject != auth.subjectFromLogin {
|
if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
|
||||||
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
||||||
"original subject: %s, "+
|
"original subjects: %s, "+
|
||||||
"new subject: %s",
|
"new subject: %s",
|
||||||
auth.subjectFromLogin, token.Subject)
|
auth.subjectsFromLogin, token.Subject)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +154,7 @@ func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
pkg/auth/oidc_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package auth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockTokenVerifier struct{}
|
||||||
|
|
||||||
|
func (m *mockTokenVerifier) Verify(ctx context.Context, subject string) (*oidc.IDToken, error) {
|
||||||
|
return &oidc.IDToken{
|
||||||
|
Subject: subject,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingWithEmptySubjectFromLoginFails(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
|
||||||
|
err := consumer.VerifyPing(&msg.Ping{
|
||||||
|
PrivilegeKey: "ping-without-login",
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
r.Error(err)
|
||||||
|
r.Contains(err.Error(), "received different OIDC subject in login and ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingAfterLoginWithNewSubjectSucceeds(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
|
||||||
|
err := consumer.VerifyLogin(&msg.Login{
|
||||||
|
PrivilegeKey: "ping-after-login",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = consumer.VerifyPing(&msg.Ping{
|
||||||
|
PrivilegeKey: "ping-after-login",
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingAfterLoginWithDifferentSubjectFails(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
|
||||||
|
err := consumer.VerifyLogin(&msg.Login{
|
||||||
|
PrivilegeKey: "login-with-first-subject",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = consumer.VerifyPing(&msg.Ping{
|
||||||
|
PrivilegeKey: "ping-with-different-subject",
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
r.Error(err)
|
||||||
|
r.Contains(err.Error(), "received different OIDC subject in login and ping")
|
||||||
|
}
|
@ -16,10 +16,9 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
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/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
@ -43,7 +42,7 @@ func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +80,7 @@ func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
||||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,14 +17,24 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config/types"
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WordSepNormalizeFunc changes all flags that contain "_" separators
|
||||||
|
func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
if strings.Contains(name, "_") {
|
||||||
|
return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-"))
|
||||||
|
}
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
|
|
||||||
type RegisterFlagOption func(*registerFlagOptions)
|
type RegisterFlagOption func(*registerFlagOptions)
|
||||||
|
|
||||||
type registerFlagOptions struct {
|
type registerFlagOptions struct {
|
||||||
@ -96,6 +106,8 @@ func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig, opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
||||||
|
cmd.Flags().StringToStringVarP(&c.Metadatas, "metadatas", "", nil, "metadata key-value pairs (e.g., key1=value1,key2=value2)")
|
||||||
|
cmd.Flags().StringToStringVarP(&c.Annotations, "annotations", "", nil, "annotation key-value pairs (e.g., key1=value1,key2=value2)")
|
||||||
|
|
||||||
if !options.sshMode {
|
if !options.sshMode {
|
||||||
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
|
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@ -130,6 +142,7 @@ func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig,
|
|||||||
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
||||||
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
|
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
|
||||||
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
|
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
|
||||||
|
cmd.Flags().StringVarP(&c.ServerUser, "server-user", "", "", "server user")
|
||||||
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
|
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
|
||||||
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
|
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
|
||||||
}
|
}
|
||||||
@ -216,6 +229,7 @@ func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig, opts ...R
|
|||||||
cmd.PersistentFlags().StringVarP(&c.BindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
cmd.PersistentFlags().StringVarP(&c.BindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||||
cmd.PersistentFlags().IntVarP(&c.BindPort, "bind_port", "p", 7000, "bind port")
|
cmd.PersistentFlags().IntVarP(&c.BindPort, "bind_port", "p", 7000, "bind port")
|
||||||
cmd.PersistentFlags().IntVarP(&c.KCPBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
cmd.PersistentFlags().IntVarP(&c.KCPBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||||
|
cmd.PersistentFlags().IntVarP(&c.QUICBindPort, "quic_bind_port", "", 0, "quic bind udp port")
|
||||||
cmd.PersistentFlags().StringVarP(&c.ProxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
cmd.PersistentFlags().StringVarP(&c.ProxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||||
cmd.PersistentFlags().IntVarP(&c.VhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
cmd.PersistentFlags().IntVarP(&c.VhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
||||||
cmd.PersistentFlags().IntVarP(&c.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
cmd.PersistentFlags().IntVarP(&c.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
||||||
|
@ -18,9 +18,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
|
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
|
||||||
@ -170,7 +170,7 @@ type ClientCommonConf struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Supported sources including: string(file path), []byte, Reader interface.
|
// Supported sources including: string(file path), []byte, Reader interface.
|
||||||
func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
Insensitive: false,
|
Insensitive: false,
|
||||||
InsensitiveSections: false,
|
InsensitiveSections: false,
|
||||||
@ -194,7 +194,7 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||||
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
common.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||||
|
|
||||||
return common, nil
|
return common, nil
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
|||||||
// otherwise just start proxies in startProxy map
|
// otherwise just start proxies in startProxy map
|
||||||
func LoadAllProxyConfsFromIni(
|
func LoadAllProxyConfsFromIni(
|
||||||
prefix string,
|
prefix string,
|
||||||
source interface{},
|
source any,
|
||||||
start []string,
|
start []string,
|
||||||
) (map[string]ProxyConf, map[string]VisitorConf, error) {
|
) (map[string]ProxyConf, map[string]VisitorConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
@ -229,10 +229,7 @@ func LoadAllProxyConfsFromIni(
|
|||||||
startProxy[s] = struct{}{}
|
startProxy[s] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
startAll := true
|
startAll := len(startProxy) == 0
|
||||||
if len(startProxy) > 0 {
|
|
||||||
startAll = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build template sections from range section And append to ini.File.
|
// Build template sections from range section And append to ini.File.
|
||||||
rangeSections := make([]*ini.Section, 0)
|
rangeSections := make([]*ini.Section, 0)
|
||||||
@ -345,35 +342,19 @@ func copySection(source, target *ini.Section) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultClientConf returns a client configuration with default values.
|
// GetDefaultClientConf returns a client configuration with default values.
|
||||||
|
// Note: Some default values here will be set to empty and will be converted to them
|
||||||
|
// new configuration through the 'Complete' function to set them as the default
|
||||||
|
// values of the new configuration.
|
||||||
func GetDefaultClientConf() ClientCommonConf {
|
func GetDefaultClientConf() ClientCommonConf {
|
||||||
return ClientCommonConf{
|
return ClientCommonConf{
|
||||||
ClientConfig: legacyauth.GetDefaultClientConf(),
|
ClientConfig: legacyauth.GetDefaultClientConf(),
|
||||||
ServerAddr: "0.0.0.0",
|
|
||||||
ServerPort: 7000,
|
|
||||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
|
||||||
DialServerTimeout: 10,
|
|
||||||
DialServerKeepAlive: 7200,
|
|
||||||
HTTPProxy: os.Getenv("http_proxy"),
|
|
||||||
LogFile: "console",
|
|
||||||
LogWay: "console",
|
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
AdminAddr: "127.0.0.1",
|
|
||||||
PoolCount: 1,
|
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Start: make([]string, 0),
|
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
QUICKeepalivePeriod: 10,
|
Start: make([]string, 0),
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
TLSEnable: true,
|
TLSEnable: true,
|
||||||
DisableCustomTLSFirstByte: true,
|
DisableCustomTLSFirstByte: true,
|
||||||
HeartbeatInterval: 30,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
Metas: make(map[string]string),
|
Metas: make(map[string]string),
|
||||||
UDPPacketSize: 1500,
|
|
||||||
IncludeConfigFiles: make([]string, 0),
|
IncludeConfigFiles: make([]string, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,7 +380,7 @@ func (cfg *ClientCommonConf) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
|
if !slices.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
|
||||||
return fmt.Errorf("invalid protocol")
|
return fmt.Errorf("invalid protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,20 +26,20 @@ import (
|
|||||||
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
||||||
out := &v1.ClientCommonConfig{}
|
out := &v1.ClientCommonConfig{}
|
||||||
out.User = conf.User
|
out.User = conf.User
|
||||||
out.Auth.Method = v1.AuthMethod(conf.ClientConfig.AuthenticationMethod)
|
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod)
|
||||||
out.Auth.Token = conf.ClientConfig.Token
|
out.Auth.Token = conf.Token
|
||||||
if conf.ClientConfig.AuthenticateHeartBeats {
|
if conf.AuthenticateHeartBeats {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
||||||
}
|
}
|
||||||
if conf.ClientConfig.AuthenticateNewWorkConns {
|
if conf.AuthenticateNewWorkConns {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
||||||
}
|
}
|
||||||
out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
|
out.Auth.OIDC.ClientID = conf.OidcClientID
|
||||||
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
|
out.Auth.OIDC.ClientSecret = conf.OidcClientSecret
|
||||||
out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
|
out.Auth.OIDC.Audience = conf.OidcAudience
|
||||||
out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
|
out.Auth.OIDC.Scope = conf.OidcScope
|
||||||
out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
|
out.Auth.OIDC.TokenEndpointURL = conf.OidcTokenEndpointURL
|
||||||
out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
|
out.Auth.OIDC.AdditionalEndpointParams = conf.OidcAdditionalEndpointParams
|
||||||
|
|
||||||
out.ServerAddr = conf.ServerAddr
|
out.ServerAddr = conf.ServerAddr
|
||||||
out.ServerPort = conf.ServerPort
|
out.ServerPort = conf.ServerPort
|
||||||
@ -59,10 +59,10 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
|
|||||||
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
||||||
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
||||||
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
||||||
out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
|
out.Transport.TLS.CertFile = conf.TLSCertFile
|
||||||
out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
|
out.Transport.TLS.KeyFile = conf.TLSKeyFile
|
||||||
out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
|
out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile
|
||||||
out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
|
out.Transport.TLS.ServerName = conf.TLSServerName
|
||||||
|
|
||||||
out.Log.To = conf.LogFile
|
out.Log.To = conf.LogFile
|
||||||
out.Log.Level = conf.LogLevel
|
out.Log.Level = conf.LogLevel
|
||||||
@ -87,18 +87,18 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
|
|||||||
|
|
||||||
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
|
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
|
||||||
out := &v1.ServerConfig{}
|
out := &v1.ServerConfig{}
|
||||||
out.Auth.Method = v1.AuthMethod(conf.ServerConfig.AuthenticationMethod)
|
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod)
|
||||||
out.Auth.Token = conf.ServerConfig.Token
|
out.Auth.Token = conf.Token
|
||||||
if conf.ServerConfig.AuthenticateHeartBeats {
|
if conf.AuthenticateHeartBeats {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
||||||
}
|
}
|
||||||
if conf.ServerConfig.AuthenticateNewWorkConns {
|
if conf.AuthenticateNewWorkConns {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
||||||
}
|
}
|
||||||
out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
|
out.Auth.OIDC.Audience = conf.OidcAudience
|
||||||
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
|
out.Auth.OIDC.Issuer = conf.OidcIssuer
|
||||||
out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
|
out.Auth.OIDC.SkipExpiryCheck = conf.OidcSkipExpiryCheck
|
||||||
out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
|
out.Auth.OIDC.SkipIssuerCheck = conf.OidcSkipIssuerCheck
|
||||||
|
|
||||||
out.BindAddr = conf.BindAddr
|
out.BindAddr = conf.BindAddr
|
||||||
out.BindPort = conf.BindPort
|
out.BindPort = conf.BindPort
|
||||||
@ -175,6 +175,9 @@ func transformHeadersFromPluginParams(params map[string]string) v1.HeaderOperati
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||||
|
if out.Set == nil {
|
||||||
|
out.Set = make(map[string]string)
|
||||||
|
}
|
||||||
out.Set[k] = v
|
out.Set[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// plugin_xxx
|
// plugin_xxx
|
||||||
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
cfg.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,38 +200,24 @@ type ServerCommonConf struct {
|
|||||||
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultServerConf returns a server configuration with reasonable
|
// GetDefaultServerConf returns a server configuration with reasonable defaults.
|
||||||
// defaults.
|
// Note: Some default values here will be set to empty and will be converted to them
|
||||||
|
// new configuration through the 'Complete' function to set them as the default
|
||||||
|
// values of the new configuration.
|
||||||
func GetDefaultServerConf() ServerCommonConf {
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
return ServerCommonConf{
|
return ServerCommonConf{
|
||||||
ServerConfig: legacyauth.GetDefaultServerConf(),
|
ServerConfig: legacyauth.GetDefaultServerConf(),
|
||||||
BindAddr: "0.0.0.0",
|
DashboardAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
LogFile: "console",
|
||||||
QUICKeepalivePeriod: 10,
|
LogWay: "console",
|
||||||
QUICMaxIdleTimeout: 30,
|
DetailedErrorsToClient: true,
|
||||||
QUICMaxIncomingStreams: 100000,
|
TCPMux: true,
|
||||||
VhostHTTPTimeout: 60,
|
AllowPorts: make(map[int]struct{}),
|
||||||
DashboardAddr: "0.0.0.0",
|
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||||
LogFile: "console",
|
|
||||||
LogWay: "console",
|
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
DetailedErrorsToClient: true,
|
|
||||||
TCPMux: true,
|
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
TCPKeepAlive: 7200,
|
|
||||||
AllowPorts: make(map[int]struct{}),
|
|
||||||
MaxPoolCount: 5,
|
|
||||||
MaxPortsPerClient: 0,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
UserConnTimeout: 10,
|
|
||||||
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
|
||||||
UDPPacketSize: 1500,
|
|
||||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
func UnmarshalServerConfFromIni(source any) (ServerCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
Insensitive: false,
|
Insensitive: false,
|
||||||
InsensitiveSections: false,
|
InsensitiveSections: false,
|
||||||
|
@ -18,10 +18,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
toml "github.com/pelletier/go-toml/v2"
|
toml "github.com/pelletier/go-toml/v2"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
@ -80,7 +80,10 @@ func DetectLegacyINIFormatFromFile(path string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RenderWithTemplate(in []byte, values *Values) ([]byte, error) {
|
func RenderWithTemplate(in []byte, values *Values) ([]byte, error) {
|
||||||
tmpl, err := template.New("frp").Parse(string(in))
|
tmpl, err := template.New("frp").Funcs(template.FuncMap{
|
||||||
|
"parseNumberRange": parseNumberRange,
|
||||||
|
"parseNumberRangePair": parseNumberRangePair,
|
||||||
|
}).Parse(string(in))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -108,6 +111,33 @@ func LoadConfigureFromFile(path string, c any, strict bool) error {
|
|||||||
return LoadConfigure(content, c, strict)
|
return LoadConfigure(content, c, strict)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseYAMLWithDotFieldsHandling parses YAML with dot-prefixed fields handling
|
||||||
|
// This function handles both cases efficiently: with or without dot fields
|
||||||
|
func parseYAMLWithDotFieldsHandling(content []byte, target any) error {
|
||||||
|
var temp any
|
||||||
|
if err := yaml.Unmarshal(content, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dot fields if it's a map
|
||||||
|
if tempMap, ok := temp.(map[string]any); ok {
|
||||||
|
for key := range tempMap {
|
||||||
|
if strings.HasPrefix(key, ".") {
|
||||||
|
delete(tempMap, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to JSON and decode with strict validation
|
||||||
|
jsonBytes, err := json.Marshal(temp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
return decoder.Decode(target)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
||||||
// Now it supports json, yaml and toml format.
|
// Now it supports json, yaml and toml format.
|
||||||
func LoadConfigure(b []byte, c any, strict bool) error {
|
func LoadConfigure(b []byte, c any, strict bool) error {
|
||||||
@ -115,7 +145,7 @@ func LoadConfigure(b []byte, c any, strict bool) error {
|
|||||||
defer v1.DisallowUnknownFieldsMu.Unlock()
|
defer v1.DisallowUnknownFieldsMu.Unlock()
|
||||||
v1.DisallowUnknownFields = strict
|
v1.DisallowUnknownFields = strict
|
||||||
|
|
||||||
var tomlObj interface{}
|
var tomlObj any
|
||||||
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
|
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
|
||||||
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
||||||
b, err = json.Marshal(&tomlObj)
|
b, err = json.Marshal(&tomlObj)
|
||||||
@ -131,10 +161,13 @@ func LoadConfigure(b []byte, c any, strict bool) error {
|
|||||||
}
|
}
|
||||||
return decoder.Decode(c)
|
return decoder.Decode(c)
|
||||||
}
|
}
|
||||||
// It wasn't JSON. Unmarshal as YAML.
|
|
||||||
|
// Handle YAML content
|
||||||
if strict {
|
if strict {
|
||||||
return yaml.UnmarshalStrict(b, c)
|
// In strict mode, always use our custom handler to support YAML merge
|
||||||
|
return parseYAMLWithDotFieldsHandling(b, c)
|
||||||
}
|
}
|
||||||
|
// Non-strict mode, parse normally
|
||||||
return yaml.Unmarshal(b, c)
|
return yaml.Unmarshal(b, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +112,29 @@ func TestLoadServerConfigStrictMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderWithTemplate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"toml", tomlServerContent, tomlServerContent},
|
||||||
|
{"yaml", yamlServerContent, yamlServerContent},
|
||||||
|
{"json", jsonServerContent, jsonServerContent},
|
||||||
|
{"template numeric", `key = {{ 123 }}`, "key = 123"},
|
||||||
|
{"template string", `key = {{ "xyz" }}`, "key = xyz"},
|
||||||
|
{"template quote", `key = {{ printf "%q" "with space" }}`, `key = "with space"`},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
got, err := RenderWithTemplate([]byte(test.content), nil)
|
||||||
|
require.NoError(err)
|
||||||
|
require.EqualValues(test.want, string(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCustomStructStrictMode(t *testing.T) {
|
func TestCustomStructStrictMode(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
@ -164,3 +187,122 @@ unixPath = "/tmp/uds.sock"
|
|||||||
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestYAMLMergeInStrictMode tests that YAML merge functionality works
|
||||||
|
// even in strict mode by properly handling dot-prefixed fields
|
||||||
|
func TestYAMLMergeInStrictMode(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
yamlContent := `
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
serverPort: 7000
|
||||||
|
|
||||||
|
.common: &common
|
||||||
|
type: stcp
|
||||||
|
secretKey: "test-secret"
|
||||||
|
localIP: 127.0.0.1
|
||||||
|
transport:
|
||||||
|
useEncryption: true
|
||||||
|
useCompression: true
|
||||||
|
|
||||||
|
proxies:
|
||||||
|
- name: ssh
|
||||||
|
localPort: 22
|
||||||
|
<<: *common
|
||||||
|
- name: web
|
||||||
|
localPort: 80
|
||||||
|
<<: *common
|
||||||
|
`
|
||||||
|
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
// This should work in strict mode
|
||||||
|
err := LoadConfigure([]byte(yamlContent), &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Verify the merge worked correctly
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Equal(7000, clientCfg.ServerPort)
|
||||||
|
require.Len(clientCfg.Proxies, 2)
|
||||||
|
|
||||||
|
// Check first proxy
|
||||||
|
sshProxy := clientCfg.Proxies[0].ProxyConfigurer
|
||||||
|
require.Equal("ssh", sshProxy.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", sshProxy.GetBaseConfig().Type)
|
||||||
|
|
||||||
|
// Check second proxy
|
||||||
|
webProxy := clientCfg.Proxies[1].ProxyConfigurer
|
||||||
|
require.Equal("web", webProxy.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", webProxy.GetBaseConfig().Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOptimizedYAMLProcessing tests the optimization logic for YAML processing
|
||||||
|
func TestOptimizedYAMLProcessing(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
yamlWithDotFields := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
.common: &common
|
||||||
|
type: stcp
|
||||||
|
proxies:
|
||||||
|
- name: test
|
||||||
|
<<: *common
|
||||||
|
`)
|
||||||
|
|
||||||
|
yamlWithoutDotFields := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
proxies:
|
||||||
|
- name: test
|
||||||
|
type: tcp
|
||||||
|
localPort: 22
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Test that YAML without dot fields works in strict mode
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
err := LoadConfigure(yamlWithoutDotFields, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Len(clientCfg.Proxies, 1)
|
||||||
|
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
|
||||||
|
|
||||||
|
// Test that YAML with dot fields still works in strict mode
|
||||||
|
err = LoadConfigure(yamlWithDotFields, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Len(clientCfg.Proxies, 1)
|
||||||
|
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestYAMLEdgeCases tests edge cases for YAML parsing, including non-map types
|
||||||
|
func TestYAMLEdgeCases(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// Test array at root (should fail for frp config)
|
||||||
|
arrayYAML := []byte(`
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
`)
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
err := LoadConfigure(arrayYAML, &clientCfg, true)
|
||||||
|
require.Error(err) // Should fail because ClientConfig expects an object
|
||||||
|
|
||||||
|
// Test scalar at root (should fail for frp config)
|
||||||
|
scalarYAML := []byte(`"just a string"`)
|
||||||
|
err = LoadConfigure(scalarYAML, &clientCfg, true)
|
||||||
|
require.Error(err) // Should fail because ClientConfig expects an object
|
||||||
|
|
||||||
|
// Test empty object (should work)
|
||||||
|
emptyYAML := []byte(`{}`)
|
||||||
|
err = LoadConfigure(emptyYAML, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Test nested structure without dots (should work)
|
||||||
|
nestedYAML := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
serverPort: 7000
|
||||||
|
`)
|
||||||
|
err = LoadConfigure(nestedYAML, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Equal(7000, clientCfg.ServerPort)
|
||||||
|
}
|
||||||
|
52
pkg/config/template.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2024 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NumberPair struct {
|
||||||
|
First int64
|
||||||
|
Second int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]NumberPair, error) {
|
||||||
|
firstRangeNumbers, err := util.ParseRangeNumbers(firstRangeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secondRangeNumbers, err := util.ParseRangeNumbers(secondRangeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(firstRangeNumbers) != len(secondRangeNumbers) {
|
||||||
|
return nil, fmt.Errorf("first and second range numbers are not in pairs")
|
||||||
|
}
|
||||||
|
pairs := make([]NumberPair, 0, len(firstRangeNumbers))
|
||||||
|
for i := 0; i < len(firstRangeNumbers); i++ {
|
||||||
|
pairs = append(pairs, NumberPair{
|
||||||
|
First: firstRangeNumbers[i],
|
||||||
|
Second: secondRangeNumbers[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNumberRange(firstRangeStr string) ([]int64, error) {
|
||||||
|
return util.ParseRangeNumbers(firstRangeStr)
|
||||||
|
}
|
@ -45,15 +45,6 @@ func NewBandwidthQuantity(s string) (BandwidthQuantity, error) {
|
|||||||
return q, nil
|
return q, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustBandwidthQuantity(s string) BandwidthQuantity {
|
|
||||||
q := BandwidthQuantity{}
|
|
||||||
err := q.UnmarshalString(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
|
func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
|
||||||
if q == nil && u == nil {
|
if q == nil && u == nil {
|
||||||
return true
|
return true
|
||||||
@ -168,18 +159,18 @@ func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
|
|||||||
out = append(out, PortsRange{Single: int(singleNum)})
|
out = append(out, PortsRange{Single: int(singleNum)})
|
||||||
case 2:
|
case 2:
|
||||||
// range numbers
|
// range numbers
|
||||||
min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
minNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
}
|
}
|
||||||
max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
maxNum, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
}
|
}
|
||||||
if max < min {
|
if maxNum < minNum {
|
||||||
return nil, fmt.Errorf("range number is invalid")
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
}
|
}
|
||||||
out = append(out, PortsRange{Start: int(min), End: int(max)})
|
out = append(out, PortsRange{Start: int(minNum), End: int(maxNum)})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("range number is invalid")
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
}
|
}
|
||||||
|
@ -58,9 +58,14 @@ type ClientCommonConfig struct {
|
|||||||
// set.
|
// set.
|
||||||
Start []string `json:"start,omitempty"`
|
Start []string `json:"start,omitempty"`
|
||||||
|
|
||||||
Log LogConfig `json:"log,omitempty"`
|
Log LogConfig `json:"log,omitempty"`
|
||||||
WebServer WebServerConfig `json:"webServer,omitempty"`
|
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||||
Transport ClientTransportConfig `json:"transport,omitempty"`
|
Transport ClientTransportConfig `json:"transport,omitempty"`
|
||||||
|
VirtualNet VirtualNetConfig `json:"virtualNet,omitempty"`
|
||||||
|
|
||||||
|
// FeatureGates specifies a set of feature gates to enable or disable.
|
||||||
|
// This can be used to enable alpha/beta features or disable default features.
|
||||||
|
FeatureGates map[string]bool `json:"featureGates,omitempty"`
|
||||||
|
|
||||||
// UDPPacketSize specifies the udp packet size
|
// UDPPacketSize specifies the udp packet size
|
||||||
// By default, this value is 1500
|
// By default, this value is 1500
|
||||||
@ -135,9 +140,15 @@ func (c *ClientTransportConfig) Complete() {
|
|||||||
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
||||||
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
||||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 30)
|
||||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
if lo.FromPtr(c.TCPMux) {
|
||||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
||||||
|
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, -1)
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1)
|
||||||
|
} else {
|
||||||
|
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||||
|
}
|
||||||
c.QUIC.Complete()
|
c.QUIC.Complete()
|
||||||
c.TLS.Complete()
|
c.TLS.Complete()
|
||||||
}
|
}
|
||||||
@ -198,3 +209,7 @@ type AuthOIDCClientConfig struct {
|
|||||||
// this field will be transfer to map[string][]string in OIDC token generator.
|
// this field will be transfer to map[string][]string in OIDC token generator.
|
||||||
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
|
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualNetConfig struct {
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
}
|
||||||
|
@ -129,3 +129,8 @@ type HTTPPluginOptions struct {
|
|||||||
type HeaderOperations struct {
|
type HeaderOperations struct {
|
||||||
Set map[string]string `json:"set,omitempty"`
|
Set map[string]string `json:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTTPHeader struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
@ -97,6 +97,9 @@ type HealthCheckConfig struct {
|
|||||||
// Path specifies the path to send health checks to if the
|
// Path specifies the path to send health checks to if the
|
||||||
// health check type is "http".
|
// health check type is "http".
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
|
// HTTPHeaders specifies the headers to send with the health request, if
|
||||||
|
// the health check type is "http".
|
||||||
|
HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainConfig struct {
|
type DomainConfig struct {
|
||||||
@ -105,9 +108,10 @@ type DomainConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProxyBaseConfig struct {
|
type ProxyBaseConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Transport ProxyTransport `json:"transport,omitempty"`
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
Transport ProxyTransport `json:"transport,omitempty"`
|
||||||
// metadata info for each proxy
|
// metadata info for each proxy
|
||||||
Metadatas map[string]string `json:"metadatas,omitempty"`
|
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||||
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
|
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
|
||||||
@ -123,6 +127,10 @@ func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
|||||||
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
||||||
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
||||||
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||||
|
|
||||||
|
if c.Plugin.ClientPluginOptions != nil {
|
||||||
|
c.Plugin.Complete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
@ -138,6 +146,7 @@ func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
|||||||
m.Group = c.LoadBalancer.Group
|
m.Group = c.LoadBalancer.Group
|
||||||
m.GroupKey = c.LoadBalancer.GroupKey
|
m.GroupKey = c.LoadBalancer.GroupKey
|
||||||
m.Metas = c.Metadatas
|
m.Metas = c.Metadatas
|
||||||
|
m.Annotations = c.Annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
@ -154,6 +163,7 @@ func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
|||||||
c.LoadBalancer.Group = m.Group
|
c.LoadBalancer.Group = m.Group
|
||||||
c.LoadBalancer.GroupKey = m.GroupKey
|
c.LoadBalancer.GroupKey = m.GroupKey
|
||||||
c.Metadatas = m.Metas
|
c.Metadatas = m.Metas
|
||||||
|
c.Annotations = m.Annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
type TypedProxyConfig struct {
|
type TypedProxyConfig struct {
|
||||||
@ -183,19 +193,23 @@ func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
|
|||||||
decoder.DisallowUnknownFields()
|
decoder.DisallowUnknownFields()
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(configurer); err != nil {
|
if err := decoder.Decode(configurer); err != nil {
|
||||||
return err
|
return fmt.Errorf("unmarshal ProxyConfig error: %v", err)
|
||||||
}
|
}
|
||||||
c.ProxyConfigurer = configurer
|
c.ProxyConfigurer = configurer
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TypedProxyConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.ProxyConfigurer)
|
||||||
|
}
|
||||||
|
|
||||||
type ProxyConfigurer interface {
|
type ProxyConfigurer interface {
|
||||||
Complete(namePrefix string)
|
Complete(namePrefix string)
|
||||||
GetBaseConfig() *ProxyBaseConfig
|
GetBaseConfig() *ProxyBaseConfig
|
||||||
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
||||||
// function will be called on the frpc side.
|
// function will be called on the frpc side.
|
||||||
MarshalToMsg(*msg.NewProxy)
|
MarshalToMsg(*msg.NewProxy)
|
||||||
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
// UnmarshalFromMsg unmarshal a msg.NewProxy message into this config.
|
||||||
// This function will be called on the frps side.
|
// This function will be called on the frps side.
|
||||||
UnmarshalFromMsg(*msg.NewProxy)
|
UnmarshalFromMsg(*msg.NewProxy)
|
||||||
}
|
}
|
||||||
@ -285,6 +299,7 @@ type HTTPProxyConfig struct {
|
|||||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
ResponseHeaders HeaderOperations `json:"responseHeaders,omitempty"`
|
||||||
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,6 +313,7 @@ func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
|||||||
m.HTTPUser = c.HTTPUser
|
m.HTTPUser = c.HTTPUser
|
||||||
m.HTTPPwd = c.HTTPPassword
|
m.HTTPPwd = c.HTTPPassword
|
||||||
m.Headers = c.RequestHeaders.Set
|
m.Headers = c.RequestHeaders.Set
|
||||||
|
m.ResponseHeaders = c.ResponseHeaders.Set
|
||||||
m.RouteByHTTPUser = c.RouteByHTTPUser
|
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +327,7 @@ func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
|||||||
c.HTTPUser = m.HTTPUser
|
c.HTTPUser = m.HTTPUser
|
||||||
c.HTTPPassword = m.HTTPPwd
|
c.HTTPPassword = m.HTTPPwd
|
||||||
c.RequestHeaders.Set = m.Headers
|
c.RequestHeaders.Set = m.Headers
|
||||||
|
c.ResponseHeaders.Set = m.ResponseHeaders
|
||||||
c.RouteByHTTPUser = m.RouteByHTTPUser
|
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,11 +17,44 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientPluginOptions interface{}
|
const (
|
||||||
|
PluginHTTP2HTTPS = "http2https"
|
||||||
|
PluginHTTPProxy = "http_proxy"
|
||||||
|
PluginHTTPS2HTTP = "https2http"
|
||||||
|
PluginHTTPS2HTTPS = "https2https"
|
||||||
|
PluginHTTP2HTTP = "http2http"
|
||||||
|
PluginSocks5 = "socks5"
|
||||||
|
PluginStaticFile = "static_file"
|
||||||
|
PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
|
PluginTLS2Raw = "tls2raw"
|
||||||
|
PluginVirtualNet = "virtual_net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
|
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
|
||||||
|
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
||||||
|
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
||||||
|
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
||||||
|
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
||||||
|
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||||
|
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||||
|
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||||
|
PluginTLS2Raw: reflect.TypeOf(TLS2RawPluginOptions{}),
|
||||||
|
PluginVirtualNet: reflect.TypeOf(VirtualNetPluginOptions{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientPluginOptions interface {
|
||||||
|
Complete()
|
||||||
|
}
|
||||||
|
|
||||||
type TypedClientPluginOptions struct {
|
type TypedClientPluginOptions struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@ -42,7 +75,7 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
c.Type = typeStruct.Type
|
c.Type = typeStruct.Type
|
||||||
if c.Type == "" {
|
if c.Type == "" {
|
||||||
return nil
|
return errors.New("plugin type is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
||||||
@ -57,30 +90,14 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := decoder.Decode(options); err != nil {
|
if err := decoder.Decode(options); err != nil {
|
||||||
return err
|
return fmt.Errorf("unmarshal ClientPluginOptions error: %v", err)
|
||||||
}
|
}
|
||||||
c.ClientPluginOptions = options
|
c.ClientPluginOptions = options
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
|
||||||
PluginHTTP2HTTPS = "http2https"
|
return json.Marshal(c.ClientPluginOptions)
|
||||||
PluginHTTPProxy = "http_proxy"
|
|
||||||
PluginHTTPS2HTTP = "https2http"
|
|
||||||
PluginHTTPS2HTTPS = "https2https"
|
|
||||||
PluginSocks5 = "socks5"
|
|
||||||
PluginStaticFile = "static_file"
|
|
||||||
PluginUnixDomainSocket = "unix_domain_socket"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
|
||||||
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
|
|
||||||
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
|
||||||
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
|
||||||
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
|
||||||
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
|
||||||
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
|
||||||
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTP2HTTPSPluginOptions struct {
|
type HTTP2HTTPSPluginOptions struct {
|
||||||
@ -90,36 +107,61 @@ type HTTP2HTTPSPluginOptions struct {
|
|||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTP2HTTPSPluginOptions) Complete() {}
|
||||||
|
|
||||||
type HTTPProxyPluginOptions struct {
|
type HTTPProxyPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
HTTPUser string `json:"httpUser,omitempty"`
|
HTTPUser string `json:"httpUser,omitempty"`
|
||||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTPProxyPluginOptions) Complete() {}
|
||||||
|
|
||||||
type HTTPS2HTTPPluginOptions struct {
|
type HTTPS2HTTPPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTPS2HTTPPluginOptions) Complete() {
|
||||||
|
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
|
||||||
|
}
|
||||||
|
|
||||||
type HTTPS2HTTPSPluginOptions struct {
|
type HTTPS2HTTPSPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTPS2HTTPSPluginOptions) Complete() {
|
||||||
|
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTP2HTTPPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *HTTP2HTTPPluginOptions) Complete() {}
|
||||||
|
|
||||||
type Socks5PluginOptions struct {
|
type Socks5PluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Socks5PluginOptions) Complete() {}
|
||||||
|
|
||||||
type StaticFilePluginOptions struct {
|
type StaticFilePluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalPath string `json:"localPath,omitempty"`
|
LocalPath string `json:"localPath,omitempty"`
|
||||||
@ -128,7 +170,26 @@ type StaticFilePluginOptions struct {
|
|||||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *StaticFilePluginOptions) Complete() {}
|
||||||
|
|
||||||
type UnixDomainSocketPluginOptions struct {
|
type UnixDomainSocketPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
UnixPath string `json:"unixPath,omitempty"`
|
UnixPath string `json:"unixPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *UnixDomainSocketPluginOptions) Complete() {}
|
||||||
|
|
||||||
|
type TLS2RawPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *TLS2RawPluginOptions) Complete() {}
|
||||||
|
|
||||||
|
type VirtualNetPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *VirtualNetPluginOptions) Complete() {}
|
@ -176,10 +176,15 @@ type ServerTransportConfig struct {
|
|||||||
|
|
||||||
func (c *ServerTransportConfig) Complete() {
|
func (c *ServerTransportConfig) Complete() {
|
||||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 30)
|
||||||
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
||||||
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
||||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
if lo.FromPtr(c.TCPMux) {
|
||||||
|
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1)
|
||||||
|
} else {
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||||
|
}
|
||||||
c.QUIC.Complete()
|
c.QUIC.Complete()
|
||||||
if c.TLS.TrustedCaFile != "" {
|
if c.TLS.TrustedCaFile != "" {
|
||||||
c.TLS.Force = true
|
c.TLS.Force = true
|
||||||
|
@ -18,10 +18,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/featuregate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||||
@ -29,7 +31,14 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
|||||||
warnings Warning
|
warnings Warning
|
||||||
errs error
|
errs error
|
||||||
)
|
)
|
||||||
if !lo.Contains(SupportedAuthMethods, c.Auth.Method) {
|
// validate feature gates
|
||||||
|
if c.VirtualNet.Address != "" {
|
||||||
|
if !featuregate.Enabled(featuregate.VirtualNet) {
|
||||||
|
return warnings, fmt.Errorf("VirtualNet feature is not enabled; enable it by setting the appropriate feature gate flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Contains(SupportedAuthMethods, c.Auth.Method) {
|
||||||
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
||||||
}
|
}
|
||||||
if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) {
|
if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) {
|
||||||
@ -63,7 +72,7 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
|||||||
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.Transport.TLS.TrustedCaFile))
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.Transport.TLS.TrustedCaFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lo.Contains(SupportedTransportProtocols, c.Transport.Protocol) {
|
if !slices.Contains(SupportedTransportProtocols, c.Transport.Protocol) {
|
||||||
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, optional values are %v", SupportedTransportProtocols))
|
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, optional values are %v", SupportedTransportProtocols))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,7 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
)
|
)
|
||||||
@ -44,7 +43,7 @@ func ValidatePort(port int, fieldPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateLogConfig(c *v1.LogConfig) error {
|
func validateLogConfig(c *v1.LogConfig) error {
|
||||||
if !lo.Contains(SupportedLogLevels, c.Level) {
|
if !slices.Contains(SupportedLogLevels, c.Level) {
|
||||||
return fmt.Errorf("invalid log level, optional values are %v", SupportedLogLevels)
|
return fmt.Errorf("invalid log level, optional values are %v", SupportedLogLevels)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -32,6 +32,8 @@ func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
|
|||||||
return validateStaticFilePluginOptions(v)
|
return validateStaticFilePluginOptions(v)
|
||||||
case *v1.UnixDomainSocketPluginOptions:
|
case *v1.UnixDomainSocketPluginOptions:
|
||||||
return validateUnixDomainSocketPluginOptions(v)
|
return validateUnixDomainSocketPluginOptions(v)
|
||||||
|
case *v1.TLS2RawPluginOptions:
|
||||||
|
return validateTLS2RawPluginOptions(v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -70,3 +72,10 @@ func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions)
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateTLS2RawPluginOptions(c *v1.TLS2RawPluginOptions) error {
|
||||||
|
if c.LocalAddr == "" {
|
||||||
|
return errors.New("localAddr is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,9 +17,10 @@ package validation
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
)
|
)
|
||||||
@ -29,11 +30,13 @@ func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
|||||||
return errors.New("name should not be empty")
|
return errors.New("name should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lo.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
|
if err := ValidateAnnotations(c.Annotations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !slices.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
|
||||||
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
|
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
|
||||||
}
|
}
|
||||||
|
if !slices.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
|
||||||
if !lo.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
|
|
||||||
return fmt.Errorf("bandwidth limit mode should be client or server")
|
return fmt.Errorf("bandwidth limit mode should be client or server")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +46,7 @@ func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lo.Contains([]string{"", "tcp", "http"}, c.HealthCheck.Type) {
|
if !slices.Contains([]string{"", "tcp", "http"}, c.HealthCheck.Type) {
|
||||||
return fmt.Errorf("not support health check type: %s", c.HealthCheck.Type)
|
return fmt.Errorf("not support health check type: %s", c.HealthCheck.Type)
|
||||||
}
|
}
|
||||||
if c.HealthCheck.Type != "" {
|
if c.HealthCheck.Type != "" {
|
||||||
@ -61,7 +64,10 @@ func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig, s *v1.ServerConfig) error {
|
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig) error {
|
||||||
|
if err := ValidateAnnotations(c.Annotations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +139,7 @@ func validateTCPMuxProxyConfigForClient(c *v1.TCPMuxProxyConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lo.Contains([]string{string(v1.TCPMultiplexerHTTPConnect)}, c.Multiplexer) {
|
if !slices.Contains([]string{string(v1.TCPMultiplexerHTTPConnect)}, c.Multiplexer) {
|
||||||
return fmt.Errorf("not support multiplexer: %s", c.Multiplexer)
|
return fmt.Errorf("not support multiplexer: %s", c.Multiplexer)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -161,7 +167,7 @@ func validateSUDPProxyConfigForClient(c *v1.SUDPProxyConfig) error {
|
|||||||
|
|
||||||
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
|
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
|
||||||
base := c.GetBaseConfig()
|
base := c.GetBaseConfig()
|
||||||
if err := validateProxyBaseConfigForServer(base, s); err != nil {
|
if err := validateProxyBaseConfigForServer(base); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,3 +237,34 @@ func validateXTCPProxyConfigForServer(c *v1.XTCPProxyConfig, s *v1.ServerConfig)
|
|||||||
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
|
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateAnnotations validates that a set of annotations are correctly defined.
|
||||||
|
func ValidateAnnotations(annotations map[string]string) error {
|
||||||
|
if len(annotations) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs error
|
||||||
|
for k := range annotations {
|
||||||
|
for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("annotation key %s is invalid: %s", k, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ValidateAnnotationsSize(annotations); err != nil {
|
||||||
|
errs = AppendError(errs, err)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
|
||||||
|
|
||||||
|
func ValidateAnnotationsSize(annotations map[string]string) error {
|
||||||
|
var totalSize int64
|
||||||
|
for k, v := range annotations {
|
||||||
|
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||||
|
}
|
||||||
|
if totalSize > (int64)(TotalAnnotationSizeLimitB) {
|
||||||
|
return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
|||||||
warnings Warning
|
warnings Warning
|
||||||
errs error
|
errs error
|
||||||
)
|
)
|
||||||
if !lo.Contains(SupportedAuthMethods, c.Auth.Method) {
|
if !slices.Contains(SupportedAuthMethods, c.Auth.Method) {
|
||||||
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
||||||
}
|
}
|
||||||
if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) {
|
if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) {
|
||||||
|
@ -17,8 +17,7 @@ package validation
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
)
|
)
|
||||||
@ -56,7 +55,7 @@ func validateVisitorBaseConfig(c *v1.VisitorBaseConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error {
|
func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error {
|
||||||
if !lo.Contains([]string{"kcp", "quic"}, c.Protocol) {
|
if !slices.Contains([]string{"kcp", "quic"}, c.Protocol) {
|
||||||
return fmt.Errorf("protocol should be kcp or quic")
|
return fmt.Errorf("protocol should be kcp or quic")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -44,6 +44,9 @@ type VisitorBaseConfig struct {
|
|||||||
// It can be less than 0, it means don't bind to the port and only receive connections redirected from
|
// It 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)
|
// other visitors. (This is not supported for SUDP now)
|
||||||
BindPort int `json:"bindPort,omitempty"`
|
BindPort int `json:"bindPort,omitempty"`
|
||||||
|
|
||||||
|
// Plugin specifies what plugin should be used.
|
||||||
|
Plugin TypedVisitorPluginOptions `json:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
|
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
|
||||||
@ -114,12 +117,16 @@ func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
|
|||||||
decoder.DisallowUnknownFields()
|
decoder.DisallowUnknownFields()
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(configurer); err != nil {
|
if err := decoder.Decode(configurer); err != nil {
|
||||||
return err
|
return fmt.Errorf("unmarshal VisitorConfig error: %v", err)
|
||||||
}
|
}
|
||||||
c.VisitorConfigurer = configurer
|
c.VisitorConfigurer = configurer
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TypedVisitorConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.VisitorConfigurer)
|
||||||
|
}
|
||||||
|
|
||||||
func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
|
func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
|
||||||
v, ok := visitorConfigTypeMap[t]
|
v, ok := visitorConfigTypeMap[t]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
86
pkg/config/v1/visitor_plugin.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2025 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VisitorPluginVirtualNet = "virtual_net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var visitorPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
|
VisitorPluginVirtualNet: reflect.TypeOf(VirtualNetVisitorPluginOptions{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
type VisitorPluginOptions interface {
|
||||||
|
Complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypedVisitorPluginOptions struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
VisitorPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypedVisitorPluginOptions) UnmarshalJSON(b []byte) error {
|
||||||
|
if len(b) == 4 && string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
typeStruct := struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(b, &typeStruct); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Type = typeStruct.Type
|
||||||
|
if c.Type == "" {
|
||||||
|
return errors.New("visitor plugin type is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := visitorPluginOptionsTypeMap[typeStruct.Type]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown visitor plugin type: %s", typeStruct.Type)
|
||||||
|
}
|
||||||
|
options := reflect.New(v).Interface().(VisitorPluginOptions)
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(bytes.NewBuffer(b))
|
||||||
|
if DisallowUnknownFields {
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(options); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal VisitorPluginOptions error: %v", err)
|
||||||
|
}
|
||||||
|
c.VisitorPluginOptions = options
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypedVisitorPluginOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.VisitorPluginOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualNetVisitorPluginOptions struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
DestinationIP string `json:"destinationIP"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *VirtualNetVisitorPluginOptions) Complete() {}
|
219
pkg/featuregate/feature_gate.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// Copyright 2025 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 featuregate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Feature represents a feature gate name
|
||||||
|
type Feature string
|
||||||
|
|
||||||
|
// FeatureStage represents the maturity level of a feature
|
||||||
|
type FeatureStage string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Alpha means the feature is experimental and disabled by default
|
||||||
|
Alpha FeatureStage = "ALPHA"
|
||||||
|
// Beta means the feature is more stable but still might change and is disabled by default
|
||||||
|
Beta FeatureStage = "BETA"
|
||||||
|
// GA means the feature is generally available and enabled by default
|
||||||
|
GA FeatureStage = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// FeatureSpec describes a feature and its properties
|
||||||
|
type FeatureSpec struct {
|
||||||
|
// Default is the default enablement state for the feature
|
||||||
|
Default bool
|
||||||
|
// LockToDefault indicates the feature cannot be changed from its default
|
||||||
|
LockToDefault bool
|
||||||
|
// Stage indicates the maturity level of the feature
|
||||||
|
Stage FeatureStage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define all available features here
|
||||||
|
var (
|
||||||
|
VirtualNet = Feature("VirtualNet")
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultFeatures defines default features with their specifications
|
||||||
|
var defaultFeatures = map[Feature]FeatureSpec{
|
||||||
|
// Actual features
|
||||||
|
VirtualNet: {Default: false, Stage: Alpha},
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeatureGate indicates whether a given feature is enabled or not
|
||||||
|
type FeatureGate interface {
|
||||||
|
// Enabled returns true if the key is enabled
|
||||||
|
Enabled(key Feature) bool
|
||||||
|
// KnownFeatures returns a slice of strings describing the known features
|
||||||
|
KnownFeatures() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutableFeatureGate allows for dynamic feature gate configuration
|
||||||
|
type MutableFeatureGate interface {
|
||||||
|
FeatureGate
|
||||||
|
|
||||||
|
// SetFromMap sets feature gate values from a map[string]bool
|
||||||
|
SetFromMap(m map[string]bool) error
|
||||||
|
// Add adds features to the feature gate
|
||||||
|
Add(features map[Feature]FeatureSpec) error
|
||||||
|
// String returns a string representing the feature gate configuration
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// featureGate implements the FeatureGate and MutableFeatureGate interfaces
|
||||||
|
type featureGate struct {
|
||||||
|
// lock guards writes to known, enabled, and reads/writes of closed
|
||||||
|
lock sync.Mutex
|
||||||
|
// known holds a map[Feature]FeatureSpec
|
||||||
|
known atomic.Value
|
||||||
|
// enabled holds a map[Feature]bool
|
||||||
|
enabled atomic.Value
|
||||||
|
// closed is set to true once the feature gates are considered immutable
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFeatureGate creates a new feature gate with the default features
|
||||||
|
func NewFeatureGate() MutableFeatureGate {
|
||||||
|
known := map[Feature]FeatureSpec{}
|
||||||
|
for k, v := range defaultFeatures {
|
||||||
|
known[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &featureGate{}
|
||||||
|
f.known.Store(known)
|
||||||
|
f.enabled.Store(map[Feature]bool{})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFromMap sets feature gate values from a map[string]bool
|
||||||
|
func (f *featureGate) SetFromMap(m map[string]bool) error {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
// Copy existing state
|
||||||
|
known := map[Feature]FeatureSpec{}
|
||||||
|
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||||
|
known[k] = v
|
||||||
|
}
|
||||||
|
enabled := map[Feature]bool{}
|
||||||
|
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||||
|
enabled[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the new settings
|
||||||
|
for k, v := range m {
|
||||||
|
k := Feature(k)
|
||||||
|
featureSpec, ok := known[k]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unrecognized feature gate: %s", k)
|
||||||
|
}
|
||||||
|
if featureSpec.LockToDefault && featureSpec.Default != v {
|
||||||
|
return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)
|
||||||
|
}
|
||||||
|
enabled[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the changes
|
||||||
|
f.known.Store(known)
|
||||||
|
f.enabled.Store(enabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds features to the feature gate
|
||||||
|
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
if f.closed {
|
||||||
|
return fmt.Errorf("cannot add feature gates after the feature gate is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy existing state
|
||||||
|
known := map[Feature]FeatureSpec{}
|
||||||
|
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||||
|
known[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new features
|
||||||
|
for name, spec := range features {
|
||||||
|
if existingSpec, found := known[name]; found {
|
||||||
|
if existingSpec == spec {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
|
||||||
|
}
|
||||||
|
known[name] = spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist changes
|
||||||
|
f.known.Store(known)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,..."
|
||||||
|
func (f *featureGate) String() string {
|
||||||
|
pairs := []string{}
|
||||||
|
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(pairs)
|
||||||
|
return strings.Join(pairs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled returns true if the key is enabled
|
||||||
|
func (f *featureGate) Enabled(key Feature) bool {
|
||||||
|
if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := f.known.Load().(map[Feature]FeatureSpec)[key]; ok {
|
||||||
|
return v.Default
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// KnownFeatures returns a slice of strings describing the FeatureGate's known features
|
||||||
|
// GA features are hidden from the list
|
||||||
|
func (f *featureGate) KnownFeatures() []string {
|
||||||
|
knownFeatures := f.known.Load().(map[Feature]FeatureSpec)
|
||||||
|
known := make([]string, 0, len(knownFeatures))
|
||||||
|
for k, v := range knownFeatures {
|
||||||
|
if v.Stage == GA {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.Stage, v.Default))
|
||||||
|
}
|
||||||
|
sort.Strings(known)
|
||||||
|
return known
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default feature gates instance
|
||||||
|
var DefaultFeatureGates = NewFeatureGate()
|
||||||
|
|
||||||
|
// Enabled checks if a feature is enabled in the default feature gates
|
||||||
|
func Enabled(name Feature) bool {
|
||||||
|
return DefaultFeatureGates.Enabled(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFromMap sets feature gate values from a map in the default feature gates
|
||||||
|
func SetFromMap(featureMap map[string]bool) error {
|
||||||
|
return DefaultFeatureGates.SetFromMap(featureMap)
|
||||||
|
}
|
@ -61,31 +61,35 @@ func (m *serverMetrics) run() {
|
|||||||
for {
|
for {
|
||||||
time.Sleep(12 * time.Hour)
|
time.Sleep(12 * time.Hour)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
count, total := m.clearUselessInfo()
|
count, total := m.clearUselessInfo(time.Duration(7*24) * time.Hour)
|
||||||
log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
|
log.Debugf("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) clearUselessInfo() (int, int) {
|
func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration time.Duration) (int, int) {
|
||||||
count := 0
|
count := 0
|
||||||
total := 0
|
total := 0
|
||||||
// To check if there are proxies that closed than 7 days and drop them.
|
// To check if there are any proxies that have been closed for more than continuousOfflineDuration and remove them.
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
total = len(m.info.ProxyStatistics)
|
total = len(m.info.ProxyStatistics)
|
||||||
for name, data := range m.info.ProxyStatistics {
|
for name, data := range m.info.ProxyStatistics {
|
||||||
if !data.LastCloseTime.IsZero() &&
|
if !data.LastCloseTime.IsZero() &&
|
||||||
data.LastStartTime.Before(data.LastCloseTime) &&
|
data.LastStartTime.Before(data.LastCloseTime) &&
|
||||||
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
time.Since(data.LastCloseTime) > continuousOfflineDuration {
|
||||||
delete(m.info.ProxyStatistics, name)
|
delete(m.info.ProxyStatistics, name)
|
||||||
count++
|
count++
|
||||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
log.Tracef("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count, total
|
return count, total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) ClearOfflineProxies() (int, int) {
|
||||||
|
return m.clearUselessInfo(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) NewClient() {
|
func (m *serverMetrics) NewClient() {
|
||||||
m.info.ClientCounts.Inc(1)
|
m.info.ClientCounts.Inc(1)
|
||||||
}
|
}
|
||||||
@ -105,7 +109,7 @@ func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
|||||||
m.info.ProxyTypeCounts[proxyType] = counter
|
m.info.ProxyTypeCounts[proxyType] = counter
|
||||||
|
|
||||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
if !(ok && proxyStats.ProxyType == proxyType) {
|
if !ok || proxyStats.ProxyType != proxyType {
|
||||||
proxyStats = &ProxyStatistics{
|
proxyStats = &ProxyStatistics{
|
||||||
Name: name,
|
Name: name,
|
||||||
ProxyType: proxyType,
|
ProxyType: proxyType,
|
||||||
|
@ -79,4 +79,5 @@ type Collector interface {
|
|||||||
GetProxiesByType(proxyType string) []*ProxyStats
|
GetProxiesByType(proxyType string) []*ProxyStats
|
||||||
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
||||||
GetProxyTraffic(name string) *ProxyTrafficInfo
|
GetProxyTraffic(name string) *ProxyTrafficInfo
|
||||||
|
ClearOfflineProxies() (int, int)
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,6 @@ func ReadMsgInto(c io.Reader, msg Message) (err error) {
|
|||||||
return msgCtl.ReadMsgInto(c, msg)
|
return msgCtl.ReadMsgInto(c, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteMsg(c io.Writer, msg interface{}) (err error) {
|
func WriteMsg(c io.Writer, msg any) (err error) {
|
||||||
return msgCtl.WriteMsg(c, msg)
|
return msgCtl.WriteMsg(c, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pack(msg interface{}) (data []byte, err error) {
|
|
||||||
return msgCtl.Pack(msg)
|
|
||||||
}
|
|
||||||
|
@ -40,7 +40,7 @@ const (
|
|||||||
TypeNatHoleReport = '6'
|
TypeNatHoleReport = '6'
|
||||||
)
|
)
|
||||||
|
|
||||||
var msgTypeMap = map[byte]interface{}{
|
var msgTypeMap = map[byte]any{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
@ -108,6 +108,7 @@ type NewProxy struct {
|
|||||||
Group string `json:"group,omitempty"`
|
Group string `json:"group,omitempty"`
|
||||||
GroupKey string `json:"group_key,omitempty"`
|
GroupKey string `json:"group_key,omitempty"`
|
||||||
Metas map[string]string `json:"metas,omitempty"`
|
Metas map[string]string `json:"metas,omitempty"`
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
|
||||||
// tcp and udp only
|
// tcp and udp only
|
||||||
RemotePort int `json:"remote_port,omitempty"`
|
RemotePort int `json:"remote_port,omitempty"`
|
||||||
@ -120,6 +121,7 @@ type NewProxy struct {
|
|||||||
HTTPPwd string `json:"http_pwd,omitempty"`
|
HTTPPwd string `json:"http_pwd,omitempty"`
|
||||||
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
|
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
|
||||||
Headers map[string]string `json:"headers,omitempty"`
|
Headers map[string]string `json:"headers,omitempty"`
|
||||||
|
ResponseHeaders map[string]string `json:"response_headers,omitempty"`
|
||||||
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
|
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
|
||||||
|
|
||||||
// stcp, sudp, xtcp
|
// stcp, sudp, xtcp
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package nathole
|
package nathole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -224,7 +226,7 @@ func (mhr *MakeHoleRecords) ReportSuccess(mode int, index int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
score.Score += 2
|
score.Score += 2
|
||||||
score.Score = lo.Min([]int{score.Score, 10})
|
score.Score = min(score.Score, 10)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,12 +235,12 @@ func (mhr *MakeHoleRecords) Recommand() (mode, index int) {
|
|||||||
mhr.mu.Lock()
|
mhr.mu.Lock()
|
||||||
defer mhr.mu.Unlock()
|
defer mhr.mu.Unlock()
|
||||||
|
|
||||||
maxScore := lo.MaxBy(mhr.scores, func(item, max *BehaviorScore) bool {
|
if len(mhr.scores) == 0 {
|
||||||
return item.Score > max.Score
|
|
||||||
})
|
|
||||||
if maxScore == nil {
|
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
maxScore := slices.MaxFunc(mhr.scores, func(a, b *BehaviorScore) int {
|
||||||
|
return cmp.Compare(a.Score, b.Score)
|
||||||
|
})
|
||||||
maxScore.Score--
|
maxScore.Score--
|
||||||
mhr.LastUpdateTime = time.Now()
|
mhr.LastUpdateTime = time.Now()
|
||||||
return maxScore.Mode, maxScore.Index
|
return maxScore.Mode, maxScore.Index
|
||||||
|
@ -17,9 +17,8 @@ package nathole
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -59,7 +58,7 @@ func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeature, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if lo.Contains(localIPs, ip) {
|
if slices.Contains(localIPs, ip) {
|
||||||
natFeature.PublicNetwork = true
|
natFeature.PublicNetwork = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -72,7 +73,7 @@ type Session struct {
|
|||||||
|
|
||||||
func (s *Session) genAnalysisKey() {
|
func (s *Session) genAnalysisKey() {
|
||||||
hash := md5.New()
|
hash := md5.New()
|
||||||
vIPs := lo.Uniq(parseIPs(s.visitorMsg.MappedAddrs))
|
vIPs := slices.Compact(parseIPs(s.visitorMsg.MappedAddrs))
|
||||||
if len(vIPs) > 0 {
|
if len(vIPs) > 0 {
|
||||||
hash.Write([]byte(vIPs[0]))
|
hash.Write([]byte(vIPs[0]))
|
||||||
}
|
}
|
||||||
@ -80,7 +81,7 @@ func (s *Session) genAnalysisKey() {
|
|||||||
hash.Write([]byte(s.vNatFeature.Behavior))
|
hash.Write([]byte(s.vNatFeature.Behavior))
|
||||||
hash.Write([]byte(strconv.FormatBool(s.vNatFeature.RegularPortsChange)))
|
hash.Write([]byte(strconv.FormatBool(s.vNatFeature.RegularPortsChange)))
|
||||||
|
|
||||||
cIPs := lo.Uniq(parseIPs(s.clientMsg.MappedAddrs))
|
cIPs := slices.Compact(parseIPs(s.clientMsg.MappedAddrs))
|
||||||
if len(cIPs) > 0 {
|
if len(cIPs) > 0 {
|
||||||
hash.Write([]byte(cIPs[0]))
|
hash.Write([]byte(cIPs[0]))
|
||||||
}
|
}
|
||||||
@ -114,7 +115,7 @@ func (c *Controller) CleanWorker(ctx context.Context) {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
count, total := c.analyzer.Clean()
|
count, total := c.analyzer.Clean()
|
||||||
log.Trace("clean %d/%d nathole analysis data, cost %v", count, total, time.Since(start))
|
log.Tracef("clean %d/%d nathole analysis data, cost %v", count, total, time.Since(start))
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -156,7 +157,7 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.
|
|||||||
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)))
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !lo.Contains(cfg.allowUsers, visitorUser) && !lo.Contains(cfg.allowUsers, "*") {
|
if !slices.Contains(cfg.allowUsers, visitorUser) && !slices.Contains(cfg.allowUsers, "*") {
|
||||||
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp visitor user [%s] not allowed for [%s]", visitorUser, m.ProxyName)))
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp visitor user [%s] not allowed for [%s]", visitorUser, m.ProxyName)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -190,11 +191,11 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.
|
|||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("handle visitorMsg error: %v", err)
|
log.Warnf("handle visitorMsg error: %v", err)
|
||||||
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Trace("handle visitor message, sid [%s], server name: %s", sid, m.ProxyName)
|
log.Tracef("handle visitor message, sid [%s], server name: %s", sid, m.ProxyName)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@ -212,14 +213,14 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.
|
|||||||
select {
|
select {
|
||||||
case <-session.notifyCh:
|
case <-session.notifyCh:
|
||||||
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||||
log.Debug("wait for NatHoleClient message timeout, sid [%s]", sid)
|
log.Debugf("wait for NatHoleClient message timeout, sid [%s]", sid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make hole-punching decisions based on the NAT information of the client and visitor.
|
// Make hole-punching decisions based on the NAT information of the client and visitor.
|
||||||
vResp, cResp, err := c.analysis(session)
|
vResp, cResp, err := c.analysis(session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("sid [%s] analysis error: %v", err)
|
log.Debugf("sid [%s] analysis error: %v", err)
|
||||||
vResp = c.GenNatHoleResponse(session.visitorMsg.TransactionID, nil, err.Error())
|
vResp = c.GenNatHoleResponse(session.visitorMsg.TransactionID, nil, err.Error())
|
||||||
cResp = c.GenNatHoleResponse(session.clientMsg.TransactionID, nil, err.Error())
|
cResp = c.GenNatHoleResponse(session.clientMsg.TransactionID, nil, err.Error())
|
||||||
}
|
}
|
||||||
@ -256,7 +257,7 @@ func (c *Controller) HandleClient(m *msg.NatHoleClient, transporter transport.Me
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Trace("handle client message, sid [%s], server name: %s", session.sid, m.ProxyName)
|
log.Tracef("handle client message, sid [%s], server name: %s", session.sid, m.ProxyName)
|
||||||
session.clientMsg = m
|
session.clientMsg = m
|
||||||
session.clientTransporter = transporter
|
session.clientTransporter = transporter
|
||||||
select {
|
select {
|
||||||
@ -270,13 +271,13 @@ func (c *Controller) HandleReport(m *msg.NatHoleReport) {
|
|||||||
session, ok := c.sessions[m.Sid]
|
session, ok := c.sessions[m.Sid]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Trace("sid [%s] report make hole success: %v, but session not found", m.Sid, m.Success)
|
log.Tracef("sid [%s] report make hole success: %v, but session not found", m.Sid, m.Success)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if m.Success {
|
if m.Success {
|
||||||
c.analyzer.ReportSuccess(session.analysisKey, session.recommandMode, session.recommandIndex)
|
c.analyzer.ReportSuccess(session.analysisKey, session.recommandMode, session.recommandIndex)
|
||||||
}
|
}
|
||||||
log.Info("sid [%s] report make hole success: %v, mode %v, index %v",
|
log.Infof("sid [%s] report make hole success: %v, mode %v, index %v",
|
||||||
m.Sid, m.Success, session.recommandMode, session.recommandIndex)
|
m.Sid, m.Success, session.recommandMode, session.recommandIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +318,7 @@ func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleR
|
|||||||
session.cBehavior = cBehavior
|
session.cBehavior = cBehavior
|
||||||
session.vBehavior = vBehavior
|
session.vBehavior = vBehavior
|
||||||
|
|
||||||
timeoutMs := lo.Max([]int{cBehavior.SendDelayMs, vBehavior.SendDelayMs}) + 5000
|
timeoutMs := max(cBehavior.SendDelayMs, vBehavior.SendDelayMs) + 5000
|
||||||
if cBehavior.ListenRandomPorts > 0 || vBehavior.ListenRandomPorts > 0 {
|
if cBehavior.ListenRandomPorts > 0 || vBehavior.ListenRandomPorts > 0 {
|
||||||
timeoutMs += 30000
|
timeoutMs += 30000
|
||||||
}
|
}
|
||||||
@ -327,8 +328,8 @@ func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleR
|
|||||||
TransactionID: vm.TransactionID,
|
TransactionID: vm.TransactionID,
|
||||||
Sid: session.sid,
|
Sid: session.sid,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
CandidateAddrs: lo.Uniq(cm.MappedAddrs),
|
CandidateAddrs: slices.Compact(cm.MappedAddrs),
|
||||||
AssistedAddrs: lo.Uniq(cm.AssistedAddrs),
|
AssistedAddrs: slices.Compact(cm.AssistedAddrs),
|
||||||
DetectBehavior: msg.NatHoleDetectBehavior{
|
DetectBehavior: msg.NatHoleDetectBehavior{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Role: vBehavior.Role,
|
Role: vBehavior.Role,
|
||||||
@ -344,8 +345,8 @@ func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleR
|
|||||||
TransactionID: cm.TransactionID,
|
TransactionID: cm.TransactionID,
|
||||||
Sid: session.sid,
|
Sid: session.sid,
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
CandidateAddrs: lo.Uniq(vm.MappedAddrs),
|
CandidateAddrs: slices.Compact(vm.MappedAddrs),
|
||||||
AssistedAddrs: lo.Uniq(vm.AssistedAddrs),
|
AssistedAddrs: slices.Compact(vm.AssistedAddrs),
|
||||||
DetectBehavior: msg.NatHoleDetectBehavior{
|
DetectBehavior: msg.NatHoleDetectBehavior{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Role: cBehavior.Role,
|
Role: cBehavior.Role,
|
||||||
@ -358,10 +359,10 @@ func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleR
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("sid [%s] visitor nat: %+v, candidateAddrs: %v; client nat: %+v, candidateAddrs: %v, protocol: %s",
|
log.Debugf("sid [%s] visitor nat: %+v, candidateAddrs: %v; client nat: %+v, candidateAddrs: %v, protocol: %s",
|
||||||
session.sid, *vNatFeature, vm.MappedAddrs, *cNatFeature, cm.MappedAddrs, protocol)
|
session.sid, *vNatFeature, vm.MappedAddrs, *cNatFeature, cm.MappedAddrs, protocol)
|
||||||
log.Debug("sid [%s] visitor detect behavior: %+v", session.sid, vResp.DetectBehavior)
|
log.Debugf("sid [%s] visitor detect behavior: %+v", session.sid, vResp.DetectBehavior)
|
||||||
log.Debug("sid [%s] client detect behavior: %+v", session.sid, cResp.DetectBehavior)
|
log.Debugf("sid [%s] client detect behavior: %+v", session.sid, cResp.DetectBehavior)
|
||||||
return vResp, cResp, nil
|
return vResp, cResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,8 +371,8 @@ func getRangePorts(addrs []string, difference, maxNumber int) []msg.PortsRange {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := lo.Last(addrs)
|
addr, isLast := lo.Last(addrs)
|
||||||
if err != nil {
|
if !isLast {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var ports []msg.PortsRange
|
var ports []msg.PortsRange
|
||||||
@ -384,8 +385,8 @@ func getRangePorts(addrs []string, difference, maxNumber int) []msg.PortsRange {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ports = append(ports, msg.PortsRange{
|
ports = append(ports, msg.PortsRange{
|
||||||
From: lo.Max([]int{port - difference - 5, port - maxNumber, 1}),
|
From: max(port-difference-5, port-maxNumber, 1),
|
||||||
To: lo.Min([]int{port + difference + 5, port + maxNumber, 65535}),
|
To: min(port+difference+5, port+maxNumber, 65535),
|
||||||
})
|
})
|
||||||
return ports
|
return ports
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/stun"
|
"github.com/pion/stun/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var responseTimeout = 3 * time.Second
|
var responseTimeout = 3 * time.Second
|
||||||
|
@ -17,14 +17,14 @@ package nathole
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
"github.com/samber/lo"
|
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp,
|
|||||||
for i := 0; i < m.DetectBehavior.ListenRandomPorts; i++ {
|
for i := 0; i < m.DetectBehavior.ListenRandomPorts; i++ {
|
||||||
tmpConn, err := net.ListenUDP("udp4", nil)
|
tmpConn, err := net.ListenUDP("udp4", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("listen random udp addr error: %v", err)
|
xl.Warnf("listen random udp addr error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
listenConns = append(listenConns, tmpConn)
|
listenConns = append(listenConns, tmpConn)
|
||||||
@ -212,11 +212,11 @@ func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detectAddrs = lo.Uniq(detectAddrs)
|
detectAddrs = slices.Compact(detectAddrs)
|
||||||
for _, detectAddr := range detectAddrs {
|
for _, detectAddr := range detectAddrs {
|
||||||
for _, conn := range listenConns {
|
for _, conn := range listenConns {
|
||||||
if err := sendSidMessage(ctx, conn, m.Sid, transactionID, detectAddr, key, m.DetectBehavior.TTL); err != nil {
|
if err := sendSidMessage(ctx, conn, m.Sid, transactionID, detectAddr, key, m.DetectBehavior.TTL); err != nil {
|
||||||
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
xl.Tracef("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,16 +289,16 @@ func waitDetectMessage(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
xl.Debug("get udp message local %s, from %s", conn.LocalAddr(), raddr)
|
xl.Debugf("get udp message local %s, from %s", conn.LocalAddr(), raddr)
|
||||||
var m msg.NatHoleSid
|
var m msg.NatHoleSid
|
||||||
if err := DecodeMessageInto(buf[:n], key, &m); err != nil {
|
if err := DecodeMessageInto(buf[:n], key, &m); err != nil {
|
||||||
xl.Warn("decode sid message error: %v", err)
|
xl.Warnf("decode sid message error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pool.PutBuf(buf)
|
pool.PutBuf(buf)
|
||||||
|
|
||||||
if m.Sid != sid {
|
if m.Sid != sid {
|
||||||
xl.Warn("get sid message with wrong sid: %s, expect: %s", m.Sid, sid)
|
xl.Warnf("get sid message with wrong sid: %s, expect: %s", m.Sid, sid)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,7 +311,7 @@ func waitDetectMessage(
|
|||||||
m.Response = true
|
m.Response = true
|
||||||
buf2, err := EncodeMessage(&m, key)
|
buf2, err := EncodeMessage(&m, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("encode sid message error: %v", err)
|
xl.Warnf("encode sid message error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, _ = conn.WriteToUDP(buf2, raddr)
|
_, _ = conn.WriteToUDP(buf2, raddr)
|
||||||
@ -329,7 +329,7 @@ func sendSidMessage(
|
|||||||
if ttl > 0 {
|
if ttl > 0 {
|
||||||
ttlStr = fmt.Sprintf(" with ttl %d", ttl)
|
ttlStr = fmt.Sprintf(" with ttl %d", ttl)
|
||||||
}
|
}
|
||||||
xl.Trace("send sid message from %s to %s%s", conn.LocalAddr(), addr, ttlStr)
|
xl.Tracef("send sid message from %s to %s%s", conn.LocalAddr(), addr, ttlStr)
|
||||||
raddr, err := net.ResolveUDPAddr("udp4", addr)
|
raddr, err := net.ResolveUDPAddr("udp4", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -341,7 +341,7 @@ func sendSidMessage(
|
|||||||
TransactionID: transactionID,
|
TransactionID: transactionID,
|
||||||
Sid: sid,
|
Sid: sid,
|
||||||
Response: false,
|
Response: false,
|
||||||
Nonce: strings.Repeat("0", rand.Intn(20)),
|
Nonce: strings.Repeat("0", rand.IntN(20)),
|
||||||
}
|
}
|
||||||
buf, err := EncodeMessage(m, key)
|
buf, err := EncodeMessage(m, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -351,14 +351,14 @@ func sendSidMessage(
|
|||||||
uConn := ipv4.NewConn(conn)
|
uConn := ipv4.NewConn(conn)
|
||||||
original, err := uConn.TTL()
|
original, err := uConn.TTL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Trace("get ttl error %v", err)
|
xl.Tracef("get ttl error %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
xl.Trace("original ttl %d", original)
|
xl.Tracef("original ttl %d", original)
|
||||||
|
|
||||||
err = uConn.SetTTL(ttl)
|
err = uConn.SetTTL(ttl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Trace("set ttl error %v", err)
|
xl.Tracef("set ttl error %v", err)
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = uConn.SetTTL(original)
|
_ = uConn.SetTTL(original)
|
||||||
@ -377,12 +377,12 @@ func sendSidMessageToRangePorts(
|
|||||||
sendFunc func(*net.UDPConn, string) error,
|
sendFunc func(*net.UDPConn, string) error,
|
||||||
) {
|
) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
for _, ip := range lo.Uniq(parseIPs(addrs)) {
|
for _, ip := range slices.Compact(parseIPs(addrs)) {
|
||||||
for _, portsRange := range ports {
|
for _, portsRange := range ports {
|
||||||
for i := portsRange.From; i <= portsRange.To; i++ {
|
for i := portsRange.From; i <= portsRange.To; i++ {
|
||||||
detectAddr := net.JoinHostPort(ip, strconv.Itoa(i))
|
detectAddr := net.JoinHostPort(ip, strconv.Itoa(i))
|
||||||
if err := sendFunc(conn, detectAddr); err != nil {
|
if err := sendFunc(conn, detectAddr); err != nil {
|
||||||
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
xl.Tracef("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
}
|
}
|
||||||
time.Sleep(2 * time.Millisecond)
|
time.Sleep(2 * time.Millisecond)
|
||||||
}
|
}
|
||||||
@ -398,7 +398,7 @@ func sendSidMessageToRandomPorts(
|
|||||||
used := sets.New[int]()
|
used := sets.New[int]()
|
||||||
getUnusedPort := func() int {
|
getUnusedPort := func() int {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
port := rand.Intn(65535-1024) + 1024
|
port := rand.IntN(65535-1024) + 1024
|
||||||
if !used.Has(port) {
|
if !used.Has(port) {
|
||||||
used.Insert(port)
|
used.Insert(port)
|
||||||
return port
|
return port
|
||||||
@ -419,10 +419,10 @@ func sendSidMessageToRandomPorts(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range lo.Uniq(parseIPs(addrs)) {
|
for _, ip := range slices.Compact(parseIPs(addrs)) {
|
||||||
detectAddr := net.JoinHostPort(ip, strconv.Itoa(port))
|
detectAddr := net.JoinHostPort(ip, strconv.Itoa(port))
|
||||||
if err := sendFunc(conn, detectAddr); err != nil {
|
if err := sendFunc(conn, detectAddr); err != nil {
|
||||||
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
xl.Tracef("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond * 15)
|
time.Sleep(time.Millisecond * 15)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
"github.com/pion/stun"
|
"github.com/pion/stun/v2"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
)
|
)
|
||||||
@ -78,9 +78,9 @@ func ListAllLocalIPs() ([]net.IP, error) {
|
|||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListLocalIPsForNatHole(max int) ([]string, error) {
|
func ListLocalIPsForNatHole(maxItems int) ([]string, error) {
|
||||||
if max <= 0 {
|
if maxItems <= 0 {
|
||||||
return nil, fmt.Errorf("max must be greater than 0")
|
return nil, fmt.Errorf("maxItems must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := ListAllLocalIPs()
|
ips, err := ListAllLocalIPs()
|
||||||
@ -88,9 +88,9 @@ func ListLocalIPsForNatHole(max int) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered := make([]string, 0, max)
|
filtered := make([]string, 0, maxItems)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
if len(filtered) >= max {
|
if len(filtered) >= maxItems {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|