mirror of
https://github.com/fatedier/frp.git
synced 2025-11-29 04:36:43 +00:00
Compare commits
240 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa0a41ee4e | ||
|
|
8a779eb88c | ||
|
|
0138dbd352 | ||
|
|
9b45c93c14 | ||
|
|
191da54980 | ||
|
|
c3b7575453 | ||
|
|
1ea1530b36 | ||
|
|
7f7305fa03 | ||
|
|
644a0cfdb6 | ||
|
|
3c2e2bcea5 | ||
|
|
e0c45a1aca | ||
|
|
e52dfc4a5c | ||
|
|
cc003a2570 | ||
|
|
0f8040b875 | ||
|
|
ef5ae3e598 | ||
|
|
3acf1bb6e9 | ||
|
|
1089eb9d22 | ||
|
|
edf9596ca8 | ||
|
|
26e54b901f | ||
|
|
008933f304 | ||
|
|
317f901c1c | ||
|
|
cd5314466c | ||
|
|
3fbdea0f6b | ||
|
|
710ecf44f5 | ||
|
|
04dafd7ff0 | ||
|
|
c6aa74a2bb | ||
|
|
813c45f5c2 | ||
|
|
c0e05bb41e | ||
|
|
aa74dc4646 | ||
|
|
1e420cc766 | ||
|
|
4fff3c7472 | ||
|
|
48fa618c34 | ||
|
|
c9fe23eb10 | ||
|
|
268afb3438 | ||
|
|
b1181fd17a | ||
|
|
b23548eeff | ||
|
|
262317192c | ||
|
|
8b75b8b837 | ||
|
|
2170c481ce | ||
|
|
dfbf9c4542 | ||
|
|
964a1bbf39 | ||
|
|
228e225f84 | ||
|
|
bd6435c982 | ||
|
|
591023a1f0 | ||
|
|
1ab23b5e0e | ||
|
|
d193519329 | ||
|
|
2406ecdfea | ||
|
|
7266154d54 | ||
|
|
4797136965 | ||
|
|
6d78af6144 | ||
|
|
7728e35c52 | ||
|
|
5a61fd84ad | ||
|
|
ad0c449a75 | ||
|
|
1c330185c4 | ||
|
|
8668fef136 | ||
|
|
7491b327f8 | ||
|
|
abb5b05d49 | ||
|
|
b6ec9dad28 | ||
|
|
caa6e8cf01 | ||
|
|
ffb932390f | ||
|
|
a8efaee1f3 | ||
|
|
4c2afb5c28 | ||
|
|
809f517db8 | ||
|
|
a4b105dedb | ||
|
|
10acf638f8 | ||
|
|
ea62bc5a34 | ||
|
|
f65ffe2812 | ||
|
|
23bb76397a | ||
|
|
859a330e6c | ||
|
|
86ac511763 | ||
|
|
f2e98ef8a4 | ||
|
|
495d999b6c | ||
|
|
6d1af85e80 | ||
|
|
1db091b381 | ||
|
|
0b9124d4fd | ||
|
|
6c6607ae68 | ||
|
|
83d80857fd | ||
|
|
98fa3855bd | ||
|
|
9440bc5d72 | ||
|
|
95753ebf1c | ||
|
|
f8c6795119 | ||
|
|
7033f3e72b | ||
|
|
e3101b7aa8 | ||
|
|
c747f160aa | ||
|
|
c8748a2948 | ||
|
|
487c8d7c29 | ||
|
|
69fa7ed16e | ||
|
|
5336155365 | ||
|
|
4feb74cb89 | ||
|
|
4a4cf552af | ||
|
|
0f59b8f329 | ||
|
|
f480160e2d | ||
|
|
4832a2a1e9 | ||
|
|
52ecd84d8a | ||
|
|
30c246c488 | ||
|
|
42014eea23 | ||
|
|
c2da396230 | ||
|
|
e91c9473be | ||
|
|
13e48c6ca0 | ||
|
|
31e2cb76bb | ||
|
|
91e46a2c53 | ||
|
|
a57679f837 | ||
|
|
75f3bce04d | ||
|
|
df18375308 | ||
|
|
c63737ab3e | ||
|
|
1cdceee347 | ||
|
|
694c434b9e | ||
|
|
62af5c8844 | ||
|
|
56c53909aa | ||
|
|
21a126e4e4 | ||
|
|
8affab1a2b | ||
|
|
12cc53d699 | ||
|
|
2ab832bb89 | ||
|
|
42425d8218 | ||
|
|
6da093a402 | ||
|
|
adc3adc13b | ||
|
|
22a79710d8 | ||
|
|
0927553fe4 | ||
|
|
858d8f0ba7 | ||
|
|
8eb945ee9b | ||
|
|
dc0fd60d30 | ||
|
|
cd44c9f55c | ||
|
|
649f47c345 | ||
|
|
6ca3160b33 | ||
|
|
5f8ed4fc60 | ||
|
|
bf0993d2a6 | ||
|
|
5dc8175fc8 | ||
|
|
dc6a5a29c1 | ||
|
|
e62d9a5242 | ||
|
|
94212ac8b8 | ||
|
|
e9e86fccf0 | ||
|
|
58745992ef | ||
|
|
234d634bfe | ||
|
|
fdc6902a90 | ||
|
|
d8d587fd93 | ||
|
|
92791260a7 | ||
|
|
4dfd851c46 | ||
|
|
bc4df74b5e | ||
|
|
666f122a72 | ||
|
|
f999c8a87e | ||
|
|
90a32ab75d | ||
|
|
0713fd28da | ||
|
|
f5b33e6de8 | ||
|
|
fc6043bb4d | ||
|
|
bc46e3330a | ||
|
|
5fc7b3ceb5 | ||
|
|
6277af4790 | ||
|
|
00bd0a8af4 | ||
|
|
a415573e45 | ||
|
|
e68012858e | ||
|
|
ca8a5b753c | ||
|
|
d1f4ac0f2d | ||
|
|
ff357882ac | ||
|
|
934ac2b836 | ||
|
|
1ad50d5982 | ||
|
|
388b016842 | ||
|
|
134a46c00b | ||
|
|
50796643fb | ||
|
|
b1838b1d5e | ||
|
|
757b3613fe | ||
|
|
ae08811636 | ||
|
|
b657c0fe09 | ||
|
|
84df71047c | ||
|
|
abc6d720d0 | ||
|
|
80154639e3 | ||
|
|
f2117d8331 | ||
|
|
261be6a7b7 | ||
|
|
b53a2c1ed9 | ||
|
|
ee0df07a3c | ||
|
|
4e363eca2b | ||
|
|
4277405c0e | ||
|
|
6a99f0caf7 | ||
|
|
394af08561 | ||
|
|
6451583e60 | ||
|
|
30cb0a3ab0 | ||
|
|
5680a88267 | ||
|
|
6b089858db | ||
|
|
b3ed863021 | ||
|
|
5796c27ed5 | ||
|
|
310e8dd768 | ||
|
|
0b40ac2dbc | ||
|
|
f22c8e0882 | ||
|
|
a388bb2c95 | ||
|
|
e611c44dea | ||
|
|
8e36e2bb67 | ||
|
|
541ad8d899 | ||
|
|
17cc0735d1 | ||
|
|
fd336a5503 | ||
|
|
802d1c1861 | ||
|
|
65fe0a1179 | ||
|
|
2d24879fa3 | ||
|
|
75383a95b3 | ||
|
|
95444ea46b | ||
|
|
9f9c01b520 | ||
|
|
285d1eba0d | ||
|
|
0dfd3a421c | ||
|
|
6a1f15b25e | ||
|
|
9f47c324b7 | ||
|
|
f0df6084af | ||
|
|
879ca47590 | ||
|
|
6a7efc81c9 | ||
|
|
12c5c553c3 | ||
|
|
988e9b1de3 | ||
|
|
db6bbc5187 | ||
|
|
c67b4e7b94 | ||
|
|
b7a73d3469 | ||
|
|
7f9d88c10a | ||
|
|
79237d2b94 | ||
|
|
9c4ec56491 | ||
|
|
74a8752570 | ||
|
|
a8ab4c5003 | ||
|
|
9cee263c91 | ||
|
|
c6bf6f59e6 | ||
|
|
4b7aef2196 | ||
|
|
f6d0046b5a | ||
|
|
84363266d2 | ||
|
|
9ac8f2a047 | ||
|
|
b2b55533b8 | ||
|
|
a4cfab689a | ||
|
|
c7df39074c | ||
|
|
fdcdccb0c2 | ||
|
|
e945c1667a | ||
|
|
87a4de4370 | ||
|
|
e1e2913b77 | ||
|
|
9be24db410 | ||
|
|
6b61cb3742 | ||
|
|
90b7f2080f | ||
|
|
d1f1c72a55 | ||
|
|
1925847ef8 | ||
|
|
8b216b0ca9 | ||
|
|
dbfeea99f3 | ||
|
|
5e64bbfa7c | ||
|
|
e691a40260 | ||
|
|
d812488767 | ||
|
|
3c03690ab7 | ||
|
|
3df27b9c04 | ||
|
|
ba45d29b7c | ||
|
|
3cf83f57a8 | ||
|
|
03e4318d79 | ||
|
|
178d134f46 |
30
.github/ISSUE_TEMPLATE
vendored
30
.github/ISSUE_TEMPLATE
vendored
@@ -1,30 +0,0 @@
|
||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||
|
||||
Use the commands below to provide key information from your environment:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||
|
||||
**What version of frp are you using (./frpc -v or ./frps -v)?**
|
||||
|
||||
|
||||
**What operating system and processor architecture are you using (`go env`)?**
|
||||
|
||||
|
||||
**Configures you used:**
|
||||
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Describe the results you received:**
|
||||
|
||||
|
||||
**Describe the results you expected:**
|
||||
|
||||
|
||||
**Additional information you deem important (e.g. issue happens only occasionally):**
|
||||
|
||||
|
||||
**Can you point out what caused this issue (optional)**
|
||||
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Bug Report for FRP
|
||||
title: ''
|
||||
labels: Requires Testing
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
|
||||
|
||||
<!-- ⚠️⚠️ Incomplete reports will be marked as invalid, and closed, with few exceptions ⚠️⚠️ -->
|
||||
<!-- in addition, please use search well so that the same solution can be found in the feedback, we will close it directly -->
|
||||
<!-- for convenience of differentiation, use FRPS or FRPC to refer to the FRP server or client -->
|
||||
|
||||
**[REQUIRED] hat version of frp are you using**
|
||||
<!-- Use ./frpc -v or ./frps -v -->
|
||||
Version:
|
||||
|
||||
**[REQUIRED] What operating system and processor architecture are you using**
|
||||
OS:
|
||||
CPU architecture:
|
||||
|
||||
**[REQUIRED] description of errors**
|
||||
|
||||
**confile**
|
||||
<!-- Please pay attention to hiding the token, server_addr and other privacy information -->
|
||||
|
||||
**log file**
|
||||
<!-- If the file is too large, use Pastebin, for example https://pastebin.ubuntu.com/ -->
|
||||
|
||||
**Steps to reproduce the issue**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Supplementary information**
|
||||
|
||||
**Can you guess what caused this issue**
|
||||
|
||||
**Checklist**:
|
||||
<!--- Make sure you've completed the following steps (put an "X" between of brackets): -->
|
||||
- [] I included all information required in the sections above
|
||||
- [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/fatedier/frp/issues?q=is%3Aissue)
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: DOCS
|
||||
url: https://github.com/fatedier/frp
|
||||
about: Here you can find out how to configure frp.
|
||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: "[+] Enhancement"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
|
||||
|
||||
**The solution you want**
|
||||
<!--A clear and concise description of the solution you want. -->
|
||||
|
||||
**Alternatives considered**
|
||||
<!--A clear and concise description of any alternative solutions or features you have considered. -->
|
||||
|
||||
**How to implement this function**
|
||||
<!--Implementation steps for the solution you want. -->
|
||||
|
||||
**Application scenarios of this function**
|
||||
<!--Make a clear and concise description of the application scenario of the solution you want. -->
|
||||
115
.github/workflows/build-and-push-image.yml
vendored
Normal file
115
.github/workflows/build-and-push-image.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
name: Build Image and Publish to Dockerhub & GPR
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ created ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Image tag'
|
||||
required: true
|
||||
default: 'test'
|
||||
jobs:
|
||||
binary:
|
||||
name: Build Golang project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
-
|
||||
run: go version
|
||||
-
|
||||
name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Build
|
||||
run: make build
|
||||
-
|
||||
name: Archive artifacts for frpc
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: frpc
|
||||
path: bin/frpc
|
||||
-
|
||||
name: Archive artifacts for frps
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: frps
|
||||
path: bin/frps
|
||||
|
||||
image:
|
||||
name: Build Image from Dockerfile and binaries
|
||||
runs-on: ubuntu-latest
|
||||
needs: binary
|
||||
steps:
|
||||
# environment
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
# download binaries of frpc and frps
|
||||
-
|
||||
name: Download binary of frpc
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: frpc
|
||||
path: bin/frpc
|
||||
-
|
||||
name: Download binary of frps
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: frps
|
||||
path: bin/frps
|
||||
# get image tag name
|
||||
-
|
||||
name: Get Image Tag Name
|
||||
run: |
|
||||
if [ x${{ github.event.inputs.tag }} == x"" ]; then
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
fi
|
||||
# prepare image tags
|
||||
-
|
||||
name: Prepare Image Tags
|
||||
run: |
|
||||
echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV
|
||||
echo "TAG_FRPC=fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
# build images
|
||||
-
|
||||
name: Build Images
|
||||
run: |
|
||||
# for Docker hub
|
||||
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} .
|
||||
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS }} .
|
||||
# for GPR
|
||||
docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} .
|
||||
docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} .
|
||||
# push to dockerhub
|
||||
-
|
||||
name: Publish to Dockerhub
|
||||
run: |
|
||||
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
||||
docker push ${{ env.TAG_FRPC }}
|
||||
docker push ${{ env.TAG_FRPS }}
|
||||
# push to gpr
|
||||
-
|
||||
name: Publish to GPR
|
||||
run: |
|
||||
echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin
|
||||
docker push ${{ env.TAG_FRPC_GPR }}
|
||||
docker push ${{ env.TAG_FRPS_GPR }}
|
||||
30
.github/workflows/goreleaser.yml
vendored
Normal file
30
.github/workflows/goreleaser.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
|
||||
- name: Make All
|
||||
run: |
|
||||
./package.sh
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist --release-notes=./Release.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
26
.github/workflows/stale.yml
vendored
Normal file
26
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: "Close stale issues"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "20 0 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug-only:
|
||||
description: 'In debug mod'
|
||||
required: false
|
||||
default: 'false'
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.'
|
||||
stale-pr-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.'
|
||||
stale-issue-label: 'lifecycle/stale'
|
||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
stale-pr-label: 'lifecycle/stale'
|
||||
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
days-before-stale: 45
|
||||
days-before-close: 10
|
||||
debug-only: ${{ github.event.inputs.debug-only }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,7 +26,10 @@ _testmain.go
|
||||
# Self
|
||||
bin/
|
||||
packages/
|
||||
release/
|
||||
test/bin/
|
||||
vendor/
|
||||
dist/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
||||
19
.goreleaser.yml
Normal file
19
.goreleaser.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
builds:
|
||||
- skip: true
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
release:
|
||||
# Same as for github
|
||||
# Note: it can only be one: either github, gitlab or gitea
|
||||
github:
|
||||
owner: fatedier
|
||||
name: frp
|
||||
|
||||
draft: false
|
||||
|
||||
# You can add extra pre-existing files to the release.
|
||||
# The filename on the release will be the last part of the path (base). If
|
||||
# another file with the same name exists, the latest one found will be used.
|
||||
# Defaults to empty.
|
||||
extra_files:
|
||||
- glob: ./release/packages/*
|
||||
@@ -2,8 +2,8 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
|
||||
install:
|
||||
- make
|
||||
|
||||
18
Makefile
18
Makefile
@@ -1,4 +1,6 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
all: fmt build
|
||||
|
||||
@@ -16,27 +18,29 @@ file:
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
|
||||
frps:
|
||||
go build -o bin/frps ./cmd/frps
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||
|
||||
frpc:
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc
|
||||
|
||||
test: gotest
|
||||
|
||||
gotest:
|
||||
go test -v --cover ./assets/...
|
||||
go test -v --cover ./client/...
|
||||
go test -v --cover ./cmd/...
|
||||
go test -v --cover ./models/...
|
||||
go test -v --cover ./client/...
|
||||
go test -v --cover ./server/...
|
||||
go test -v --cover ./utils/...
|
||||
go test -v --cover ./pkg/...
|
||||
|
||||
ci:
|
||||
go test -count=1 -p=1 -v ./tests/...
|
||||
|
||||
alltest: gotest ci
|
||||
e2e:
|
||||
./hack/run-e2e.sh
|
||||
|
||||
alltest: gotest ci e2e
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
all: build
|
||||
@@ -6,32 +7,29 @@ all: build
|
||||
build: app
|
||||
|
||||
app:
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_amd64.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
|
||||
|
||||
temp:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_darwin_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_darwin_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_386.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_windows_386.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_amd64.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_windows_amd64.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64le ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64le ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mipsle ./cmd/frps
|
||||
|
||||
539
README.md
539
README.md
@@ -1,53 +1,67 @@
|
||||
# frp
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
[](https://github.com/fatedier/frp/releases)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
||||
|
||||
Now it also try to support p2p connect.
|
||||
frp also has a P2P connect mode.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Status](#status)
|
||||
* [Development Status](#development-status)
|
||||
* [Architecture](#architecture)
|
||||
* [Example Usage](#example-usage)
|
||||
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||
* [Forward DNS query request](#forward-dns-query-request)
|
||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||
* [Expose your service in security](#expose-your-service-in-security)
|
||||
* [Forward Unix domain socket](#forward-unix-domain-socket)
|
||||
* [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
|
||||
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||
* [Expose your service privately](#expose-your-service-privately)
|
||||
* [P2P Mode](#p2p-mode)
|
||||
* [Features](#features)
|
||||
* [Configuration File](#configuration-file)
|
||||
* [Configuration file template](#configuration-file-template)
|
||||
* [Configuration Files](#configuration-files)
|
||||
* [Using Environment Variables](#using-environment-variables)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Authentication](#authentication)
|
||||
* [Admin UI](#admin-ui)
|
||||
* [Monitor](#monitor)
|
||||
* [Prometheus](#prometheus)
|
||||
* [Authenticating the Client](#authenticating-the-client)
|
||||
* [Token Authentication](#token-authentication)
|
||||
* [OIDC Authentication](#oidc-authentication)
|
||||
* [Encryption and Compression](#encryption-and-compression)
|
||||
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||
* [TLS](#tls)
|
||||
* [Hot-Reloading frpc configuration](#hot-reloading-frpc-configuration)
|
||||
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||
* [Port White List](#port-white-list)
|
||||
* [Only allowing certain ports on the server](#only-allowing-certain-ports-on-the-server)
|
||||
* [Port Reuse](#port-reuse)
|
||||
* [Bandwidth Limit](#bandwidth-limit)
|
||||
* [For Each Proxy](#for-each-proxy)
|
||||
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||
* [Support KCP Protocol](#support-kcp-protocol)
|
||||
* [Connection Pool](#connection-pool)
|
||||
* [Connection Pooling](#connection-pooling)
|
||||
* [Load balancing](#load-balancing)
|
||||
* [Health Check](#health-check)
|
||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
||||
* [Service Health Check](#service-health-check)
|
||||
* [Rewriting the HTTP Host Header](#rewriting-the-http-host-header)
|
||||
* [Setting other HTTP Headers](#setting-other-http-headers)
|
||||
* [Get Real IP](#get-real-ip)
|
||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||
* [Custom subdomain names](#custom-subdomain-names)
|
||||
* [URL routing](#url-routing)
|
||||
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
||||
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||
* [Proxy Protocol](#proxy-protocol)
|
||||
* [Require HTTP Basic Auth (Password) for Web Services](#require-http-basic-auth-password-for-web-services)
|
||||
* [Custom Subdomain Names](#custom-subdomain-names)
|
||||
* [URL Routing](#url-routing)
|
||||
* [TCP Port Multiplexing](#tcp-port-multiplexing)
|
||||
* [Connecting to frps via HTTP PROXY](#connecting-to-frps-via-http-proxy)
|
||||
* [Range ports mapping](#range-ports-mapping)
|
||||
* [Plugin](#plugin)
|
||||
* [Client Plugins](#client-plugins)
|
||||
* [Server Manage Plugins](#server-manage-plugins)
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
* [Donation](#donation)
|
||||
@@ -57,11 +71,11 @@ Now it also try to support p2p connect.
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## Status
|
||||
## Development Status
|
||||
|
||||
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
||||
frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
|
||||
|
||||
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
|
||||
**The protocol might change at a release and we don't promise backwards compatibility. Please check the release log when upgrading the client and the server.**
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -69,15 +83,15 @@ frp is under development and you can try it with latest release version. Master
|
||||
|
||||
## Example Usage
|
||||
|
||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch.
|
||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your operating system and architecture.
|
||||
|
||||
Put **frps** and **frps.ini** to your server with public IP.
|
||||
Put `frps` and `frps.ini` onto your server A with public IP.
|
||||
|
||||
Put **frpc** and **frpc.ini** to your server in LAN.
|
||||
Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected from public Internet).
|
||||
|
||||
### Access your computer in LAN by SSH
|
||||
|
||||
1. Modify frps.ini:
|
||||
1. Modify `frps.ini` on server A and set the `bind_port` to be connected to frp clients:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -85,11 +99,11 @@ Put **frpc** and **frpc.ini** to your server in LAN.
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
2. Start `frps` on server A:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini, `server_addr` is your frps's server IP:
|
||||
3. On server B, modify `frpc.ini` to put in your `frps` server public IP as `server_addr` field:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -104,21 +118,23 @@ Put **frpc** and **frpc.ini** to your server in LAN.
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
Note that `local_port` (listened on client) and `remote_port` (exposed on server) are for traffic goes in/out the frp system, whereas `server_port` is used between frps.
|
||||
|
||||
4. Start `frpc` on server B:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Connect to server in LAN by ssh assuming that username is test:
|
||||
5. From another machine, SSH to server B like this (assuming that username is `test`):
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### Visit your web service in LAN by custom domains
|
||||
|
||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
|
||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local IP.
|
||||
|
||||
However, we can expose a http or https service using frp.
|
||||
However, we can expose an HTTP(S) service using frp.
|
||||
|
||||
1. Modify frps.ini, configure http port 8080:
|
||||
1. Modify `frps.ini`, set the vhost HTTP port to 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -127,11 +143,11 @@ However, we can expose a http or https service using frp.
|
||||
vhost_http_port = 8080
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
2. Start `frps`:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service:
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. The `local_port` is the port of your web service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -142,20 +158,20 @@ However, we can expose a http or https service using frp.
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = www.yourdomain.com
|
||||
custom_domains = www.example.com
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
4. Start `frpc`:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain.
|
||||
5. Resolve A record of `www.example.com` to the public IP of the remote frps server or CNAME record to your origin domain.
|
||||
|
||||
6. Now visit your local web service using url `http://www.yourdomain.com:8080`.
|
||||
6. Now visit your local web service using url `http://www.example.com:8080`.
|
||||
|
||||
### Forward DNS query request
|
||||
|
||||
1. Modify frps.ini:
|
||||
1. Modify `frps.ini`:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -163,11 +179,11 @@ However, we can expose a http or https service using frp.
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. Start frps:
|
||||
2. Start `frps`:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to google dns server `8.8.8.8:53`:
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server, forward DNS query request to Google Public DNS server `8.8.8.8:53`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -186,17 +202,17 @@ However, we can expose a http or https service using frp.
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Send dns query request by dig:
|
||||
5. Test DNS resolution using `dig` command:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.google.com`
|
||||
|
||||
### Forward unix domain socket
|
||||
### Forward Unix domain socket
|
||||
|
||||
Using tcp port to connect unix domain socket like docker daemon.
|
||||
Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP.
|
||||
|
||||
Configure frps same as above.
|
||||
Configure `frps` same as above.
|
||||
|
||||
1. Start frpc with configurations:
|
||||
1. Start `frpc` with configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -211,17 +227,17 @@ Configure frps same as above.
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
2. Get docker version by curl command:
|
||||
2. Test: Get Docker version using `curl`:
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### Expose a simple http file server
|
||||
### Expose a simple HTTP file server
|
||||
|
||||
A simple way to visit files in the LAN.
|
||||
Browser your files stored in the LAN, from public Internet.
|
||||
|
||||
Configure frps same as above.
|
||||
Configure `frps` same as above.
|
||||
|
||||
1. Start frpc with configurations:
|
||||
1. Start `frpc` with configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -233,23 +249,45 @@ Configure frps same as above.
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = static_file
|
||||
plugin_local_path = /tmp/file
|
||||
plugin_local_path = /tmp/files
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct user and password to view files in `/tmp/files` on the `frpc` machine.
|
||||
|
||||
### Expose your service in security
|
||||
### Enable HTTPS for local HTTP service
|
||||
|
||||
For some services, if expose them to the public network directly will be a security risk.
|
||||
1. Start `frpc` with configuration:
|
||||
|
||||
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
Configure frps same as above.
|
||||
[test_https2http]
|
||||
type = https
|
||||
custom_domains = test.example.com
|
||||
|
||||
1. Start frpc, forward ssh port and `remote_port` is useless:
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
```
|
||||
|
||||
2. Visit `https://test.example.com`.
|
||||
|
||||
### Expose your service privately
|
||||
|
||||
Some services will be at risk if exposed directly to the public network. With **STCP** (secret TCP) mode, a preshared key is needed to access the service from another client.
|
||||
|
||||
Configure `frps` same as above.
|
||||
|
||||
1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `sk` field for the preshared key, and that the `remote_port` field is removed here:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -264,7 +302,7 @@ Configure frps same as above.
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
2. Start another frpc in which you want to connect this ssh server:
|
||||
2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`sk` field):
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -281,23 +319,24 @@ Configure frps same as above.
|
||||
bind_port = 6000
|
||||
```
|
||||
|
||||
3. Connect to server in LAN by ssh assuming that username is test:
|
||||
3. On machine C, connect to SSH on machine B, using this command:
|
||||
|
||||
`ssh -oPort=6000 test@127.0.0.1`
|
||||
`ssh -oPort=6000 127.0.0.1`
|
||||
|
||||
### P2P Mode
|
||||
|
||||
**xtcp** is designed for transmitting a large amount of data directly between two client.
|
||||
**xtcp** is designed for transmitting large amounts of data directly between clients. A frps server is still needed, as P2P here only refers the actual data transmission.
|
||||
|
||||
Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
|
||||
Note it can't penetrate all types of NAT devices. You might want to fallback to **stcp** if **xtcp** doesn't work.
|
||||
|
||||
1. Configure a udp port for xtcp:
|
||||
1. In `frps.ini` configure a UDP port for xtcp:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
bind_udp_port = 7001
|
||||
```
|
||||
|
||||
2. Start frpc, forward ssh port and `remote_port` is useless:
|
||||
2. Start `frpc` on machine B, expose the SSH port. Note that `remote_port` field is removed:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -312,7 +351,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
3. Start another frpc in which you want to connect this ssh server:
|
||||
3. Start another `frpc` (typically on another machine C) with the config to connect to SSH using P2P mode:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -329,23 +368,23 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
|
||||
bind_port = 6000
|
||||
```
|
||||
|
||||
4. Connect to server in LAN by ssh assuming that username is test:
|
||||
4. On machine C, connect to SSH on machine B, using this command:
|
||||
|
||||
`ssh -oPort=6000 test@127.0.0.1`
|
||||
`ssh -oPort=6000 127.0.0.1`
|
||||
|
||||
## Features
|
||||
|
||||
### Configuration File
|
||||
### Configuration Files
|
||||
|
||||
You can find features which this document not metioned from full example configuration files.
|
||||
Read the full example configuration files to find out even more features not described here.
|
||||
|
||||
[frps full configuration file](./conf/frps_full.ini)
|
||||
[Full configuration file for frps (Server)](./conf/frps_full.ini)
|
||||
|
||||
[frpc full configuration file](./conf/frpc_full.ini)
|
||||
[Full configuration file for frpc (Client)](./conf/frpc_full.ini)
|
||||
|
||||
### Configuration file template
|
||||
### Using Environment Variables
|
||||
|
||||
Configuration file tempalte can be rendered using os environments. Template uses Go's standard format.
|
||||
Environment variables can be referenced in the configuration file, using Go's standard format:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -360,7 +399,7 @@ local_port = 22
|
||||
remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
|
||||
```
|
||||
|
||||
Start frpc program:
|
||||
With the config above, variables can be passed into `frpc` program like this:
|
||||
|
||||
```
|
||||
export FRP_SERVER_ADDR="x.x.x.x"
|
||||
@@ -368,12 +407,11 @@ export FRP_SSH_REMOTE_PORT="6000"
|
||||
./frpc -c ./frpc.ini
|
||||
```
|
||||
|
||||
frpc will auto render configuration file template using os environments.
|
||||
All environments has prefix `.Envs`.
|
||||
`frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`.
|
||||
|
||||
### Dashboard
|
||||
|
||||
Check frp's status and proxies's statistics information by Dashboard.
|
||||
Check frp's status and proxies' statistics information by Dashboard.
|
||||
|
||||
Configure a port for dashboard to enable this feature:
|
||||
|
||||
@@ -385,17 +423,83 @@ dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
```
|
||||
|
||||
Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`.
|
||||
Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin` by default.
|
||||
|
||||

|
||||
|
||||
### Authentication
|
||||
### Admin UI
|
||||
|
||||
`token` in frps.ini and frpc.ini should be same.
|
||||
The Admin UI helps you check and manage frpc's configuration.
|
||||
|
||||
Configure an address for admin UI to enable this feature:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
```
|
||||
|
||||
Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin` by default.
|
||||
|
||||
### Monitor
|
||||
|
||||
When dashboard is enabled, frps will save monitor data in cache. It will be cleared after process restart.
|
||||
|
||||
Prometheus is also supported.
|
||||
|
||||
#### Prometheus
|
||||
|
||||
Enable dashboard first, then configure `enable_prometheus = true` in `frps.ini`.
|
||||
|
||||
`http://{dashboard_addr}/metrics` will provide prometheus monitor data.
|
||||
|
||||
### Authenticating the Client
|
||||
|
||||
There are 2 authentication methods to authenticate frpc with frps.
|
||||
|
||||
You can decide which one to use by configuring `authentication_method` under `[common]` in `frpc.ini` and `frps.ini`.
|
||||
|
||||
Configuring `authenticate_heartbeats = true` under `[common]` will use the configured authentication method to add and validate authentication on every heartbeat between frpc and frps.
|
||||
|
||||
Configuring `authenticate_new_work_conns = true` under `[common]` will do the same for every new work connection between frpc and frps.
|
||||
|
||||
#### Token Authentication
|
||||
|
||||
When specifying `authentication_method = token` under `[common]` in `frpc.ini` and `frps.ini` - token based authentication will be used.
|
||||
|
||||
Make sure to specify the same `token` in the `[common]` section in `frps.ini` and `frpc.ini` for frpc to pass frps validation
|
||||
|
||||
#### OIDC Authentication
|
||||
|
||||
When specifying `authentication_method = oidc` under `[common]` in `frpc.ini` and `frps.ini` - OIDC based authentication will be used.
|
||||
|
||||
OIDC stands for OpenID Connect, and the flow used is called [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).
|
||||
|
||||
To use this authentication type - configure `frpc.ini` and `frps.ini` as follows:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
authentication_method = oidc
|
||||
oidc_issuer = https://example-oidc-issuer.com/
|
||||
oidc_audience = https://oidc-audience.com/.default
|
||||
```
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
authentication_method = oidc
|
||||
oidc_client_id = 98692467-37de-409a-9fac-bb2585826f18 # Replace with OIDC client ID
|
||||
oidc_client_secret = oidc_secret
|
||||
oidc_audience = https://oidc-audience.com/.default
|
||||
oidc_token_endpoint_url = https://example-oidc-endpoint.com/oauth2/v2.0/token
|
||||
```
|
||||
|
||||
### Encryption and Compression
|
||||
|
||||
Defalut value is false, you could decide if the proxy will use encryption or compression:
|
||||
The features are off by default. You can turn on encryption and/or compression:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -407,9 +511,19 @@ use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
### Hot-Reload frpc configuration
|
||||
#### TLS
|
||||
|
||||
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||
frp supports the TLS protocol between `frpc` and `frps` since v0.25.0.
|
||||
|
||||
Config `tls_enable = true` in the `[common]` section to `frpc.ini` to enable this feature.
|
||||
|
||||
For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection.
|
||||
|
||||
To enforce `frps` to only accept TLS connections - configure `tls_only = true` in the `[common]` section in `frps.ini`.
|
||||
|
||||
### Hot-Reloading frpc configuration
|
||||
|
||||
The `admin_addr` and `admin_port` fields are required for enabling HTTP API:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -418,17 +532,17 @@ admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
```
|
||||
|
||||
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
|
||||
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let `frpc` create or update or delete proxies.
|
||||
|
||||
**Note that parameters in [common] section won't be modified except 'start' now.**
|
||||
**Note that parameters in [common] section won't be modified except 'start'.**
|
||||
|
||||
### Get proxy status from client
|
||||
|
||||
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
|
||||
Use `frpc status -c ./frpc.ini` to get status of all proxies. The `admin_addr` and `admin_port` fields are required for enabling HTTP API.
|
||||
|
||||
### Port White List
|
||||
### Only allowing certain ports on the server
|
||||
|
||||
`allow_ports` in frps.ini is used for preventing abuse of ports:
|
||||
`allow_ports` in `frps.ini` is used to avoid abuse of ports:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -436,19 +550,34 @@ Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set ad
|
||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
`allow_ports` consists of a specific port or a range of ports divided by `,`.
|
||||
`allow_ports` consists of specific ports or port ranges (lowest port number, dash `-`, highest port number), separated by comma `,`.
|
||||
|
||||
### Port Reuse
|
||||
|
||||
Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly.
|
||||
`vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect the connection's protocol and handle it correspondingly.
|
||||
|
||||
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
|
||||
|
||||
### Bandwidth Limit
|
||||
|
||||
#### For Each Proxy
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
bandwidth_limit = 1MB
|
||||
```
|
||||
|
||||
Set `bandwidth_limit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`.
|
||||
|
||||
### TCP Stream Multiplexing
|
||||
|
||||
frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
|
||||
frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.
|
||||
|
||||
You can disable this feature by modify frps.ini and frpc.ini:
|
||||
You can disable this feature by modify `frps.ini` and `frpc.ini`:
|
||||
|
||||
```ini
|
||||
# frps.ini and frpc.ini, must be same
|
||||
@@ -458,40 +587,40 @@ tcp_mux = false
|
||||
|
||||
### Support KCP Protocol
|
||||
|
||||
frp support kcp protocol since v0.12.0.
|
||||
|
||||
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||
|
||||
Using kcp in frp:
|
||||
KCP mode uses UDP as the underlying transport. Using KCP in frp:
|
||||
|
||||
1. Enable kcp protocol in frps:
|
||||
1. Enable KCP in frps:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
# kcp needs to bind a udp port, it can be same with 'bind_port'
|
||||
# Specify a UDP port for KCP.
|
||||
kcp_bind_port = 7000
|
||||
```
|
||||
|
||||
2. Configure the protocol used in frpc to connect frps:
|
||||
The `kcp_bind_port` number can be the same number as `bind_port`, since `bind_port` field specifies a TCP port.
|
||||
|
||||
2. Configure `frpc.ini` to use KCP to connect to frps:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
# specify the 'kcp_bind_port' in frps
|
||||
# Same as the 'kcp_bind_port' in frps.ini
|
||||
server_port = 7000
|
||||
protocol = kcp
|
||||
```
|
||||
|
||||
### Connection Pool
|
||||
### Connection Pooling
|
||||
|
||||
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
||||
By default, frps creates a new frpc connection to the backend service upon a user request. With connection pooling, frps keeps a certain number of pre-established connections, reducing the time needed to establish a connection.
|
||||
|
||||
This feature is fit for a large number of short connections.
|
||||
This feature is suitable for a large number of short connections.
|
||||
|
||||
1. Configure the limit of pool count each proxy can use in frps.ini:
|
||||
1. Configure the limit of pool count each proxy can use in `frps.ini`:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
@@ -510,7 +639,8 @@ This feature is fit for a large number of short connections.
|
||||
### Load balancing
|
||||
|
||||
Load balancing is supported by `group`.
|
||||
This feature is available only for type `tcp` now.
|
||||
|
||||
This feature is only available for types `tcp` and `http` now.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -531,19 +661,19 @@ group_key = 123
|
||||
|
||||
`group_key` is used for authentication.
|
||||
|
||||
Proxies in same group will accept connections from port 80 randomly.
|
||||
Connections to port 80 will be dispatched to proxies in the same group randomly.
|
||||
|
||||
### Health Check
|
||||
For type `tcp`, `remote_port` in the same group should be the same.
|
||||
|
||||
For type `http`, `custom_domains`, `subdomain`, `locations` should be the same.
|
||||
|
||||
### Service Health Check
|
||||
|
||||
Health check feature can help you achieve high availability with load balancing.
|
||||
|
||||
Add `health_check_type = {type}` to enable health check.
|
||||
Add `health_check_type = tcp` or `health_check_type = http` to enable health check.
|
||||
|
||||
**type** can be tcp or http.
|
||||
|
||||
Type tcp will dial the service port and type http will send a http rquest to service and require a 200 response.
|
||||
|
||||
Type tcp configuration:
|
||||
With health check type **tcp**, the service port will be pinged (TCPing):
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -551,77 +681,102 @@ Type tcp configuration:
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
# enable tcp health check
|
||||
# Enable TCP health check
|
||||
health_check_type = tcp
|
||||
# dial timeout seconds
|
||||
# TCPing timeout seconds
|
||||
health_check_timeout_s = 3
|
||||
# if continuous failed in 3 times, the proxy will be removed from frps
|
||||
# If health check failed 3 times in a row, the proxy will be removed from frps
|
||||
health_check_max_failed = 3
|
||||
# every 10 seconds will do a health check
|
||||
# A health check every 10 seconds
|
||||
health_check_interval_s = 10
|
||||
```
|
||||
|
||||
Type http configuration:
|
||||
With health check type **http**, an HTTP request will be sent to the service and an HTTP 2xx OK response is expected:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
# enable http health check
|
||||
custom_domains = test.example.com
|
||||
# Enable HTTP health check
|
||||
health_check_type = http
|
||||
# frpc will send a GET http request '/status' to local http service
|
||||
# http service is alive when it return 2xx http response code
|
||||
# frpc will send a GET request to '/status'
|
||||
# and expect an HTTP 2xx OK response
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 10
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
health_check_max_failed = 3
|
||||
health_check_interval_s = 10
|
||||
```
|
||||
|
||||
### Rewriting the Host Header
|
||||
### Rewriting the HTTP Host Header
|
||||
|
||||
When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
|
||||
By default frp does not modify the tunneled HTTP requests at all as it's a byte-for-byte copy.
|
||||
|
||||
However, speaking of web servers and HTTP requests, your web server might rely on the `Host` HTTP header to determine the website to be accessed. frp can rewrite the `Host` header when forwarding the HTTP requests, with the `host_header_rewrite` field:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
custom_domains = test.example.com
|
||||
host_header_rewrite = dev.example.com
|
||||
```
|
||||
|
||||
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address.
|
||||
The HTTP request will have the the `Host` header rewritten to `Host: dev.example.com` when it reaches the actual web server, although the request from the browser probably has `Host: test.example.com`.
|
||||
|
||||
### Set Headers In HTTP Request
|
||||
### Setting other HTTP Headers
|
||||
|
||||
You can set headers for proxy which type is `http`.
|
||||
Similar to `Host`, You can override other HTTP request headers with proxy type `http`.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
custom_domains = test.example.com
|
||||
host_header_rewrite = dev.example.com
|
||||
header_X-From-Where = frp
|
||||
```
|
||||
|
||||
Note that params which have prefix `header_` will be added to http request headers.
|
||||
In this example, it will set header `X-From-Where: frp` to http request.
|
||||
Note that parameter(s) prefixed with `header_` will be added to HTTP request headers.
|
||||
|
||||
In this example, it will set header `X-From-Where: frp` in the HTTP request.
|
||||
|
||||
### Get Real IP
|
||||
|
||||
Features for http proxy only.
|
||||
#### HTTP X-Forwarded-For
|
||||
|
||||
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
|
||||
This feature is for http proxy only.
|
||||
|
||||
### Password protecting your web service
|
||||
You can get user's real IP from HTTP request headers `X-Forwarded-For` and `X-Real-IP`.
|
||||
|
||||
#### Proxy Protocol
|
||||
|
||||
frp supports Proxy Protocol to send user's real IP to local services. It support all types except UDP.
|
||||
|
||||
Here is an example for https service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = https
|
||||
local_port = 443
|
||||
custom_domains = test.example.com
|
||||
|
||||
# now v1 and v2 are supported
|
||||
proxy_protocol_version = v2
|
||||
```
|
||||
|
||||
You can enable Proxy Protocol support in nginx to expose user's real IP in HTTP header `X-Real-IP`, and then read `X-Real-IP` header in your web service for the real IP.
|
||||
|
||||
### Require HTTP Basic Auth (Password) for Web Services
|
||||
|
||||
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
|
||||
|
||||
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
|
||||
This enforces HTTP Basic Auth on all requests with the username and password specified in frpc's configure file.
|
||||
|
||||
It can only be enabled when proxy type is http.
|
||||
|
||||
@@ -630,23 +785,23 @@ It can only be enabled when proxy type is http.
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
custom_domains = test.example.com
|
||||
http_user = abc
|
||||
http_pwd = abc
|
||||
```
|
||||
|
||||
Visit `http://test.yourdomain.com` and now you need to input username and password.
|
||||
Visit `http://test.example.com` in the browser and now you are prompted to enter the username and password.
|
||||
|
||||
### Custom subdomain names
|
||||
### Custom Subdomain Names
|
||||
|
||||
It is convenient to use `subdomain` configure for http、https type when many people use one frps server together.
|
||||
It is convenient to use `subdomain` configure for http and https types when many people share one frps server.
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
subdomain_host = frps.com
|
||||
```
|
||||
|
||||
Resolve `*.frps.com` to the frps server's IP.
|
||||
Resolve `*.frps.com` to the frps server's IP. This is usually called a Wildcard DNS record.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -656,35 +811,79 @@ local_port = 80
|
||||
subdomain = test
|
||||
```
|
||||
|
||||
Now you can visit your web service by host `test.frps.com`.
|
||||
Now you can visit your web service on `test.frps.com`.
|
||||
|
||||
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
|
||||
|
||||
### URL routing
|
||||
### URL Routing
|
||||
|
||||
frp support forward http requests to different backward web services by url routing.
|
||||
frp supports forwarding HTTP requests to different backend web services by url routing.
|
||||
|
||||
`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
|
||||
`locations` specifies the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = web.yourdomain.com
|
||||
custom_domains = web.example.com
|
||||
locations = /
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_port = 81
|
||||
custom_domains = web.yourdomain.com
|
||||
custom_domains = web.example.com
|
||||
locations = /news,/about
|
||||
```
|
||||
Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**.
|
||||
|
||||
### Connect frps by HTTP PROXY
|
||||
HTTP requests with URL prefix `/news` or `/about` will be forwarded to **web02** and other requests to **web01**.
|
||||
|
||||
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
||||
### TCP Port Multiplexing
|
||||
|
||||
frp supports receiving TCP sockets directed to different proxies on a single port on frps, similar to `vhost_http_port` and `vhost_https_port`.
|
||||
|
||||
The only supported TCP port multiplexing method available at the moment is `httpconnect` - HTTP CONNECT tunnel.
|
||||
|
||||
When setting `tcpmux_httpconnect_port` to anything other than 0 in frps under `[common]`, frps will listen on this port for HTTP CONNECT requests.
|
||||
|
||||
The host of the HTTP CONNECT request will be used to match the proxy in frps. Proxy hosts can be configured in frpc by configuring `custom_domain` and / or `subdomain` under `type = tcpmux` proxies, when `multiplexer = httpconnect`.
|
||||
|
||||
For example:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
tcpmux_httpconnect_port = 1337
|
||||
```
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[proxy1]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
custom_domains = test1
|
||||
|
||||
[proxy2]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
custom_domains = test2
|
||||
```
|
||||
|
||||
In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:
|
||||
|
||||
```
|
||||
CONNECT test1 HTTP/1.1\r\n\r\n
|
||||
```
|
||||
and the connection will be routed to `proxy1`.
|
||||
|
||||
### Connecting to frps via HTTP PROXY
|
||||
|
||||
frpc can connect to frps using HTTP proxy if you set OS environment variable `HTTP_PROXY`, or if `http_proxy` is set in frpc.ini file.
|
||||
|
||||
It only works when protocol is tcp.
|
||||
|
||||
@@ -698,7 +897,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
|
||||
### Range ports mapping
|
||||
|
||||
Proxy name has prefix `range:` will support mapping range ports.
|
||||
Proxy with names that start with `range:` will support mapping range ports.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -709,15 +908,15 @@ local_port = 6000-6006,6007
|
||||
remote_port = 6000-6006,6007
|
||||
```
|
||||
|
||||
frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
|
||||
frpc will generate 8 proxies like `test_tcp_0`, `test_tcp_1`, ..., `test_tcp_7`.
|
||||
|
||||
### Plugin
|
||||
### Client Plugins
|
||||
|
||||
frpc only forward request to local tcp or udp port by default.
|
||||
frpc only forwards requests to local TCP or UDP ports by default.
|
||||
|
||||
Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
||||
Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
||||
|
||||
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
||||
Specify which plugin to use with the `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` are not used for plugin.
|
||||
|
||||
Using plugin **http_proxy**:
|
||||
|
||||
@@ -733,11 +932,15 @@ plugin_http_passwd = abc
|
||||
|
||||
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||
|
||||
### Server Manage Plugins
|
||||
|
||||
Read the [document](/doc/server_plugin.md).
|
||||
|
||||
Find more plugins in [gofrp/plugin](https://github.com/gofrp/plugin).
|
||||
|
||||
## Development Plan
|
||||
|
||||
* Log http request information in frps.
|
||||
* Direct reverse proxy, like haproxy.
|
||||
* kubernetes ingress support.
|
||||
* Log HTTP request information in frps.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -745,14 +948,14 @@ Interested in getting involved? We would like to help you!
|
||||
|
||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
||||
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
||||
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
||||
* If you have some wonderful ideas, send email to fatedier@gmail.com.
|
||||
* Sorry for my poor English. Improvements for this document are welcome, even some typo fixes.
|
||||
* If you have great ideas, send an email to fatedier@gmail.com.
|
||||
|
||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatedly.**
|
||||
|
||||
## Donation
|
||||
|
||||
If frp help you a lot, you can support us by:
|
||||
If frp helps you a lot, you can support us by:
|
||||
|
||||
frp QQ group: 606194980
|
||||
|
||||
|
||||
782
README_zh.md
782
README_zh.md
@@ -1,781 +1,33 @@
|
||||
# frp
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
[](https://github.com/fatedier/frp/releases)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。
|
||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||
|
||||
## 目录
|
||||
## 为什么使用 frp ?
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
||||
|
||||
* [开发状态](#开发状态)
|
||||
* [架构](#架构)
|
||||
* [使用示例](#使用示例)
|
||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||
* [点对点内网穿透](#点对点内网穿透)
|
||||
* [功能说明](#功能说明)
|
||||
* [配置文件](#配置文件)
|
||||
* [配置文件模版渲染](#配置文件模版渲染)
|
||||
* [Dashboard](#dashboard)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||
* [客户端查看代理状态](#客户端查看代理状态)
|
||||
* [端口白名单](#端口白名单)
|
||||
* [端口复用](#端口复用)
|
||||
* [TCP 多路复用](#tcp-多路复用)
|
||||
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
|
||||
* [连接池](#连接池)
|
||||
* [负载均衡](#负载均衡)
|
||||
* [健康检查](#健康检查)
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
||||
* [获取用户真实 IP](#获取用户真实-ip)
|
||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||
* [自定义二级域名](#自定义二级域名)
|
||||
* [URL 路由](#url-路由)
|
||||
* [通过代理连接 frps](#通过代理连接-frps)
|
||||
* [范围端口映射](#范围端口映射)
|
||||
* [插件](#插件)
|
||||
* [开发计划](#开发计划)
|
||||
* [为 frp 做贡献](#为-frp-做贡献)
|
||||
* [捐助](#捐助)
|
||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||
* [微信支付捐赠](#微信支付捐赠)
|
||||
* [Paypal 捐赠](#paypal-捐赠)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
* 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。
|
||||
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。
|
||||
* 代理组间的负载均衡。
|
||||
* 端口复用,多个服务通过同一个服务端端口暴露。
|
||||
* 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
|
||||
* 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。
|
||||
* 服务端和客户端 UI 页面。
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 仍然处于开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
frp 目前已被很多公司广泛用于测试、生产环境。
|
||||
|
||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
**目前的交互协议可能随时改变,不保证向后兼容,升级新版本时需要注意公告说明同时升级服务端和客户端。**
|
||||
## 文档
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||
|
||||
## 使用示例
|
||||
|
||||
根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。
|
||||
|
||||
将 **frps** 及 **frps.ini** 放到具有公网 IP 的机器上。
|
||||
|
||||
将 **frpc** 及 **frpc.ini** 放到处于内网环境的机器上。
|
||||
|
||||
### 通过 ssh 访问公司内网机器
|
||||
|
||||
1. 修改 frps.ini 文件,这里使用了最简化的配置:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x;
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### 通过自定义域名访问部署于内网的 web 服务
|
||||
|
||||
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP,无法将域名解析到本地的机器,通过 frp 就可以实现这一功能,以下示例为 http 服务,https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port, type 设置为 https 即可。
|
||||
|
||||
1. 修改 frps.ini 文件,设置 http 访问端口为 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
vhost_http_port = 8080
|
||||
```
|
||||
|
||||
2. 启动 frps;
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,假设 frps 所在的服务器的 IP 为 x.x.x.x,local_port 为本地机器上 web 服务对应的端口, 绑定自定义域名 `www.yourdomain.com`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = www.yourdomain.com
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 将 `www.yourdomain.com` 的域名 A 记录解析到 IP `x.x.x.x`,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。
|
||||
|
||||
6. 通过浏览器访问 `http://www.yourdomain.com:8080` 即可访问到处于内网机器上的 web 服务。
|
||||
|
||||
### 转发 DNS 查询请求
|
||||
|
||||
DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。
|
||||
|
||||
1. 修改 frps.ini 文件:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,设置 frps 所在服务器的 IP 为 x.x.x.x,转发到 Google 的 DNS 查询服务器 `8.8.8.8` 的 udp 53 端口:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 8.8.8.8
|
||||
local_port = 53
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.google.com`
|
||||
|
||||
### 转发 Unix域套接字
|
||||
|
||||
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 启动 frpc,启用 `unix_domain_socket` 插件,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
2. 通过 curl 命令查看 docker 版本信息
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### 对外提供简单的文件访问服务
|
||||
|
||||
通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 启动 frpc,启用 `static_file` 插件,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_static_file]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = static_file
|
||||
# 要对外暴露的文件目录
|
||||
plugin_local_path = /tmp/file
|
||||
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||
|
||||
### 安全地暴露内网服务
|
||||
|
||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||
|
||||
使用 **stcp(secret tcp)** 类型的代理可以避免让任何人都能访问到要穿透的服务,但是访问者也需要运行另外一个 frpc。
|
||||
|
||||
以下示例将会创建一个只有自己能访问到的 ssh 服务代理。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[secret_ssh]
|
||||
type = stcp
|
||||
# 只有 sk 一致的用户才能访问到此服务
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
2. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[secret_ssh_visitor]
|
||||
type = stcp
|
||||
# stcp 的访问者
|
||||
role = visitor
|
||||
# 要访问的 stcp 代理的名字
|
||||
server_name = secret_ssh
|
||||
sk = abcdefg
|
||||
# 绑定本地端口用于访问 ssh 服务
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 6000
|
||||
```
|
||||
|
||||
3. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||
|
||||
`ssh -oPort=6000 test@127.0.0.1`
|
||||
|
||||
### 点对点内网穿透
|
||||
|
||||
frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。
|
||||
|
||||
使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。
|
||||
|
||||
目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。
|
||||
|
||||
1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端:
|
||||
|
||||
```ini
|
||||
bind_udp_port = 7001
|
||||
```
|
||||
|
||||
2. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[p2p_ssh]
|
||||
type = xtcp
|
||||
# 只有 sk 一致的用户才能访问到此服务
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
3. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[p2p_ssh_visitor]
|
||||
type = xtcp
|
||||
# xtcp 的访问者
|
||||
role = visitor
|
||||
# 要访问的 xtcp 代理的名字
|
||||
server_name = p2p_ssh
|
||||
sk = abcdefg
|
||||
# 绑定本地端口用于访问 ssh 服务
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 6000
|
||||
```
|
||||
|
||||
4. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||
|
||||
`ssh -oPort=6000 test@127.0.0.1`
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 配置文件
|
||||
|
||||
由于 frp 目前支持的功能和配置项较多,未在文档中列出的功能可以从完整的示例配置文件中发现。
|
||||
|
||||
[frps 完整配置文件](./conf/frps_full.ini)
|
||||
|
||||
[frpc 完整配置文件](./conf/frpc_full.ini)
|
||||
|
||||
### 配置文件模版渲染
|
||||
|
||||
配置文件支持使用系统环境变量进行模版渲染,模版格式采用 Go 的标准格式。
|
||||
|
||||
示例配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = {{ .Envs.FRP_SERVER_ADDR }}
|
||||
server_port = 7000
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
|
||||
```
|
||||
|
||||
启动 frpc 程序:
|
||||
|
||||
```
|
||||
export FRP_SERVER_ADDR="x.x.x.x"
|
||||
export FRP_SSH_REMOTE_PORT="6000"
|
||||
./frpc -c ./frpc.ini
|
||||
```
|
||||
|
||||
frpc 会自动使用环境变量渲染配置文件模版,所有环境变量需要以 `.Envs` 为前缀。
|
||||
|
||||
### Dashboard
|
||||
|
||||
通过浏览器查看 frp 的状态以及代理统计信息展示。
|
||||
|
||||
**注:Dashboard 尚未针对大量的 proxy 数据展示做优化,如果出现 Dashboard 访问较慢的情况,请不要启用此功能。**
|
||||
|
||||
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
# dashboard 用户名密码,默认都为 admin
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
```
|
||||
|
||||
打开浏览器通过 `http://[server_addr]:7500` 访问 dashboard 界面,用户名密码默认为 `admin`。
|
||||
|
||||

|
||||
|
||||
### 身份验证
|
||||
|
||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||
|
||||
### 加密与压缩
|
||||
|
||||
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
|
||||
|
||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||
|
||||
### 客户端热加载配置文件
|
||||
|
||||
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
||||
|
||||
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
```
|
||||
|
||||
之后执行重启命令:
|
||||
|
||||
`frpc reload -c ./frpc.ini`
|
||||
|
||||
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
|
||||
|
||||
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
|
||||
|
||||
### 客户端查看代理状态
|
||||
|
||||
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。
|
||||
|
||||
### 端口白名单
|
||||
|
||||
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
`allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
|
||||
|
||||
### 端口复用
|
||||
|
||||
目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口,frps 会对连接的协议进行分析,之后进行不同的处理。
|
||||
|
||||
例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。
|
||||
|
||||
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
|
||||
|
||||
### TCP 多路复用
|
||||
|
||||
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
|
||||
|
||||
该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致:
|
||||
|
||||
```ini
|
||||
# frps.ini 和 frpc.ini 中
|
||||
[common]
|
||||
tcp_mux = false
|
||||
```
|
||||
|
||||
### 底层通信可选 kcp 协议
|
||||
|
||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
|
||||
开启 kcp 协议支持:
|
||||
|
||||
1. 在 frps.ini 中启用 kcp 协议支持,指定一个 udp 端口用于接收客户端请求:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
# kcp 绑定的是 udp 端口,可以和 bind_port 一样
|
||||
kcp_bind_port = 7000
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 指定需要使用的协议类型,目前只支持 tcp 和 kcp。其他代理配置不需要变更:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
# server_port 指定为 frps 的 kcp_bind_port
|
||||
server_port = 7000
|
||||
protocol = kcp
|
||||
```
|
||||
|
||||
3. 像之前一样使用 frp,需要注意开放相关机器上的 udp 的端口的访问权限。
|
||||
|
||||
### 连接池
|
||||
|
||||
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
|
||||
|
||||
这一功能比较适合有大量短连接请求时开启。
|
||||
|
||||
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,客户端设置超过此配置后会被调整到当前值:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
max_pool_count = 5
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 中为客户端启用连接池,指定预创建连接的数量:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
pool_count = 1
|
||||
```
|
||||
|
||||
### 负载均衡
|
||||
|
||||
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
||||
目前只支持 tcp 类型的 proxy。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[test1]
|
||||
type = tcp
|
||||
local_port = 8080
|
||||
remote_port = 80
|
||||
group = web
|
||||
group_key = 123
|
||||
|
||||
[test2]
|
||||
type = tcp
|
||||
local_port = 8081
|
||||
remote_port = 80
|
||||
group = web
|
||||
group_key = 123
|
||||
```
|
||||
|
||||
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
|
||||
|
||||
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
||||
|
||||
### 健康检查
|
||||
|
||||
通过给 proxy 加上健康检查的功能,可以在要反向代理的服务出现故障时,将这个服务从 frps 中摘除,搭配负载均衡的功能,可以用来实现高可用的架构,避免服务单点故障。
|
||||
|
||||
在每一个 proxy 的配置下加上 `health_check_type = {type}` 来启用健康检查功能。
|
||||
|
||||
**type** 目前可选 tcp 和 http。
|
||||
|
||||
tcp 只要能够建立连接则认为服务正常,http 会发送一个 http 请求,服务需要返回 2xx 的状态码才会被认为正常。
|
||||
|
||||
tcp 示例配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[test1]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
# 启用健康检查,类型为 tcp
|
||||
health_check_type = tcp
|
||||
# 建立连接超时时间为 3 秒
|
||||
health_check_timeout_s = 3
|
||||
# 连续 3 次检查失败,此 proxy 会被摘除
|
||||
health_check_max_failed = 3
|
||||
# 每隔 10 秒进行一次健康检查
|
||||
health_check_interval_s = 10
|
||||
```
|
||||
|
||||
http 示例配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
# 启用健康检查,类型为 http
|
||||
health_check_type = http
|
||||
# 健康检查发送 http 请求的 url,后端服务需要返回 2xx 的 http 状态码
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 10
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
```
|
||||
|
||||
### 修改 Host Header
|
||||
|
||||
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
|
||||
|
||||
### 设置 HTTP 请求的 header
|
||||
|
||||
对于 `type = http` 的代理,可以设置在转发中动态添加的 header 参数。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
header_X-From-Where = frp
|
||||
```
|
||||
|
||||
对于参数配置中所有以 `header_` 开头的参数(支持同时配置多个),都会被添加到 http 请求的 header 中,根据如上的配置,会在请求的 header 中加上 `X-From-Where: frp`。
|
||||
|
||||
### 获取用户真实 IP
|
||||
|
||||
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
|
||||
|
||||
### 通过密码保护你的 web 服务
|
||||
|
||||
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
|
||||
|
||||
frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通过用户名和密码才能访问到你的服务。
|
||||
|
||||
该功能目前仅限于 http 类型的代理,需要在 frpc 的代理配置中添加用户名和密码的设置。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
http_user = abc
|
||||
http_pwd = abc
|
||||
```
|
||||
|
||||
通过浏览器访问 `http://test.yourdomain.com`,需要输入配置的用户名和密码才能访问。
|
||||
|
||||
### 自定义二级域名
|
||||
|
||||
在多人同时使用一个 frps 时,通过自定义二级域名的方式来使用会更加方便。
|
||||
|
||||
通过在 frps 的配置文件中配置 `subdomain_host`,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 `custom_domains`,而是配置一个 `subdomain` 参数。
|
||||
|
||||
只需要将 `*.{subdomain_host}` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
subdomain_host = frps.com
|
||||
```
|
||||
|
||||
将泛域名 `*.frps.com` 解析到 frps 所在服务器的 IP 地址。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
subdomain = test
|
||||
```
|
||||
|
||||
frps 和 frpc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
|
||||
|
||||
**注:如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。**
|
||||
|
||||
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
|
||||
|
||||
### URL 路由
|
||||
|
||||
frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
|
||||
|
||||
通过配置文件中的 `locations` 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 `locations = /news`,则所有 URL 以 `/news` 开头的请求都会被转发到这个服务。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_port = 81
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /news,/about
|
||||
```
|
||||
|
||||
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。
|
||||
|
||||
### 通过代理连接 frps
|
||||
|
||||
在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。
|
||||
|
||||
可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。
|
||||
|
||||
仅在 `protocol = tcp` 时生效。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
```
|
||||
|
||||
### 范围端口映射
|
||||
|
||||
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
|
||||
|
||||
这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy,每一个 proxy 以数字为后缀命名。
|
||||
|
||||
例如要映射本地 6000-6005, 6007 这6个端口,主要配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[range:test_tcp]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6000-6006,6007
|
||||
remote_port = 6000-6006,6007
|
||||
```
|
||||
|
||||
实际连接成功后会创建 8 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
|
||||
|
||||
### 插件
|
||||
|
||||
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||
|
||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
|
||||
|
||||
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
||||
|
||||
使用 **http_proxy** 插件的示例:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
|
||||
|
||||
## 开发计划
|
||||
|
||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||
|
||||
* frps 记录 http 请求日志。
|
||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
||||
|
||||
## 为 frp 做贡献
|
||||
|
||||
@@ -786,7 +38,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
* 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
|
||||
* 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。
|
||||
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
|
||||
* 如果你有任何其他方面的问题,欢迎反馈至 fatedier@gmail.com 共同交流。
|
||||
* 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。
|
||||
|
||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
||||
|
||||
@@ -794,11 +46,9 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
|
||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||
|
||||
frp 交流群:606194980 (QQ 群号)
|
||||
|
||||
### 知识星球
|
||||
|
||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
|
||||

|
||||
|
||||
|
||||
3
Release.md
Normal file
3
Release.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### New
|
||||
|
||||
* Command line parameters support `enable_prometheus`.
|
||||
@@ -55,6 +55,7 @@ func ReadFile(file string) (content string, err error) {
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
defer file.Close()
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
@@ -65,6 +66,7 @@ func ReadFile(file string) (content string, err error) {
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
defer file.Close()
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
|
||||
@@ -21,8 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -36,8 +35,8 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
|
||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// api, see dashboard_api.go
|
||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
@@ -47,7 +46,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
|
||||
// view
|
||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
@@ -23,9 +23,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
@@ -47,7 +46,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -55,7 +54,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -63,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -83,12 +82,13 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
type StatusResp struct {
|
||||
Tcp []ProxyStatusResp `json:"tcp"`
|
||||
Udp []ProxyStatusResp `json:"udp"`
|
||||
Http []ProxyStatusResp `json:"http"`
|
||||
Https []ProxyStatusResp `json:"https"`
|
||||
Stcp []ProxyStatusResp `json:"stcp"`
|
||||
Xtcp []ProxyStatusResp `json:"xtcp"`
|
||||
TCP []ProxyStatusResp `json:"tcp"`
|
||||
UDP []ProxyStatusResp `json:"udp"`
|
||||
HTTP []ProxyStatusResp `json:"http"`
|
||||
HTTPS []ProxyStatusResp `json:"https"`
|
||||
STCP []ProxyStatusResp `json:"stcp"`
|
||||
XTCP []ProxyStatusResp `json:"xtcp"`
|
||||
SUDP []ProxyStatusResp `json:"sudp"`
|
||||
}
|
||||
|
||||
type ProxyStatusResp struct {
|
||||
@@ -107,53 +107,58 @@ func (a ByProxyStatusResp) Len() int { return len(a) }
|
||||
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
|
||||
|
||||
func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
||||
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
Type: status.Type,
|
||||
Status: status.Status,
|
||||
Status: status.Phase,
|
||||
Err: status.Err,
|
||||
}
|
||||
switch cfg := status.Cfg.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
case *config.TCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
case *config.UDPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
case *config.HTTPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
case *config.HttpsProxyConf:
|
||||
case *config.HTTPSProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
case *config.StcpProxyConf:
|
||||
case *config.STCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
case *config.XtcpProxyConf:
|
||||
case *config.XTCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
case *config.SUDPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
}
|
||||
@@ -166,12 +171,13 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
buf []byte
|
||||
res StatusResp
|
||||
)
|
||||
res.Tcp = make([]ProxyStatusResp, 0)
|
||||
res.Udp = make([]ProxyStatusResp, 0)
|
||||
res.Http = make([]ProxyStatusResp, 0)
|
||||
res.Https = make([]ProxyStatusResp, 0)
|
||||
res.Stcp = make([]ProxyStatusResp, 0)
|
||||
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||
res.TCP = make([]ProxyStatusResp, 0)
|
||||
res.UDP = make([]ProxyStatusResp, 0)
|
||||
res.HTTP = make([]ProxyStatusResp, 0)
|
||||
res.HTTPS = make([]ProxyStatusResp, 0)
|
||||
res.STCP = make([]ProxyStatusResp, 0)
|
||||
res.XTCP = make([]ProxyStatusResp, 0)
|
||||
res.SUDP = make([]ProxyStatusResp, 0)
|
||||
|
||||
log.Info("Http request [/api/status]")
|
||||
defer func() {
|
||||
@@ -184,25 +190,28 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
for _, status := range ps {
|
||||
switch status.Type {
|
||||
case "tcp":
|
||||
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
|
||||
res.TCP = append(res.TCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "udp":
|
||||
res.Udp = append(res.Udp, NewProxyStatusResp(status))
|
||||
res.UDP = append(res.UDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "http":
|
||||
res.Http = append(res.Http, NewProxyStatusResp(status))
|
||||
res.HTTP = append(res.HTTP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "https":
|
||||
res.Https = append(res.Https, NewProxyStatusResp(status))
|
||||
res.HTTPS = append(res.HTTPS, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "stcp":
|
||||
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
|
||||
res.STCP = append(res.STCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "xtcp":
|
||||
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
|
||||
res.XTCP = append(res.XTCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "sudp":
|
||||
res.SUDP = append(res.SUDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
}
|
||||
sort.Sort(ByProxyStatusResp(res.Tcp))
|
||||
sort.Sort(ByProxyStatusResp(res.Udp))
|
||||
sort.Sort(ByProxyStatusResp(res.Http))
|
||||
sort.Sort(ByProxyStatusResp(res.Https))
|
||||
sort.Sort(ByProxyStatusResp(res.Stcp))
|
||||
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||
sort.Sort(ByProxyStatusResp(res.TCP))
|
||||
sort.Sort(ByProxyStatusResp(res.UDP))
|
||||
sort.Sort(ByProxyStatusResp(res.HTTP))
|
||||
sort.Sort(ByProxyStatusResp(res.HTTPS))
|
||||
sort.Sort(ByProxyStatusResp(res.STCP))
|
||||
sort.Sort(ByProxyStatusResp(res.XTCP))
|
||||
sort.Sort(ByProxyStatusResp(res.SUDP))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -219,14 +228,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.CfgFile == "" {
|
||||
if svr.cfgFile == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "frpc has no config file path"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -277,7 +286,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// get token from origin content
|
||||
token := ""
|
||||
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||
b, err := ioutil.ReadFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -316,7 +325,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
content = strings.Join(newRows, "\n")
|
||||
|
||||
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
||||
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
|
||||
if err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
|
||||
@@ -15,18 +15,22 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
@@ -35,17 +39,17 @@ import (
|
||||
|
||||
type Control struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runId string
|
||||
runID string
|
||||
|
||||
// manage all proxies
|
||||
pxyCfgs map[string]config.ProxyConf
|
||||
pm *proxy.ProxyManager
|
||||
pm *proxy.Manager
|
||||
|
||||
// manage all visitors
|
||||
vm *VisitorManager
|
||||
|
||||
// control connection
|
||||
conn frpNet.Conn
|
||||
conn net.Conn
|
||||
|
||||
// tcp stream multiplexing, if enabled
|
||||
session *fmux.Session
|
||||
@@ -64,18 +68,37 @@ type Control struct {
|
||||
// last time got the Pong message
|
||||
lastPong time.Time
|
||||
|
||||
// The client configuration
|
||||
clientCfg config.ClientCommonConf
|
||||
|
||||
readerShutdown *shutdown.Shutdown
|
||||
writerShutdown *shutdown.Shutdown
|
||||
msgHandlerShutdown *shutdown.Shutdown
|
||||
|
||||
// The UDP port that the server is listening on
|
||||
serverUDPPort int
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
log.Logger
|
||||
xl *xlog.Logger
|
||||
|
||||
// service context
|
||||
ctx context.Context
|
||||
|
||||
// sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
}
|
||||
|
||||
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
|
||||
func NewControl(ctx context.Context, runID string, conn net.Conn, session *fmux.Session,
|
||||
clientCfg config.ClientCommonConf,
|
||||
pxyCfgs map[string]config.ProxyConf,
|
||||
visitorCfgs map[string]config.VisitorConf,
|
||||
serverUDPPort int,
|
||||
authSetter auth.Setter) *Control {
|
||||
|
||||
// new xlog instance
|
||||
ctl := &Control{
|
||||
runId: runId,
|
||||
runID: runID,
|
||||
conn: conn,
|
||||
session: session,
|
||||
pxyCfgs: pxyCfgs,
|
||||
@@ -83,14 +106,18 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
|
||||
readCh: make(chan msg.Message, 100),
|
||||
closedCh: make(chan struct{}),
|
||||
closedDoneCh: make(chan struct{}),
|
||||
clientCfg: clientCfg,
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
msgHandlerShutdown: shutdown.New(),
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
serverUDPPort: serverUDPPort,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
authSetter: authSetter,
|
||||
}
|
||||
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
|
||||
|
||||
ctl.vm = NewVisitorManager(ctl)
|
||||
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
return ctl
|
||||
}
|
||||
@@ -107,46 +134,60 @@ func (ctl *Control) Run() {
|
||||
}
|
||||
|
||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||
xl := ctl.xl
|
||||
workConn, err := ctl.connectServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m := &msg.NewWorkConn{
|
||||
RunId: ctl.runId,
|
||||
RunID: ctl.runID,
|
||||
}
|
||||
if err = ctl.authSetter.SetNewWorkConn(m); err != nil {
|
||||
xl.Warn("error during NewWorkConn authentication: %v", err)
|
||||
return
|
||||
}
|
||||
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||
ctl.Warn("work connection write to server error: %v", err)
|
||||
xl.Warn("work connection write to server error: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
var startMsg msg.StartWorkConn
|
||||
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
||||
ctl.Error("work connection closed, %v", err)
|
||||
xl.Error("work connection closed before response StartWorkConn message: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
if startMsg.Error != "" {
|
||||
xl.Error("StartWorkConn contains error: %s", startMsg.Error)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||
|
||||
// dispatch this work connection to related proxy
|
||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn)
|
||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||
}
|
||||
|
||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
xl := ctl.xl
|
||||
// Server will return NewProxyResp message to each NewProxy message.
|
||||
// Start a new proxy handler if no error got
|
||||
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
||||
if err != nil {
|
||||
ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
||||
xl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
||||
} else {
|
||||
ctl.Info("[%s] start proxy success", inMsg.ProxyName)
|
||||
xl.Info("[%s] start proxy success", inMsg.ProxyName)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) Close() error {
|
||||
ctl.pm.Close()
|
||||
ctl.conn.Close()
|
||||
ctl.vm.Close()
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,20 +197,35 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||
}
|
||||
|
||||
// connectServer return a new connection to frps
|
||||
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
xl := ctl.xl
|
||||
if ctl.clientCfg.TCPMux {
|
||||
stream, errRet := ctl.session.OpenStream()
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
ctl.Warn("start new connection to server error: %v", err)
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
}
|
||||
conn = frpNet.WrapConn(stream)
|
||||
conn = stream
|
||||
} else {
|
||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
if ctl.clientCfg.TLSEnable {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
ctl.clientCfg.TLSCertFile,
|
||||
ctl.clientCfg.TLSKeyFile,
|
||||
ctl.clientCfg.TLSTrustedCaFile,
|
||||
ctl.clientCfg.ServerAddr)
|
||||
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration when connecting to server, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
ctl.Warn("start new connection to server error: %v", err)
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -178,64 +234,68 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
|
||||
// reader read all messages from frps and send to readCh
|
||||
func (ctl *Control) reader() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.Error("panic error: %v", err)
|
||||
ctl.Error(string(debug.Stack()))
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.readerShutdown.Done()
|
||||
defer close(ctl.closedCh)
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
m, err := msg.ReadMsg(encReader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
ctl.Debug("read from control connection EOF")
|
||||
return
|
||||
} else {
|
||||
ctl.Warn("read error: %v", err)
|
||||
xl.Debug("read from control connection EOF")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctl.readCh <- m
|
||||
xl.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
ctl.readCh <- m
|
||||
}
|
||||
}
|
||||
|
||||
// writer writes messages got from sendCh to frps
|
||||
func (ctl *Control) writer() {
|
||||
xl := ctl.xl
|
||||
defer ctl.writerShutdown.Done()
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
xl.Error("crypto new writer error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
for {
|
||||
if m, ok := <-ctl.sendCh; !ok {
|
||||
ctl.Info("control writer is closing")
|
||||
m, ok := <-ctl.sendCh
|
||||
if !ok {
|
||||
xl.Info("control writer is closing")
|
||||
return
|
||||
}
|
||||
|
||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||
xl.Warn("write message to control connection error: %v", err)
|
||||
return
|
||||
} else {
|
||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||
ctl.Warn("write message to control connection error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// msgHandler handles all channel events and do corresponding operations.
|
||||
func (ctl *Control) msgHandler() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.Error("panic error: %v", err)
|
||||
ctl.Error(string(debug.Stack()))
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
@@ -246,11 +306,16 @@ func (ctl *Control) msgHandler() {
|
||||
select {
|
||||
case <-hbSend.C:
|
||||
// send heartbeat to server
|
||||
ctl.Debug("send heartbeat to server")
|
||||
ctl.sendCh <- &msg.Ping{}
|
||||
xl.Debug("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
if err := ctl.authSetter.SetPing(pingMsg); err != nil {
|
||||
xl.Warn("error during ping authentication: %v", err)
|
||||
return
|
||||
}
|
||||
ctl.sendCh <- pingMsg
|
||||
case <-hbCheck.C:
|
||||
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
|
||||
ctl.Warn("heartbeat timeout")
|
||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
ctl.conn.Close()
|
||||
return
|
||||
@@ -266,8 +331,13 @@ func (ctl *Control) msgHandler() {
|
||||
case *msg.NewProxyResp:
|
||||
ctl.HandleNewProxyResp(m)
|
||||
case *msg.Pong:
|
||||
if m.Error != "" {
|
||||
xl.Error("Pong contains error: %s", m.Error)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
ctl.lastPong = time.Now()
|
||||
ctl.Debug("receive heartbeat from server")
|
||||
xl.Debug("receive heartbeat from server")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,6 +363,9 @@ func (ctl *Control) worker() {
|
||||
ctl.vm.Close()
|
||||
|
||||
close(ctl.closedDoneCh)
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package event
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
type EventType int
|
||||
type Type int
|
||||
|
||||
const (
|
||||
EvStartProxy EventType = iota
|
||||
EvStartProxy Type = iota
|
||||
EvCloseProxy
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ var (
|
||||
ErrPayloadType = errors.New("error payload type")
|
||||
)
|
||||
|
||||
type EventHandler func(evType EventType, payload interface{}) error
|
||||
type Handler func(evType Type, payload interface{}) error
|
||||
|
||||
type StartProxyPayload struct {
|
||||
NewProxyMsg *msg.NewProxy
|
||||
|
||||
@@ -18,18 +18,20 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHealthCheckType = errors.New("error health check type")
|
||||
)
|
||||
|
||||
type HealthCheckMonitor struct {
|
||||
type Monitor struct {
|
||||
checkType string
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
@@ -48,12 +50,12 @@ type HealthCheckMonitor struct {
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
l log.Logger
|
||||
}
|
||||
|
||||
func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string,
|
||||
statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
|
||||
func NewMonitor(ctx context.Context, checkType string,
|
||||
intervalS int, timeoutS int, maxFailedTimes int,
|
||||
addr string, url string,
|
||||
statusNormalFn func(), statusFailedFn func()) *Monitor {
|
||||
|
||||
if intervalS <= 0 {
|
||||
intervalS = 10
|
||||
@@ -64,8 +66,8 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai
|
||||
if maxFailedTimes <= 0 {
|
||||
maxFailedTimes = 1
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &HealthCheckMonitor{
|
||||
newctx, cancel := context.WithCancel(ctx)
|
||||
return &Monitor{
|
||||
checkType: checkType,
|
||||
interval: time.Duration(intervalS) * time.Second,
|
||||
timeout: time.Duration(timeoutS) * time.Second,
|
||||
@@ -75,31 +77,28 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai
|
||||
statusOK: false,
|
||||
statusNormalFn: statusNormalFn,
|
||||
statusFailedFn: statusFailedFn,
|
||||
ctx: ctx,
|
||||
ctx: newctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
|
||||
monitor.l = l
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) Start() {
|
||||
func (monitor *Monitor) Start() {
|
||||
go monitor.checkWorker()
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) Stop() {
|
||||
func (monitor *Monitor) Stop() {
|
||||
monitor.cancel()
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||
func (monitor *Monitor) checkWorker() {
|
||||
xl := xlog.FromContextSafe(monitor.ctx)
|
||||
for {
|
||||
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||
err := monitor.doCheck(ctx)
|
||||
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||
err := monitor.doCheck(doCtx)
|
||||
|
||||
// check if this monitor has been closed
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-monitor.ctx.Done():
|
||||
cancel()
|
||||
return
|
||||
default:
|
||||
@@ -107,25 +106,17 @@ func (monitor *HealthCheckMonitor) checkWorker() {
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if monitor.l != nil {
|
||||
monitor.l.Trace("do one health check success")
|
||||
}
|
||||
xl.Trace("do one health check success")
|
||||
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
||||
if monitor.l != nil {
|
||||
monitor.l.Info("health check status change to success")
|
||||
}
|
||||
xl.Info("health check status change to success")
|
||||
monitor.statusOK = true
|
||||
monitor.statusNormalFn()
|
||||
}
|
||||
} else {
|
||||
if monitor.l != nil {
|
||||
monitor.l.Warn("do one health check failed: %v", err)
|
||||
}
|
||||
xl.Warn("do one health check failed: %v", err)
|
||||
monitor.failedTimes++
|
||||
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
||||
if monitor.l != nil {
|
||||
monitor.l.Warn("health check status change to failed")
|
||||
}
|
||||
xl.Warn("health check status change to failed")
|
||||
monitor.statusOK = false
|
||||
monitor.statusFailedFn()
|
||||
}
|
||||
@@ -135,18 +126,18 @@ func (monitor *HealthCheckMonitor) checkWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error {
|
||||
func (monitor *Monitor) doCheck(ctx context.Context) error {
|
||||
switch monitor.checkType {
|
||||
case "tcp":
|
||||
return monitor.doTcpCheck(ctx)
|
||||
return monitor.doTCPCheck(ctx)
|
||||
case "http":
|
||||
return monitor.doHttpCheck(ctx)
|
||||
return monitor.doHTTPCheck(ctx)
|
||||
default:
|
||||
return ErrHealthCheckType
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
|
||||
func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
|
||||
// if tcp address is not specified, always return nil
|
||||
if monitor.addr == "" {
|
||||
return nil
|
||||
@@ -161,7 +152,7 @@ func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
||||
func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
|
||||
req, err := http.NewRequest("GET", monitor.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -170,6 +161,8 @@ func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||
|
||||
@@ -16,23 +16,30 @@ package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/plugin"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Proxy defines how to handle work connections for different proxy type.
|
||||
@@ -40,66 +47,91 @@ type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(conn frpNet.Conn)
|
||||
InWorkConn(net.Conn, *msg.StartWorkConn)
|
||||
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
||||
var limiter *rate.Limiter
|
||||
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
||||
if limitBytes > 0 {
|
||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||
}
|
||||
|
||||
baseProxy := BaseProxy{
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||
clientCfg: clientCfg,
|
||||
serverUDPPort: serverUDPPort,
|
||||
limiter: limiter,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
pxy = &TcpProxy{
|
||||
case *config.TCPProxyConf:
|
||||
pxy = &TCPProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
pxy = &UdpProxy{
|
||||
case *config.TCPMuxProxyConf:
|
||||
pxy = &TCPMuxProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
case *config.UDPProxyConf:
|
||||
pxy = &UDPProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
case *config.HTTPProxyConf:
|
||||
pxy = &HTTPProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.StcpProxyConf:
|
||||
pxy = &StcpProxy{
|
||||
case *config.HTTPSProxyConf:
|
||||
pxy = &HTTPSProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
pxy = &XtcpProxy{
|
||||
case *config.STCPProxyConf:
|
||||
pxy = &STCPProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XTCPProxyConf:
|
||||
pxy = &XTCPProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.SUDPProxyConf:
|
||||
pxy = &SUDPProxy{
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
closed bool
|
||||
clientCfg config.ClientCommonConf
|
||||
serverUDPPort int
|
||||
limiter *rate.Limiter
|
||||
|
||||
mu sync.RWMutex
|
||||
xl *xlog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxy struct {
|
||||
type TCPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.TcpProxyConf
|
||||
cfg *config.TCPProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Run() (err error) {
|
||||
func (pxy *TCPProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
@@ -109,26 +141,55 @@ func (pxy *TcpProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Close() {
|
||||
func (pxy *TCPProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// TCP Multiplexer
|
||||
type TCPMuxProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.TCPMuxProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *TCPMuxProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TCPMuxProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
type HTTPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
cfg *config.HTTPProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Run() (err error) {
|
||||
func (pxy *HTTPProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
@@ -138,26 +199,26 @@ func (pxy *HttpProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Close() {
|
||||
func (pxy *HTTPProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
func (pxy *HTTPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
type HTTPSProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
cfg *config.HTTPSProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Run() (err error) {
|
||||
func (pxy *HTTPSProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
@@ -167,26 +228,26 @@ func (pxy *HttpsProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
func (pxy *HTTPSProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
func (pxy *HTTPSProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// STCP
|
||||
type StcpProxy struct {
|
||||
type STCPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.StcpProxyConf
|
||||
cfg *config.STCPProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) Run() (err error) {
|
||||
func (pxy *STCPProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
@@ -196,26 +257,26 @@ func (pxy *StcpProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) Close() {
|
||||
func (pxy *STCPProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
conn, []byte(pxy.clientCfg.Token), m)
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XtcpProxy struct {
|
||||
type XTCPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.XtcpProxyConf
|
||||
cfg *config.XTCPProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) Run() (err error) {
|
||||
func (pxy *XTCPProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
@@ -225,18 +286,19 @@ func (pxy *XtcpProxy) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) Close() {
|
||||
func (pxy *XTCPProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer conn.Close()
|
||||
var natHoleSidMsg msg.NatHoleSid
|
||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||
if err != nil {
|
||||
pxy.Error("xtcp read from workConn error: %v", err)
|
||||
xl.Error("xtcp read from workConn error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -245,13 +307,13 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
Sid: natHoleSidMsg.Sid,
|
||||
}
|
||||
raddr, _ := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||
fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
|
||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||
defer clientConn.Close()
|
||||
|
||||
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||
if err != nil {
|
||||
pxy.Error("send natHoleClientMsg to server error: %v", err)
|
||||
xl.Error("send natHoleClientMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -262,73 +324,138 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
buf := pool.GetBuf(1024)
|
||||
n, err := clientConn.Read(buf)
|
||||
if err != nil {
|
||||
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||
xl.Error("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||
if err != nil {
|
||||
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||
xl.Error("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
clientConn.SetReadDeadline(time.Time{})
|
||||
clientConn.Close()
|
||||
|
||||
if natHoleRespMsg.Error != "" {
|
||||
pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Send sid to visitor udp address.
|
||||
time.Sleep(time.Second)
|
||||
// Send detect message
|
||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
}
|
||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
if err != nil {
|
||||
pxy.Error("resolve visitor udp address error: %v", err)
|
||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
return
|
||||
}
|
||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
xl.Trace("send all detect msg done")
|
||||
|
||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||
|
||||
// Listen for clientConn's address and wait for visitor connection
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
xl.Error("listen on visitorConn's local adress error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||
sidBuf := pool.GetBuf(1024)
|
||||
var uAddr *net.UDPAddr
|
||||
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||
if err != nil {
|
||||
xl.Warn("get sid from visitor error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Time{})
|
||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||
xl.Warn("incorrect sid from visitor")
|
||||
return
|
||||
}
|
||||
pool.PutBuf(sidBuf)
|
||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||
|
||||
kcpConn, err := frpNet.NewKCPConnFromUDP(lConn, false, uAddr.String())
|
||||
if err != nil {
|
||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||
if err != nil {
|
||||
pxy.Error("dial visitor udp address error: %v", err)
|
||||
xl.Error("create yamux server from kcp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||
|
||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Accept()
|
||||
if err != nil {
|
||||
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||
xl.Error("accept for yamux connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
|
||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||
muxConn, []byte(pxy.cfg.Sk), m)
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//uConn := ipv4.NewConn(tConn)
|
||||
//uConn.SetTTL(3)
|
||||
|
||||
tConn.Write(content)
|
||||
tConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxy struct {
|
||||
type UDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.UdpProxyConf
|
||||
cfg *config.UDPProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
readCh chan *msg.UdpPacket
|
||||
readCh chan *msg.UDPPacket
|
||||
|
||||
// include msg.UdpPacket and msg.Ping
|
||||
// include msg.UDPPacket and msg.Ping
|
||||
sendCh chan msg.Message
|
||||
workConn frpNet.Conn
|
||||
workConn net.Conn
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
|
||||
func (pxy *UDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Close() {
|
||||
func (pxy *UDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
|
||||
@@ -346,48 +473,69 @@ func (pxy *UdpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
pxy.sendCh = make(chan msg.Message, 1024)
|
||||
pxy.closed = false
|
||||
pxy.mu.Unlock()
|
||||
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) {
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
for {
|
||||
var udpMsg msg.UdpPacket
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
if errRet := errors.PanicToError(func() {
|
||||
pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
pxy.Info("writer goroutine for udp work connection closed")
|
||||
xl.Info("writer goroutine for udp work connection closed")
|
||||
}()
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UdpPacket:
|
||||
pxy.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.Ping:
|
||||
pxy.Trace("send ping message to udp workConn")
|
||||
xl.Trace("send ping message to udp workConn")
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
pxy.Error("udp work write error: %v", errRet)
|
||||
xl.Error("udp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -399,7 +547,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
pxy.Trace("heartbeat goroutine for udp work connection closed")
|
||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -408,24 +556,189 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
go heartbeatFn(pxy.workConn, pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
||||
|
||||
type SUDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.SUDPProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
return
|
||||
default:
|
||||
close(pxy.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
workConn := conn
|
||||
readCh := make(chan *msg.UDPPacket, 1024)
|
||||
sendCh := make(chan msg.Message, 1024)
|
||||
isClose := false
|
||||
|
||||
mu := &sync.Mutex{}
|
||||
|
||||
closeFn := func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if isClose {
|
||||
return
|
||||
}
|
||||
|
||||
isClose = true
|
||||
if workConn != nil {
|
||||
workConn.Close()
|
||||
}
|
||||
close(readCh)
|
||||
close(sendCh)
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
defer closeFn()
|
||||
|
||||
for {
|
||||
// first to check sudp proxy is closed or not
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
if errRet := errors.PanicToError(func() {
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
closeFn()
|
||||
xl.Info("writer goroutine for sudp work connection closed")
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
||||
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||
case *msg.Ping:
|
||||
xl.Trace("frpc send ping message to frpc visitor")
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("sudp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
closeFn()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
||||
return
|
||||
}
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(workConn, sendCh)
|
||||
go workConnReaderFn(workConn, readCh)
|
||||
go heartbeatFn(workConn, sendCh)
|
||||
|
||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
||||
|
||||
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||
xl := xlog.FromContextSafe(ctx)
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
err error
|
||||
)
|
||||
remote = workConn
|
||||
if limiter != nil {
|
||||
remote = frpIo.WrapReadWriteCloser(limit.NewReader(workConn, limiter), limit.NewWriter(workConn, limiter), func() error {
|
||||
return workConn.Close()
|
||||
})
|
||||
}
|
||||
|
||||
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
||||
baseInfo.UseEncryption, baseInfo.UseCompression)
|
||||
if baseInfo.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, encKey)
|
||||
if err != nil {
|
||||
workConn.Close()
|
||||
workConn.Error("create encryption stream error: %v", err)
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -433,23 +746,61 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
// check if we need to send proxy protocol info
|
||||
var extraInfo []byte
|
||||
if baseInfo.ProxyProtocolVersion != "" {
|
||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||
if m.DstAddr == "" {
|
||||
m.DstAddr = "127.0.0.1"
|
||||
}
|
||||
h := &pp.Header{
|
||||
Command: pp.PROXY,
|
||||
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||
SourcePort: m.SrcPort,
|
||||
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||
DestinationPort: m.DstPort,
|
||||
}
|
||||
|
||||
if strings.Contains(m.SrcAddr, ".") {
|
||||
h.TransportProtocol = pp.TCPv4
|
||||
} else {
|
||||
h.TransportProtocol = pp.TCPv6
|
||||
}
|
||||
|
||||
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||
h.Version = 1
|
||||
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||
h.Version = 2
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
h.WriteTo(buf)
|
||||
extraInfo = buf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
if proxyPlugin != nil {
|
||||
// if plugin is set, let plugin handle connections first
|
||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||
proxyPlugin.Handle(remote, workConn)
|
||||
workConn.Debug("handle by plugin finished")
|
||||
xl.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||
xl.Debug("handle by plugin finished")
|
||||
return
|
||||
} else {
|
||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
||||
if err != nil {
|
||||
workConn.Close()
|
||||
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
||||
return
|
||||
}
|
||||
|
||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
frpIo.Join(localConn, remote)
|
||||
workConn.Debug("join connections closed")
|
||||
}
|
||||
|
||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIP, localInfo.LocalPort))
|
||||
if err != nil {
|
||||
workConn.Close()
|
||||
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
|
||||
if len(extraInfo) > 0 {
|
||||
localConn.Write(extraInfo)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, remote)
|
||||
xl.Debug("join connections closed")
|
||||
}
|
||||
|
||||
@@ -1,40 +1,46 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/client/event"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type ProxyManager struct {
|
||||
type Manager struct {
|
||||
sendCh chan (msg.Message)
|
||||
proxies map[string]*ProxyWrapper
|
||||
proxies map[string]*Wrapper
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
logPrefix string
|
||||
log.Logger
|
||||
clientCfg config.ClientCommonConf
|
||||
|
||||
// The UDP port that the server is listening on
|
||||
serverUDPPort int
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
|
||||
return &ProxyManager{
|
||||
proxies: make(map[string]*ProxyWrapper),
|
||||
sendCh: msgSendCh,
|
||||
closed: false,
|
||||
logPrefix: logPrefix,
|
||||
Logger: log.NewPrefixLogger(logPrefix),
|
||||
func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager {
|
||||
return &Manager{
|
||||
sendCh: msgSendCh,
|
||||
proxies: make(map[string]*Wrapper),
|
||||
closed: false,
|
||||
clientCfg: clientCfg,
|
||||
serverUDPPort: serverUDPPort,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
|
||||
func (pm *Manager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
|
||||
pm.mu.RLock()
|
||||
pxy, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
@@ -49,27 +55,27 @@ func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Close() {
|
||||
func (pm *Manager) Close() {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
pxy.Stop()
|
||||
}
|
||||
pm.proxies = make(map[string]*ProxyWrapper)
|
||||
pm.proxies = make(map[string]*Wrapper)
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
|
||||
func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
|
||||
pm.mu.RLock()
|
||||
pw, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if ok {
|
||||
pw.InWorkConn(workConn)
|
||||
pw.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) HandleEvent(evType event.EventType, payload interface{}) error {
|
||||
func (pm *Manager) HandleEvent(evType event.Type, payload interface{}) error {
|
||||
var m msg.Message
|
||||
switch e := payload.(type) {
|
||||
case *event.StartProxyPayload:
|
||||
@@ -86,8 +92,8 @@ func (pm *ProxyManager) HandleEvent(evType event.EventType, payload interface{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
|
||||
ps := make([]*ProxyStatus, 0)
|
||||
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||
ps := make([]*WorkingStatus, 0)
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
@@ -96,7 +102,8 @@ func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
|
||||
return ps
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
xl := xlog.FromContextSafe(pm.ctx)
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
@@ -120,13 +127,13 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
}
|
||||
}
|
||||
if len(delPxyNames) > 0 {
|
||||
pm.Info("proxy removed: %v", delPxyNames)
|
||||
xl.Info("proxy removed: %v", delPxyNames)
|
||||
}
|
||||
|
||||
addPxyNames := make([]string, 0)
|
||||
for name, cfg := range pxyCfgs {
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
|
||||
@@ -134,6 +141,6 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
}
|
||||
}
|
||||
if len(addPxyNames) > 0 {
|
||||
pm.Info("proxy added: %v", addPxyNames)
|
||||
xl.Info("proxy added: %v", addPxyNames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/client/event"
|
||||
"github.com/fatedier/frp/client/health"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ProxyStatusNew = "new"
|
||||
ProxyStatusWaitStart = "wait start"
|
||||
ProxyStatusStartErr = "start error"
|
||||
ProxyStatusRunning = "running"
|
||||
ProxyStatusCheckFailed = "check failed"
|
||||
ProxyStatusClosed = "closed"
|
||||
ProxyPhaseNew = "new"
|
||||
ProxyPhaseWaitStart = "wait start"
|
||||
ProxyPhaseStartErr = "start error"
|
||||
ProxyPhaseRunning = "running"
|
||||
ProxyPhaseCheckFailed = "check failed"
|
||||
ProxyPhaseClosed = "closed"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,29 +32,29 @@ var (
|
||||
startErrTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
type ProxyStatus struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Err string `json:"err"`
|
||||
Cfg config.ProxyConf `json:"cfg"`
|
||||
type WorkingStatus struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Phase string `json:"status"`
|
||||
Err string `json:"err"`
|
||||
Cfg config.ProxyConf `json:"cfg"`
|
||||
|
||||
// Got from server.
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
type ProxyWrapper struct {
|
||||
ProxyStatus
|
||||
type Wrapper struct {
|
||||
WorkingStatus
|
||||
|
||||
// underlying proxy
|
||||
pxy Proxy
|
||||
|
||||
// if ProxyConf has healcheck config
|
||||
// monitor will watch if it is alive
|
||||
monitor *health.HealthCheckMonitor
|
||||
monitor *health.Monitor
|
||||
|
||||
// event handler
|
||||
handler event.EventHandler
|
||||
handler event.Handler
|
||||
|
||||
health uint32
|
||||
lastSendStartMsg time.Time
|
||||
@@ -62,73 +63,75 @@ type ProxyWrapper struct {
|
||||
healthNotifyCh chan struct{}
|
||||
mu sync.RWMutex
|
||||
|
||||
log.Logger
|
||||
xl *xlog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
|
||||
func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper {
|
||||
baseInfo := cfg.GetBaseInfo()
|
||||
pw := &ProxyWrapper{
|
||||
ProxyStatus: ProxyStatus{
|
||||
Name: baseInfo.ProxyName,
|
||||
Type: baseInfo.ProxyType,
|
||||
Status: ProxyStatusNew,
|
||||
Cfg: cfg,
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
||||
pw := &Wrapper{
|
||||
WorkingStatus: WorkingStatus{
|
||||
Name: baseInfo.ProxyName,
|
||||
Type: baseInfo.ProxyType,
|
||||
Phase: ProxyPhaseNew,
|
||||
Cfg: cfg,
|
||||
},
|
||||
closeCh: make(chan struct{}),
|
||||
healthNotifyCh: make(chan struct{}),
|
||||
handler: eventHandler,
|
||||
Logger: log.NewPrefixLogger(logPrefix),
|
||||
xl: xl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
}
|
||||
pw.AddLogPrefix(pw.Name)
|
||||
|
||||
if baseInfo.HealthCheckType != "" {
|
||||
pw.health = 1 // means failed
|
||||
pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
||||
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
||||
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
|
||||
baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
|
||||
pw.monitor.SetLogger(pw.Logger)
|
||||
pw.Trace("enable health check monitor")
|
||||
baseInfo.HealthCheckURL, pw.statusNormalCallback, pw.statusFailedCallback)
|
||||
xl.Trace("enable health check monitor")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.Cfg)
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
|
||||
return pw
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||
func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
if pw.Status != ProxyStatusWaitStart {
|
||||
if pw.Phase != ProxyPhaseWaitStart {
|
||||
return fmt.Errorf("status not wait start, ignore start message")
|
||||
}
|
||||
|
||||
pw.RemoteAddr = remoteAddr
|
||||
if respErr != "" {
|
||||
pw.Status = ProxyStatusStartErr
|
||||
pw.Phase = ProxyPhaseStartErr
|
||||
pw.Err = respErr
|
||||
pw.lastStartErr = time.Now()
|
||||
return fmt.Errorf(pw.Err)
|
||||
}
|
||||
|
||||
if err := pw.pxy.Run(); err != nil {
|
||||
pw.Status = ProxyStatusStartErr
|
||||
pw.close()
|
||||
pw.Phase = ProxyPhaseStartErr
|
||||
pw.Err = err.Error()
|
||||
pw.lastStartErr = time.Now()
|
||||
return err
|
||||
}
|
||||
|
||||
pw.Status = ProxyStatusRunning
|
||||
pw.Phase = ProxyPhaseRunning
|
||||
pw.Err = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) Start() {
|
||||
func (pw *Wrapper) Start() {
|
||||
go pw.checkWorker()
|
||||
if pw.monitor != nil {
|
||||
go pw.monitor.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) Stop() {
|
||||
func (pw *Wrapper) Stop() {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
close(pw.closeCh)
|
||||
@@ -137,8 +140,11 @@ func (pw *ProxyWrapper) Stop() {
|
||||
if pw.monitor != nil {
|
||||
pw.monitor.Stop()
|
||||
}
|
||||
pw.Status = ProxyStatusClosed
|
||||
pw.Phase = ProxyPhaseClosed
|
||||
pw.close()
|
||||
}
|
||||
|
||||
func (pw *Wrapper) close() {
|
||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||
CloseProxyMsg: &msg.CloseProxy{
|
||||
ProxyName: pw.Name,
|
||||
@@ -146,7 +152,8 @@ func (pw *ProxyWrapper) Stop() {
|
||||
})
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) checkWorker() {
|
||||
func (pw *Wrapper) checkWorker() {
|
||||
xl := pw.xl
|
||||
if pw.monitor != nil {
|
||||
// let monitor do check request first
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
@@ -156,13 +163,13 @@ func (pw *ProxyWrapper) checkWorker() {
|
||||
now := time.Now()
|
||||
if atomic.LoadUint32(&pw.health) == 0 {
|
||||
pw.mu.Lock()
|
||||
if pw.Status == ProxyStatusNew ||
|
||||
pw.Status == ProxyStatusCheckFailed ||
|
||||
(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
||||
(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
||||
if pw.Phase == ProxyPhaseNew ||
|
||||
pw.Phase == ProxyPhaseCheckFailed ||
|
||||
(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
||||
(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
||||
|
||||
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
|
||||
pw.Status = ProxyStatusWaitStart
|
||||
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart)
|
||||
pw.Phase = ProxyPhaseWaitStart
|
||||
|
||||
var newProxyMsg msg.NewProxy
|
||||
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
||||
@@ -174,14 +181,10 @@ func (pw *ProxyWrapper) checkWorker() {
|
||||
pw.mu.Unlock()
|
||||
} else {
|
||||
pw.mu.Lock()
|
||||
if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart {
|
||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||
CloseProxyMsg: &msg.CloseProxy{
|
||||
ProxyName: pw.Name,
|
||||
},
|
||||
})
|
||||
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
|
||||
pw.Status = ProxyStatusCheckFailed
|
||||
if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart {
|
||||
pw.close()
|
||||
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed)
|
||||
pw.Phase = ProxyPhaseCheckFailed
|
||||
}
|
||||
pw.mu.Unlock()
|
||||
}
|
||||
@@ -195,7 +198,8 @@ func (pw *ProxyWrapper) checkWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) statusNormalCallback() {
|
||||
func (pw *Wrapper) statusNormalCallback() {
|
||||
xl := pw.xl
|
||||
atomic.StoreUint32(&pw.health, 0)
|
||||
errors.PanicToError(func() {
|
||||
select {
|
||||
@@ -203,10 +207,11 @@ func (pw *ProxyWrapper) statusNormalCallback() {
|
||||
default:
|
||||
}
|
||||
})
|
||||
pw.Info("health check success")
|
||||
xl.Info("health check success")
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) statusFailedCallback() {
|
||||
func (pw *Wrapper) statusFailedCallback() {
|
||||
xl := pw.xl
|
||||
atomic.StoreUint32(&pw.health, 1)
|
||||
errors.PanicToError(func() {
|
||||
select {
|
||||
@@ -214,28 +219,29 @@ func (pw *ProxyWrapper) statusFailedCallback() {
|
||||
default:
|
||||
}
|
||||
})
|
||||
pw.Info("health check failed")
|
||||
xl.Info("health check failed")
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
|
||||
func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pw.xl
|
||||
pw.mu.RLock()
|
||||
pxy := pw.pxy
|
||||
pw.mu.RUnlock()
|
||||
if pxy != nil {
|
||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
go pxy.InWorkConn(workConn)
|
||||
xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
go pxy.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
|
||||
func (pw *Wrapper) GetStatus() *WorkingStatus {
|
||||
pw.mu.RLock()
|
||||
defer pw.mu.RUnlock()
|
||||
ps := &ProxyStatus{
|
||||
ps := &WorkingStatus{
|
||||
Name: pw.Name,
|
||||
Type: pw.Type,
|
||||
Status: pw.Status,
|
||||
Phase: pw.Phase,
|
||||
Err: pw.Err,
|
||||
Cfg: pw.Cfg,
|
||||
RemoteAddr: pw.RemoteAddr,
|
||||
|
||||
@@ -15,54 +15,73 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runId string
|
||||
runID string
|
||||
|
||||
// manager control connection with server
|
||||
ctl *Control
|
||||
ctlMu sync.RWMutex
|
||||
|
||||
// Sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
cfg config.ClientCommonConf
|
||||
pxyCfgs map[string]config.ProxyConf
|
||||
visitorCfgs map[string]config.VisitorConf
|
||||
cfgMu sync.RWMutex
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
closedCh chan int
|
||||
// The configuration file used to initialize this client, or an empty
|
||||
// string if no configuration file was used.
|
||||
cfgFile string
|
||||
|
||||
// This is configured by the login response from frps
|
||||
serverUDPPort int
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
|
||||
// service context
|
||||
ctx context.Context
|
||||
// call cancel to stop service
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
|
||||
// Init assets
|
||||
err = assets.Load("")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Load assets error: %v", err)
|
||||
return
|
||||
}
|
||||
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
svr = &Service{
|
||||
authSetter: auth.NewAuthSetter(cfg.ClientConfig),
|
||||
cfg: cfg,
|
||||
cfgFile: cfgFile,
|
||||
pxyCfgs: pxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
exit: 0,
|
||||
closedCh: make(chan int),
|
||||
ctx: xlog.NewContext(ctx, xlog.New()),
|
||||
cancel: cancel,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -74,22 +93,23 @@ func (svr *Service) GetController() *Control {
|
||||
}
|
||||
|
||||
func (svr *Service) Run() error {
|
||||
// first login
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
// login to frps
|
||||
for {
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
log.Warn("login to server failed: %v", err)
|
||||
xl.Warn("login to server failed: %v", err)
|
||||
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and try again to connect to server
|
||||
if g.GlbClientCfg.LoginFailExit {
|
||||
if svr.cfg.LoginFailExit {
|
||||
return err
|
||||
} else {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
@@ -100,33 +120,61 @@ func (svr *Service) Run() error {
|
||||
|
||||
go svr.keepControllerWorking()
|
||||
|
||||
if g.GlbClientCfg.AdminPort != 0 {
|
||||
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
if svr.cfg.AdminPort != 0 {
|
||||
// Init admin server assets
|
||||
err := assets.Load(svr.cfg.AssetsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Load assets error: %v", err)
|
||||
}
|
||||
|
||||
err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||
}
|
||||
|
||||
<-svr.closedCh
|
||||
<-svr.ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) keepControllerWorking() {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
maxDelayTime := 20 * time.Second
|
||||
delayTime := time.Second
|
||||
|
||||
// if frpc reconnect frps, we need to limit retry times in 1min
|
||||
// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
|
||||
// when exceed 1min, we will reset delay and counts
|
||||
cutoffTime := time.Now().Add(time.Minute)
|
||||
reconnectDelay := time.Second
|
||||
reconnectCounts := 1
|
||||
|
||||
for {
|
||||
<-svr.ctl.ClosedDoneCh()
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// the first three retry with no delay
|
||||
if reconnectCounts > 3 {
|
||||
time.Sleep(reconnectDelay)
|
||||
reconnectDelay *= 2
|
||||
}
|
||||
reconnectCounts++
|
||||
|
||||
now := time.Now()
|
||||
if now.After(cutoffTime) {
|
||||
// reset
|
||||
cutoffTime = now.Add(time.Minute)
|
||||
reconnectDelay = time.Second
|
||||
reconnectCounts = 1
|
||||
}
|
||||
|
||||
for {
|
||||
log.Info("try to reconnect to server...")
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
log.Warn("reconnect to server error: %v", err)
|
||||
xl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
@@ -137,9 +185,12 @@ func (svr *Service) keepControllerWorking() {
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
|
||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.Close()
|
||||
}
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
@@ -150,9 +201,22 @@ func (svr *Service) keepControllerWorking() {
|
||||
// login creates a connection to frps and registers it self as a client
|
||||
// conn: control connection
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
if svr.cfg.TLSEnable {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
svr.cfg.TLSCertFile,
|
||||
svr.cfg.TLSKeyFile,
|
||||
svr.cfg.TLSTrustedCaFile,
|
||||
svr.cfg.ServerAddr)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration when service login, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -160,10 +224,13 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
if session != nil {
|
||||
session.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
if svr.cfg.TCPMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
@@ -177,19 +244,23 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
conn = frpNet.WrapConn(stream)
|
||||
conn = stream
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: g.GlbClientCfg.PoolCount,
|
||||
User: g.GlbClientCfg.User,
|
||||
Version: version.Full(),
|
||||
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
|
||||
Timestamp: now,
|
||||
RunId: svr.runId,
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: svr.cfg.PoolCount,
|
||||
User: svr.cfg.User,
|
||||
Version: version.Full(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
RunID: svr.runID,
|
||||
Metas: svr.cfg.Metas,
|
||||
}
|
||||
|
||||
// Add auth
|
||||
if err = svr.authSetter.SetLogin(loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = msg.WriteMsg(conn, loginMsg); err != nil {
|
||||
@@ -205,13 +276,16 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
log.Error("%s", loginRespMsg.Error)
|
||||
xl.Error("%s", loginRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
svr.runId = loginRespMsg.RunId
|
||||
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
||||
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||
svr.runID = loginRespMsg.RunID
|
||||
xl.ResetPrefixes()
|
||||
xl.AppendPrefix(svr.runID)
|
||||
|
||||
svr.serverUDPPort = loginRespMsg.ServerUDPPort
|
||||
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -226,6 +300,8 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
|
||||
|
||||
func (svr *Service) Close() {
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
svr.ctl.Close()
|
||||
close(svr.closedCh)
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.Close()
|
||||
}
|
||||
svr.cancel()
|
||||
}
|
||||
|
||||
@@ -16,70 +16,77 @@ package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
// Visitor is used for forward traffics from local port tot remote service.
|
||||
type Visitor interface {
|
||||
Run() error
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||
func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
||||
baseVisitor := BaseVisitor{
|
||||
ctl: ctl,
|
||||
Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName),
|
||||
ctl: ctl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
}
|
||||
switch cfg := cfg.(type) {
|
||||
case *config.StcpVisitorConf:
|
||||
visitor = &StcpVisitor{
|
||||
case *config.STCPVisitorConf:
|
||||
visitor = &STCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpVisitorConf:
|
||||
visitor = &XtcpVisitor{
|
||||
case *config.XTCPVisitorConf:
|
||||
visitor = &XTCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.SUDPVisitorConf:
|
||||
visitor = &SUDPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
checkCloseCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseVisitor struct {
|
||||
ctl *Control
|
||||
l frpNet.Listener
|
||||
l net.Listener
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type StcpVisitor struct {
|
||||
type STCPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.StcpVisitorConf
|
||||
cfg *config.STCPVisitorConf
|
||||
}
|
||||
|
||||
func (sv *StcpVisitor) Run() (err error) {
|
||||
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
|
||||
func (sv *STCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -88,15 +95,16 @@ func (sv *StcpVisitor) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *StcpVisitor) Close() {
|
||||
func (sv *STCPVisitor) Close() {
|
||||
sv.l.Close()
|
||||
}
|
||||
|
||||
func (sv *StcpVisitor) worker() {
|
||||
func (sv *STCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
sv.Warn("stcp local listener closed")
|
||||
xl.Warn("stcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,10 +112,11 @@ func (sv *StcpVisitor) worker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
sv.Debug("get a new stcp user connection")
|
||||
xl.Debug("get a new stcp user connection")
|
||||
visitorConn, err := sv.ctl.connectServer()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -124,7 +133,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
sv.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,13 +141,13 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
sv.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -147,7 +156,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
sv.Error("create encryption stream error: %v", err)
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -159,14 +168,14 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
frpIo.Join(userConn, remote)
|
||||
}
|
||||
|
||||
type XtcpVisitor struct {
|
||||
type XTCPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.XtcpVisitorConf
|
||||
cfg *config.XTCPVisitorConf
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) Run() (err error) {
|
||||
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
|
||||
func (sv *XTCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -175,15 +184,16 @@ func (sv *XtcpVisitor) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) Close() {
|
||||
func (sv *XTCPVisitor) Close() {
|
||||
sv.l.Close()
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) worker() {
|
||||
func (sv *XTCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
sv.Warn("xtcp local listener closed")
|
||||
xl.Warn("xtcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -191,25 +201,26 @@ func (sv *XtcpVisitor) worker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
sv.Debug("get a new xtcp user connection")
|
||||
if g.GlbClientCfg.ServerUdpPort == 0 {
|
||||
sv.Error("xtcp is not supported by server")
|
||||
xl.Debug("get a new xtcp user connection")
|
||||
if sv.ctl.serverUDPPort == 0 {
|
||||
xl.Error("xtcp is not supported by server")
|
||||
return
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||
fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
|
||||
if err != nil {
|
||||
sv.Error("resolve server UDP addr error")
|
||||
xl.Error("resolve server UDP addr error")
|
||||
return
|
||||
}
|
||||
|
||||
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
||||
if err != nil {
|
||||
sv.Warn("dial server udp addr error: %v", err)
|
||||
xl.Warn("dial server udp addr error: %v", err)
|
||||
return
|
||||
}
|
||||
defer visitorConn.Close()
|
||||
@@ -222,7 +233,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
||||
if err != nil {
|
||||
sv.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||
xl.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -232,107 +243,311 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
buf := pool.GetBuf(1024)
|
||||
n, err := visitorConn.Read(buf)
|
||||
if err != nil {
|
||||
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||
if err != nil {
|
||||
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
pool.PutBuf(buf)
|
||||
|
||||
if natHoleRespMsg.Error != "" {
|
||||
sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Close visitorConn, so we can use it's local address.
|
||||
visitorConn.Close()
|
||||
|
||||
// Send detect message.
|
||||
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||
return
|
||||
}
|
||||
// send sid message to client
|
||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||
if err != nil {
|
||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||
xl.Error("resolve client udp address error: %v", err)
|
||||
return
|
||||
}
|
||||
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
sv.Trace("send all detect msg done")
|
||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
xl.Error("dial client udp address error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
// Listen for visitorConn's address and wait for client connection.
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
sv.Error("listen on visitorConn's local adress error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||
|
||||
// read ack sid from client
|
||||
sidBuf := pool.GetBuf(1024)
|
||||
n, _, err = lConn.ReadFromUDP(sidBuf)
|
||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||
n, err = lConn.Read(sidBuf)
|
||||
if err != nil {
|
||||
sv.Warn("get sid from client error: %v", err)
|
||||
xl.Warn("get sid from client error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Time{})
|
||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||
sv.Warn("incorrect sid from client")
|
||||
xl.Warn("incorrect sid from client")
|
||||
return
|
||||
}
|
||||
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
||||
pool.PutBuf(sidBuf)
|
||||
|
||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
// wrap kcp connection
|
||||
var remote io.ReadWriteCloser
|
||||
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
|
||||
remote, err = frpNet.NewKCPConnFromUDP(lConn, true, natHoleRespMsg.ClientAddr)
|
||||
if err != nil {
|
||||
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Client(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
xl.Error("create yamux session error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Open()
|
||||
if err != nil {
|
||||
xl.Error("open yamux stream error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var muxConnRWCloser io.ReadWriteCloser = muxConn
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
sv.Error("create encryption stream error: %v", err)
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if sv.cfg.UseCompression {
|
||||
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, muxConnRWCloser)
|
||||
xl.Debug("join connections closed")
|
||||
}
|
||||
|
||||
type SUDPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
checkCloseCh chan struct{}
|
||||
// udpConn is the listener of udp packet
|
||||
udpConn *net.UDPConn
|
||||
readCh chan *msg.UDPPacket
|
||||
sendCh chan *msg.UDPPacket
|
||||
|
||||
cfg *config.SUDPVisitorConf
|
||||
}
|
||||
|
||||
// SUDP Run start listen a udp port
|
||||
func (sv *SUDPVisitor) Run() (err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||
}
|
||||
|
||||
sv.udpConn, err = net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
||||
}
|
||||
|
||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
|
||||
xl.Info("sudp start to work")
|
||||
|
||||
go sv.dispatcher()
|
||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) dispatcher() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
for {
|
||||
// loop for get frpc to frps tcp conn
|
||||
// setup worker
|
||||
// wait worker to finished
|
||||
// retry or exit
|
||||
visitorConn, err := sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
// check if proxy is closed
|
||||
// if checkCloseCh is close, we will return, other case we will continue to reconnect
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||
continue
|
||||
}
|
||||
|
||||
sv.worker(visitorConn)
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Debug("starting sudp proxy worker")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
closeCh := make(chan struct{})
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
close(closeCh)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
|
||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
xl.Debug("frpc visitor get ping message from frpc")
|
||||
continue
|
||||
case *msg.UDPPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
sv.readCh <- m
|
||||
xl.Trace("frpc visitor get udp packet from frpc")
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnSenderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-sv.sendCh:
|
||||
if !ok {
|
||||
xl.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn)
|
||||
|
||||
wg.Wait()
|
||||
xl.Info("sudp worker is closed")
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
visitorConn, err := sv.ctl.connectServer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.UseEncryption,
|
||||
UseCompression: sv.cfg.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
||||
}
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sv.cfg.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, remote)
|
||||
sv.Debug("join connections closed")
|
||||
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if err != nil {
|
||||
return err
|
||||
func (sv *SUDPVisitor) Close() {
|
||||
sv.mu.Lock()
|
||||
defer sv.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
close(sv.checkCloseCh)
|
||||
}
|
||||
|
||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
return err
|
||||
if sv.udpConn != nil {
|
||||
sv.udpConn.Close()
|
||||
}
|
||||
|
||||
uConn := ipv4.NewConn(tConn)
|
||||
uConn.SetTTL(3)
|
||||
|
||||
tConn.Write(content)
|
||||
tConn.Close()
|
||||
return nil
|
||||
close(sv.readCh)
|
||||
close(sv.sendCh)
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type VisitorManager struct {
|
||||
@@ -30,48 +31,65 @@ type VisitorManager struct {
|
||||
|
||||
checkInterval time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func NewVisitorManager(ctl *Control) *VisitorManager {
|
||||
func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
|
||||
return &VisitorManager{
|
||||
ctl: ctl,
|
||||
cfgs: make(map[string]config.VisitorConf),
|
||||
visitors: make(map[string]Visitor),
|
||||
checkInterval: 10 * time.Second,
|
||||
ctx: ctx,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Run() {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
|
||||
ticker := time.NewTicker(vm.checkInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
time.Sleep(vm.checkInterval)
|
||||
vm.mu.Lock()
|
||||
for _, cfg := range vm.cfgs {
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
if _, exist := vm.visitors[name]; !exist {
|
||||
log.Info("try to start visitor [%s]", name)
|
||||
vm.startVisitor(cfg)
|
||||
select {
|
||||
case <-vm.stopCh:
|
||||
xl.Info("gracefully shutdown visitor manager")
|
||||
return
|
||||
case <-ticker.C:
|
||||
vm.mu.Lock()
|
||||
for _, cfg := range vm.cfgs {
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
if _, exist := vm.visitors[name]; !exist {
|
||||
xl.Info("try to start visitor [%s]", name)
|
||||
vm.startVisitor(cfg)
|
||||
}
|
||||
}
|
||||
vm.mu.Unlock()
|
||||
}
|
||||
vm.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Hold lock before calling this function.
|
||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
visitor := NewVisitor(vm.ctl, cfg)
|
||||
visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
|
||||
err = visitor.Run()
|
||||
if err != nil {
|
||||
visitor.Warn("start error: %v", err)
|
||||
xl.Warn("start error: %v", err)
|
||||
} else {
|
||||
vm.visitors[name] = visitor
|
||||
visitor.Info("start visitor success")
|
||||
xl.Info("start visitor success")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
|
||||
@@ -97,7 +115,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
}
|
||||
}
|
||||
if len(delNames) > 0 {
|
||||
log.Info("visitor removed: %v", delNames)
|
||||
xl.Info("visitor removed: %v", delNames)
|
||||
}
|
||||
|
||||
addNames := make([]string, 0)
|
||||
@@ -109,7 +127,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
}
|
||||
}
|
||||
if len(addNames) > 0 {
|
||||
log.Info("visitor added: %v", addNames)
|
||||
xl.Info("visitor added: %v", addNames)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -120,4 +138,9 @@ func (vm *VisitorManager) Close() {
|
||||
for _, v := range vm.visitors {
|
||||
v.Close()
|
||||
}
|
||||
select {
|
||||
case <-vm.stopCh:
|
||||
default:
|
||||
close(vm.stopCh)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
@@ -23,6 +26,7 @@ import (
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
sub.Execute()
|
||||
}
|
||||
|
||||
@@ -19,23 +19,17 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
RegisterCommonFlags(httpCmd)
|
||||
|
||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
httpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||
@@ -53,26 +47,26 @@ var httpCmd = &cobra.Command{
|
||||
Use: "http",
|
||||
Short: "Run frpc with a single http proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.HttpProxyConf{}
|
||||
cfg := &config.HTTPProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.HttpProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.ProxyType = consts.HTTPProxy
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||
cfg.SubDomain = subDomain
|
||||
cfg.Locations = strings.Split(locations, ",")
|
||||
cfg.HttpUser = httpUser
|
||||
cfg.HttpPwd = httpPwd
|
||||
cfg.HTTPUser = httpUser
|
||||
cfg.HTTPPwd = httpPwd
|
||||
cfg.HostHeaderRewrite = hostHeaderRewrite
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
@@ -86,7 +80,7 @@ var httpCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -21,21 +21,15 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
RegisterCommonFlags(httpsCmd)
|
||||
|
||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
httpsCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||
@@ -49,20 +43,20 @@ var httpsCmd = &cobra.Command{
|
||||
Use: "https",
|
||||
Short: "Run frpc with a single https proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.HttpsProxyConf{}
|
||||
cfg := &config.HTTPSProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.HttpsProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.ProxyType = consts.HTTPSProxy
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||
cfg.SubDomain = subDomain
|
||||
@@ -78,7 +72,7 @@ var httpsCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -22,10 +22,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -42,13 +41,13 @@ var reloadCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = reload()
|
||||
err = reload(clientCfg)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc reload error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -58,35 +57,34 @@ var reloadCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func reload() error {
|
||||
if g.GlbClientCfg.AdminPort == 0 {
|
||||
func reload(clientCfg config.ClientCommonConf) error {
|
||||
if clientCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
|
||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||
g.GlbClientCfg.AdminPwd))
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||
clientCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return nil
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,16 +43,17 @@ var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
|
||||
serverAddr string
|
||||
user string
|
||||
protocol string
|
||||
token string
|
||||
logLevel string
|
||||
logFile string
|
||||
logMaxDays int
|
||||
serverAddr string
|
||||
user string
|
||||
protocol string
|
||||
token string
|
||||
logLevel string
|
||||
logFile string
|
||||
logMaxDays int
|
||||
disableLogColor bool
|
||||
|
||||
proxyName string
|
||||
localIp string
|
||||
localIP string
|
||||
localPort int
|
||||
remotePort int
|
||||
useEncryption bool
|
||||
@@ -65,20 +66,35 @@ var (
|
||||
hostHeaderRewrite string
|
||||
role string
|
||||
sk string
|
||||
multiplexer string
|
||||
serverName string
|
||||
bindAddr string
|
||||
bindPort int
|
||||
|
||||
tlsEnable bool
|
||||
|
||||
kcpDoneCh chan struct{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
|
||||
kcpDoneCh = make(chan struct{})
|
||||
}
|
||||
|
||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frpc",
|
||||
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
|
||||
@@ -113,59 +129,66 @@ func handleSignal(svr *client.Service) {
|
||||
close(kcpDoneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfg(fileType int, content string) (err error) {
|
||||
func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseClientCommonCfgFromIni(content)
|
||||
cfg, err = parseClientCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseClientCommonCfgFromCmd()
|
||||
cfg, err = parseClientCommonCfgFromCmd()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.GlbClientCfg.ClientCommonConf.Check()
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
||||
func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(content)
|
||||
if err != nil {
|
||||
return err
|
||||
return config.ClientCommonConf{}, err
|
||||
}
|
||||
g.GlbClientCfg.ClientCommonConf = *cfg
|
||||
return
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (err error) {
|
||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
cfg = config.GetDefaultClientConf()
|
||||
|
||||
strs := strings.Split(serverAddr, ":")
|
||||
if len(strs) < 2 {
|
||||
err = fmt.Errorf("invalid server_addr")
|
||||
return
|
||||
}
|
||||
if strs[0] != "" {
|
||||
g.GlbClientCfg.ServerAddr = strs[0]
|
||||
cfg.ServerAddr = strs[0]
|
||||
}
|
||||
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||
cfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid server_addr")
|
||||
return
|
||||
}
|
||||
|
||||
g.GlbClientCfg.User = user
|
||||
g.GlbClientCfg.Protocol = protocol
|
||||
g.GlbClientCfg.Token = token
|
||||
g.GlbClientCfg.LogLevel = logLevel
|
||||
g.GlbClientCfg.LogFile = logFile
|
||||
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
||||
cfg.User = user
|
||||
cfg.Protocol = protocol
|
||||
cfg.LogLevel = logLevel
|
||||
cfg.LogFile = logFile
|
||||
cfg.LogMaxDays = int64(logMaxDays)
|
||||
if logFile == "console" {
|
||||
g.GlbClientCfg.LogWay = "console"
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
return nil
|
||||
cfg.DisableLogColor = disableLogColor
|
||||
|
||||
// Only token authentication is supported in cmd mode
|
||||
cfg.ClientConfig = auth.GetDefaultClientConf()
|
||||
cfg.Token = token
|
||||
cfg.TLSEnable = tlsEnable
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) (err error) {
|
||||
@@ -174,26 +197,33 @@ func runClient(cfgFilePath string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
g.GlbClientCfg.CfgFile = cfgFilePath
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
||||
cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = startService(pxyCfgs, visitorCfgs)
|
||||
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||
return
|
||||
}
|
||||
|
||||
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
|
||||
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
|
||||
if g.GlbClientCfg.DnsServer != "" {
|
||||
s := g.GlbClientCfg.DnsServer
|
||||
func startService(
|
||||
cfg config.ClientCommonConf,
|
||||
pxyCfgs map[string]config.ProxyConf,
|
||||
visitorCfgs map[string]config.VisitorConf,
|
||||
cfgFile string,
|
||||
) (err error) {
|
||||
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
|
||||
if cfg.DNSServer != "" {
|
||||
s := cfg.DNSServer
|
||||
if !strings.Contains(s, ":") {
|
||||
s += ":53"
|
||||
}
|
||||
@@ -205,19 +235,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
||||
},
|
||||
}
|
||||
}
|
||||
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
if cfg.Protocol == "kcp" {
|
||||
go handleSignal(svr)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
if cfg.Protocol == "kcp" {
|
||||
<-kcpDoneCh
|
||||
}
|
||||
return
|
||||
|
||||
@@ -23,12 +23,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = status()
|
||||
err = status(clientCfg)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc get status error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -60,94 +59,96 @@ var statusCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func status() error {
|
||||
if g.GlbClientCfg.AdminPort == 0 {
|
||||
func status(clientCfg config.ClientCommonConf) error {
|
||||
if clientCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
|
||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||
g.GlbClientCfg.AdminPwd))
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||
clientCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.TCP) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.TCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.UDP) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.UDP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.HTTP) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.HTTP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.HTTPS) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.HTTPS {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.STCP) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.STCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.XTCP) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.XTCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,26 +18,20 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
RegisterCommonFlags(stcpCmd)
|
||||
|
||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
stcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
@@ -51,7 +45,7 @@ var stcpCmd = &cobra.Command{
|
||||
Use: "stcp",
|
||||
Short: "Run frpc with a single stcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -66,14 +60,14 @@ var stcpCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
cfg := &config.StcpProxyConf{}
|
||||
cfg := &config.STCPProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.ProxyType = consts.STCPProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
@@ -82,9 +76,9 @@ var stcpCmd = &cobra.Command{
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
cfg := &config.StcpVisitorConf{}
|
||||
cfg := &config.STCPVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.ProxyType = consts.STCPProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
@@ -103,7 +97,7 @@ var stcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(proxyConfs, visitorConfs)
|
||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
106
cmd/frpc/sub/sudp.go
Normal file
106
cmd/frpc/sub/sudp.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCommonFlags(sudpCmd)
|
||||
|
||||
sudpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
sudpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
sudpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||
sudpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||
sudpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
sudpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
sudpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(sudpCmd)
|
||||
}
|
||||
|
||||
var sudpCmd = &cobra.Command{
|
||||
Use: "sudp",
|
||||
Short: "Run frpc with a single sudp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := make(map[string]config.ProxyConf)
|
||||
visitorConfs := make(map[string]config.VisitorConf)
|
||||
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
cfg := &config.SUDPProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.SUDPProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
cfg := &config.SUDPVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.SUDPProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.ServerName = serverName
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.BindPort = bindPort
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
visitorConfs[cfg.ProxyName] = cfg
|
||||
} else {
|
||||
fmt.Println("invalid role")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -20,21 +20,15 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
RegisterCommonFlags(tcpCmd)
|
||||
|
||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
tcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
@@ -47,20 +41,20 @@ var tcpCmd = &cobra.Command{
|
||||
Use: "tcp",
|
||||
Short: "Run frpc with a single tcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.TcpProxyConf{}
|
||||
cfg := &config.TCPProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.TcpProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.ProxyType = consts.TCPProxy
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.RemotePort = remotePort
|
||||
cfg.UseEncryption = useEncryption
|
||||
@@ -75,7 +69,7 @@ var tcpCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
84
cmd/frpc/sub/tcpmux.go
Normal file
84
cmd/frpc/sub/tcpmux.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCommonFlags(tcpMuxCmd)
|
||||
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||
tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
|
||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(tcpMuxCmd)
|
||||
}
|
||||
|
||||
var tcpMuxCmd = &cobra.Command{
|
||||
Use: "tcpmux",
|
||||
Short: "Run frpc with a single tcpmux proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.TCPMuxProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.TCPMuxProxy
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||
cfg.SubDomain = subDomain
|
||||
cfg.Multiplexer = multiplexer
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -18,23 +18,17 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
RegisterCommonFlags(udpCmd)
|
||||
|
||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
udpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
@@ -47,20 +41,20 @@ var udpCmd = &cobra.Command{
|
||||
Use: "udp",
|
||||
Short: "Run frpc with a single udp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.UdpProxyConf{}
|
||||
cfg := &config.UDPProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.UdpProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.ProxyType = consts.UDPProxy
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
cfg.RemotePort = remotePort
|
||||
cfg.UseEncryption = useEncryption
|
||||
@@ -75,7 +69,7 @@ var udpCmd = &cobra.Command{
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
err = startService(clientCfg, proxyConfs, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -18,26 +18,20 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
RegisterCommonFlags(xtcpCmd)
|
||||
|
||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
@@ -51,7 +45,7 @@ var xtcpCmd = &cobra.Command{
|
||||
Use: "xtcp",
|
||||
Short: "Run frpc with a single xtcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -66,14 +60,14 @@ var xtcpCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
cfg := &config.XtcpProxyConf{}
|
||||
cfg := &config.XTCPProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.ProxyType = consts.XTCPProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalIP = localIP
|
||||
cfg.LocalPort = localPort
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
@@ -82,9 +76,9 @@ var xtcpCmd = &cobra.Command{
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
cfg := &config.XtcpVisitorConf{}
|
||||
cfg := &config.XTCPVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.ProxyType = consts.XTCPProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
@@ -103,7 +97,7 @@ var xtcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(proxyConfs, visitorConfs)
|
||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||
_ "github.com/fatedier/frp/pkg/metrics"
|
||||
)
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
Execute()
|
||||
}
|
||||
|
||||
132
cmd/frps/root.go
132
cmd/frps/root.go
@@ -18,14 +18,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/server"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,51 +39,58 @@ var (
|
||||
|
||||
bindAddr string
|
||||
bindPort int
|
||||
bindUdpPort int
|
||||
bindUDPPort int
|
||||
kcpBindPort int
|
||||
proxyBindAddr string
|
||||
vhostHttpPort int
|
||||
vhostHttpsPort int
|
||||
vhostHttpTimeout int64
|
||||
vhostHTTPPort int
|
||||
vhostHTTPSPort int
|
||||
vhostHTTPTimeout int64
|
||||
dashboardAddr string
|
||||
dashboardPort int
|
||||
dashboardUser string
|
||||
dashboardPwd string
|
||||
enablePrometheus bool
|
||||
assetsDir string
|
||||
logFile string
|
||||
logLevel string
|
||||
logMaxDays int64
|
||||
disableLogColor bool
|
||||
token string
|
||||
subDomainHost string
|
||||
tcpMux bool
|
||||
allowPorts string
|
||||
maxPoolCount int64
|
||||
maxPortsPerClient int64
|
||||
tlsOnly bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindUDPPort, "bind_udp_port", "", 0, "bind udp port")
|
||||
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
|
||||
rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
||||
rootCmd.PersistentFlags().Int64VarP(&vhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
|
||||
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
rootCmd.PersistentFlags().BoolVarP(&enablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard")
|
||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
|
||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
||||
rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
|
||||
rootCmd.PersistentFlags().BoolVarP(&tlsOnly, "tls_only", "", false, "frps tls only")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -95,6 +102,7 @@ var rootCmd = &cobra.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
var cfg config.ServerCommonConf
|
||||
var err error
|
||||
if cfgFile != "" {
|
||||
var content string
|
||||
@@ -102,16 +110,15 @@ var rootCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.GlbServerCfg.CfgFile = cfgFile
|
||||
err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||
} else {
|
||||
err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = runServer()
|
||||
err = runServer(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -126,52 +133,56 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerCommonCfg(fileType int, content string) (err error) {
|
||||
func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseServerCommonCfgFromIni(content)
|
||||
cfg, err = parseServerCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseServerCommonCfgFromCmd()
|
||||
cfg, err = parseServerCommonCfgFromCmd()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.GlbServerCfg.ServerCommonConf.Check()
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
|
||||
return
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
||||
func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(content)
|
||||
if err != nil {
|
||||
return err
|
||||
return config.ServerCommonConf{}, err
|
||||
}
|
||||
g.GlbServerCfg.ServerCommonConf = *cfg
|
||||
return
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromCmd() (err error) {
|
||||
g.GlbServerCfg.BindAddr = bindAddr
|
||||
g.GlbServerCfg.BindPort = bindPort
|
||||
g.GlbServerCfg.BindUdpPort = bindUdpPort
|
||||
g.GlbServerCfg.KcpBindPort = kcpBindPort
|
||||
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
|
||||
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
|
||||
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
|
||||
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
|
||||
g.GlbServerCfg.DashboardAddr = dashboardAddr
|
||||
g.GlbServerCfg.DashboardPort = dashboardPort
|
||||
g.GlbServerCfg.DashboardUser = dashboardUser
|
||||
g.GlbServerCfg.DashboardPwd = dashboardPwd
|
||||
g.GlbServerCfg.LogFile = logFile
|
||||
g.GlbServerCfg.LogLevel = logLevel
|
||||
g.GlbServerCfg.LogMaxDays = logMaxDays
|
||||
g.GlbServerCfg.Token = token
|
||||
g.GlbServerCfg.SubDomainHost = subDomainHost
|
||||
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||
cfg = config.GetDefaultServerConf()
|
||||
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.BindPort = bindPort
|
||||
cfg.BindUDPPort = bindUDPPort
|
||||
cfg.KCPBindPort = kcpBindPort
|
||||
cfg.ProxyBindAddr = proxyBindAddr
|
||||
cfg.VhostHTTPPort = vhostHTTPPort
|
||||
cfg.VhostHTTPSPort = vhostHTTPSPort
|
||||
cfg.VhostHTTPTimeout = vhostHTTPTimeout
|
||||
cfg.DashboardAddr = dashboardAddr
|
||||
cfg.DashboardPort = dashboardPort
|
||||
cfg.DashboardUser = dashboardUser
|
||||
cfg.DashboardPwd = dashboardPwd
|
||||
cfg.EnablePrometheus = enablePrometheus
|
||||
cfg.LogFile = logFile
|
||||
cfg.LogLevel = logLevel
|
||||
cfg.LogMaxDays = logMaxDays
|
||||
cfg.SubDomainHost = subDomainHost
|
||||
cfg.TLSOnly = tlsOnly
|
||||
|
||||
// Only token authentication is supported in cmd mode
|
||||
cfg.ServerConfig = auth.GetDefaultServerConf()
|
||||
cfg.Token = token
|
||||
if len(allowPorts) > 0 {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||
@@ -181,28 +192,27 @@ func parseServerCommonCfgFromCmd() (err error) {
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
|
||||
cfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||
cfg.MaxPortsPerClient = maxPortsPerClient
|
||||
|
||||
if logFile == "console" {
|
||||
g.GlbClientCfg.LogWay = "console"
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
cfg.DisableLogColor = disableLogColor
|
||||
return
|
||||
}
|
||||
|
||||
func runServer() (err error) {
|
||||
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
|
||||
g.GlbServerCfg.LogMaxDays)
|
||||
svr, err := server.NewService()
|
||||
func runServer(cfg config.ServerCommonConf) (err error) {
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
svr, err := server.NewService(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Start frps success")
|
||||
server.ServerService = svr
|
||||
log.Info("start frps success")
|
||||
svr.Run()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# if you want to connect frps by http proxy or socks5 proxy, you can set http_proxy here or in global environment variables
|
||||
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
# http_proxy = socks5://user:passwd@192.168.1.128:1080
|
||||
# http_proxy = ntlm://user:passwd@192.168.1.128:2080
|
||||
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
@@ -18,7 +19,17 @@ log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# for authentication
|
||||
# disable log colors when log_file is console, default is false
|
||||
disable_log_color = false
|
||||
|
||||
# for authentication, should be same as your frps.ini
|
||||
# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||
authenticate_heartbeats = false
|
||||
|
||||
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||
authenticate_new_work_conns = false
|
||||
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
# set admin address for control frpc's action by http api such as reload
|
||||
@@ -26,6 +37,8 @@ admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||
# assets_dir = ./static
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 5
|
||||
@@ -41,13 +54,20 @@ user = your_name
|
||||
login_fail_exit = true
|
||||
|
||||
# communication protocol used to connect to server
|
||||
# now it supports tcp and kcp and websocket, default is tcp
|
||||
# now it supports tcp, kcp and websocket, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# if tls_enable is true, frpc will connect frps by tls
|
||||
tls_enable = true
|
||||
|
||||
# tls_cert_file = client.crt
|
||||
# tls_key_file = client.key
|
||||
# tls_trusted_ca_file = ca.crt
|
||||
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start divided by ','
|
||||
# proxy names you want to start seperated by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
@@ -56,6 +76,15 @@ protocol = tcp
|
||||
# heartbeat_interval = 30
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# additional meta info for client
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
|
||||
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||
# This parameter should be same between client and server.
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
# 'ssh' is the unique proxy name
|
||||
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||
[ssh]
|
||||
@@ -63,6 +92,8 @@ protocol = tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# limit bandwidth for this proxy, unit is KB and MB
|
||||
bandwidth_limit = 1MB
|
||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||
use_encryption = false
|
||||
# if true, message will be compressed
|
||||
@@ -82,6 +113,9 @@ health_check_timeout_s = 3
|
||||
health_check_max_failed = 3
|
||||
# every 10 seconds will do a health check
|
||||
health_check_interval_s = 10
|
||||
# additional meta info for each proxy
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
@@ -151,6 +185,9 @@ use_encryption = false
|
||||
use_compression = false
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||
# v1 or v2 or empty
|
||||
proxy_protocol_version = v2
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
@@ -184,6 +221,24 @@ plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_https2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[plugin_http2https]
|
||||
type = http
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = http2https
|
||||
plugin_local_addr = 127.0.0.1:443
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[secret_tcp]
|
||||
# If the type is secret tcp, remote_port is useless
|
||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||
@@ -226,3 +281,10 @@ bind_addr = 127.0.0.1
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
|
||||
@@ -23,6 +23,12 @@ vhost_https_port = 443
|
||||
# response header timeout(seconds) for vhost http server, default is 60s
|
||||
# vhost_http_timeout = 60
|
||||
|
||||
# TcpMuxHttpConnectPort specifies the port that the server listens for TCP
|
||||
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
# requests on one single port. If it's not - it will listen on this value for
|
||||
# HTTP CONNECT requests. By default, this value is 0.
|
||||
# tcpmux_httpconnect_port = 1337
|
||||
|
||||
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||
# dashboard_addr's default value is same with bind_addr
|
||||
# dashboard is available only if dashboard_port is set
|
||||
@@ -33,6 +39,9 @@ dashboard_port = 7500
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
|
||||
# enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
|
||||
enable_prometheus = true
|
||||
|
||||
# dashboard assets directory(only for debug mode)
|
||||
# assets_dir = ./static
|
||||
# console or real logFile path like ./frps.log
|
||||
@@ -43,9 +52,41 @@ log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# disable log colors when log_file is console, default is false
|
||||
disable_log_color = false
|
||||
|
||||
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||
detailed_errors_to_client = true
|
||||
|
||||
# AuthenticationMethod specifies what authentication method to use authenticate frpc with frps.
|
||||
# If "token" is specified - token will be read into login message.
|
||||
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||
authentication_method = token
|
||||
|
||||
# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||
authenticate_heartbeats = false
|
||||
|
||||
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||
authenticate_new_work_conns = false
|
||||
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
# OidcClientId specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||
# By default, this value is "".
|
||||
oidc_client_id =
|
||||
|
||||
# OidcClientSecret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||
# By default, this value is "".
|
||||
oidc_client_secret =
|
||||
|
||||
# OidcAudience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_audience =
|
||||
|
||||
# OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint.
|
||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_token_endpoint_url =
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_timeout is 90
|
||||
# heartbeat_timeout = 90
|
||||
@@ -59,9 +100,34 @@ max_pool_count = 5
|
||||
# max ports can be used for each client, default value is 0 means no limit
|
||||
max_ports_per_client = 0
|
||||
|
||||
# TlsOnly specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||
tls_only = false
|
||||
|
||||
# tls_cert_file = server.crt
|
||||
# tls_key_file = server.key
|
||||
# tls_trusted_ca_file = ca.crt
|
||||
|
||||
# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||
# when subdomain is test, the host used by routing is test.frps.com
|
||||
subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom_404_page = /path/to/404.html
|
||||
|
||||
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||
# This parameter should be same between client and server.
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
ops = Login
|
||||
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9001
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
|
||||
14
conf/systemd/frpc.service
Normal file
14
conf/systemd/frpc.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Frp Client Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
|
||||
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
14
conf/systemd/frpc@.service
Normal file
14
conf/systemd/frpc@.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Frp Client Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
|
||||
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
conf/systemd/frps.service
Normal file
13
conf/systemd/frps.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Frp Server Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
conf/systemd/frps@.service
Normal file
13
conf/systemd/frps@.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Frp Server Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
240
doc/server_plugin.md
Normal file
240
doc/server_plugin.md
Normal file
@@ -0,0 +1,240 @@
|
||||
### Server Plugin
|
||||
|
||||
frp server plugin is aimed to extend frp's ability without modifying the Golang code.
|
||||
|
||||
An external server should run in a different process receiving RPC calls from frps.
|
||||
Before frps is doing some operations, it will send RPC requests to notify the external RPC server and act according to its response.
|
||||
|
||||
### RPC request
|
||||
|
||||
RPC requests are based on JSON over HTTP.
|
||||
|
||||
When a server plugin accepts an operation request, it can respond with three different responses:
|
||||
|
||||
* Reject operation and return a reason.
|
||||
* Allow operation and keep original content.
|
||||
* Allow operation and return modified content.
|
||||
|
||||
### Interface
|
||||
|
||||
HTTP path can be configured for each manage plugin in frps. We'll assume for this example that it's `/handler`.
|
||||
|
||||
A request to the RPC server will look like:
|
||||
|
||||
```
|
||||
POST /handler?version=0.1.0&op=Login
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"op": "Login",
|
||||
"content": {
|
||||
... // Operation info
|
||||
}
|
||||
}
|
||||
|
||||
Request Header:
|
||||
X-Frp-Reqid: for tracing
|
||||
```
|
||||
|
||||
The response can look like any of the following:
|
||||
|
||||
* Non-200 HTTP response status code (this will automatically tell frps that the request should fail)
|
||||
|
||||
* Reject operation:
|
||||
|
||||
```
|
||||
{
|
||||
"reject": true,
|
||||
"reject_reason": "invalid user"
|
||||
}
|
||||
```
|
||||
|
||||
* Allow operation and keep original content:
|
||||
|
||||
```
|
||||
{
|
||||
"reject": false,
|
||||
"unchange": true
|
||||
}
|
||||
```
|
||||
|
||||
* Allow operation and modify content
|
||||
|
||||
```
|
||||
{
|
||||
"unchange": "false",
|
||||
"content": {
|
||||
... // Replaced content
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Operation
|
||||
|
||||
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
|
||||
#### Login
|
||||
|
||||
Client login operation
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"version": <string>,
|
||||
"hostname": <string>,
|
||||
"os": <string>,
|
||||
"arch": <string>,
|
||||
"user": <string>,
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>,
|
||||
"run_id": <string>,
|
||||
"pool_count": <int>,
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewProxy
|
||||
|
||||
Create new proxy
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"use_encryption": <bool>,
|
||||
"use_compression": <bool>,
|
||||
"group": <string>,
|
||||
"group_key": <string>,
|
||||
|
||||
// tcp and udp only
|
||||
"remote_port": <int>,
|
||||
|
||||
// http and https only
|
||||
"custom_domains": []<string>,
|
||||
"subdomain": <string>,
|
||||
"locations": <string>,
|
||||
"http_user": <string>,
|
||||
"http_pwd": <string>,
|
||||
"host_header_rewrite": <string>,
|
||||
"headers": map<string>string,
|
||||
|
||||
// stcp only
|
||||
"sk": <string>,
|
||||
|
||||
// tcpmux only
|
||||
"multiplexer": <string>
|
||||
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ping
|
||||
|
||||
Heartbeat from frpc
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewWorkConn
|
||||
|
||||
New work connection received from frpc (RPC sent after `run_id` is matched with an existing frp connection)
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"run_id": <string>
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewUserConn
|
||||
|
||||
New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcpmux`) .
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"remote_addr": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Server Plugin Configuration
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
ops = Login
|
||||
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9001
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
```
|
||||
|
||||
addr: the address where the external RPC service listens on.
|
||||
path: http request url path for the POST request.
|
||||
ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
||||
|
||||
### Metadata
|
||||
|
||||
Metadata will be sent to the server plugin in each RPC request.
|
||||
|
||||
There are 2 types of metadata entries - 1 under `[common]` and the other under each proxy configuration.
|
||||
Metadata entries under `[common]` will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
|
||||
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
|
||||
|
||||
Metadata entries start with `meta_`. This is an example of metadata entries in `[common]` and under the proxy named `[ssh]`:
|
||||
|
||||
```
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 7000
|
||||
user = fake
|
||||
meta_token = fake
|
||||
meta_version = 1.0.0
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
meta_id = 123
|
||||
```
|
||||
228
doc/server_plugin_zh.md
Normal file
228
doc/server_plugin_zh.md
Normal file
@@ -0,0 +1,228 @@
|
||||
### 服务端管理插件
|
||||
|
||||
frp 管理插件的作用是在不侵入自身代码的前提下,扩展 frp 服务端的能力。
|
||||
|
||||
frp 管理插件会以单独进程的形式运行,并且监听在一个端口上,对外提供 RPC 接口,响应 frps 的请求。
|
||||
|
||||
frps 在执行某些操作前,会根据配置向管理插件发送 RPC 请求,根据管理插件的响应来执行相应的操作。
|
||||
|
||||
### RPC 请求
|
||||
|
||||
管理插件接收到操作请求后,可以给出三种回应。
|
||||
|
||||
* 拒绝操作,需要返回拒绝操作的原因。
|
||||
* 允许操作,不需要修改操作内容。
|
||||
* 允许操作,对操作请求进行修改后,返回修改后的内容。
|
||||
|
||||
### 接口
|
||||
|
||||
接口路径可以在 frps 配置中为每个插件单独配置,这里以 `/handler` 为例。
|
||||
|
||||
Request
|
||||
|
||||
```
|
||||
POST /handler
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"op": "Login",
|
||||
"content": {
|
||||
... // 具体的操作信息
|
||||
}
|
||||
}
|
||||
|
||||
请求 Header
|
||||
X-Frp-Reqid: 用于追踪请求
|
||||
```
|
||||
|
||||
Response
|
||||
|
||||
非 200 的返回都认为是请求异常。
|
||||
|
||||
拒绝执行操作
|
||||
|
||||
```
|
||||
{
|
||||
"reject": true,
|
||||
"reject_reason": "invalid user"
|
||||
}
|
||||
```
|
||||
|
||||
允许且内容不需要变动
|
||||
|
||||
```
|
||||
{
|
||||
"reject": false,
|
||||
"unchange": true
|
||||
}
|
||||
```
|
||||
|
||||
允许且需要替换操作内容
|
||||
|
||||
```
|
||||
{
|
||||
"unchange": "false",
|
||||
"content": {
|
||||
... // 替换后的操作信息,格式必须和请求时的一致
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 操作类型
|
||||
|
||||
目前插件支持管理的操作类型有 `Login`、`NewProxy`、`Ping`、`NewWorkConn` 和 `NewUserConn`。
|
||||
|
||||
#### Login
|
||||
|
||||
用户登录操作信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"version": <string>,
|
||||
"hostname": <string>,
|
||||
"os": <string>,
|
||||
"arch": <string>,
|
||||
"user": <string>,
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>,
|
||||
"run_id": <string>,
|
||||
"pool_count": <int>,
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewProxy
|
||||
|
||||
创建代理的相关信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"use_encryption": <bool>,
|
||||
"use_compression": <bool>,
|
||||
"group": <string>,
|
||||
"group_key": <string>,
|
||||
|
||||
// tcp and udp only
|
||||
"remote_port": <int>,
|
||||
|
||||
// http and https only
|
||||
"custom_domains": []<string>,
|
||||
"subdomain": <string>,
|
||||
"locations": <string>,
|
||||
"http_user": <string>,
|
||||
"http_pwd": <string>,
|
||||
"host_header_rewrite": <string>,
|
||||
"headers": map<string>string,
|
||||
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ping
|
||||
|
||||
心跳相关信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewWorkConn
|
||||
|
||||
新增 `frpc` 连接相关信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"run_id": <string>
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewUserConn
|
||||
|
||||
新增 `proxy` 连接相关信息 (支持 `tcp`、`stcp`、`https` 和 `tcpmux` 协议)。
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"remote_addr": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### frps 中插件配置
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
ops = Login
|
||||
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9001
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
```
|
||||
|
||||
addr: 插件监听的网络地址。
|
||||
path: 插件监听的 HTTP 请求路径。
|
||||
ops: 插件需要处理的操作列表,多个 op 以英文逗号分隔。
|
||||
|
||||
### 元数据
|
||||
|
||||
为了减少 frps 的代码修改,同时提高管理插件的扩展能力,在 frpc 的配置文件中引入自定义元数据的概念。元数据会在调用 RPC 请求时发送给插件。
|
||||
|
||||
元数据以 `meta_` 开头,可以配置多个,元数据分为两种,一种配置在 `common` 下,一种配置在各个 proxy 中。
|
||||
|
||||
```
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 7000
|
||||
user = fake
|
||||
meta_token = fake
|
||||
meta_version = 1.0.0
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
meta_id = 123
|
||||
```
|
||||
14
dockerfiles/Dockerfile-for-frpc
Normal file
14
dockerfiles/Dockerfile-for-frpc
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM alpine:3.12.0 AS temp
|
||||
|
||||
COPY bin/frpc /tmp
|
||||
|
||||
RUN chmod -R 777 /tmp/frpc
|
||||
|
||||
|
||||
FROM alpine:3.12.0
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=temp /tmp/frpc /usr/bin
|
||||
|
||||
ENTRYPOINT ["/usr/bin/frpc"]
|
||||
14
dockerfiles/Dockerfile-for-frps
Normal file
14
dockerfiles/Dockerfile-for-frps
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM alpine:3.12.0 AS temp
|
||||
|
||||
COPY bin/frps /tmp
|
||||
|
||||
RUN chmod -R 777 /tmp/frps
|
||||
|
||||
|
||||
FROM alpine:3.12.0
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=temp /tmp/frps /usr/bin
|
||||
|
||||
ENTRYPOINT ["/usr/bin/frps"]
|
||||
32
g/g.go
32
g/g.go
@@ -1,32 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
var (
|
||||
GlbClientCfg *ClientCfg
|
||||
GlbServerCfg *ServerCfg
|
||||
)
|
||||
|
||||
func init() {
|
||||
GlbClientCfg = &ClientCfg{
|
||||
ClientCommonConf: *config.GetDefaultClientConf(),
|
||||
}
|
||||
GlbServerCfg = &ServerCfg{
|
||||
ServerCommonConf: *config.GetDefaultServerConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type ClientCfg struct {
|
||||
config.ClientCommonConf
|
||||
|
||||
CfgFile string
|
||||
ServerUdpPort int // this is configured by login response from frps
|
||||
}
|
||||
|
||||
type ServerCfg struct {
|
||||
config.ServerCommonConf
|
||||
|
||||
CfgFile string
|
||||
}
|
||||
39
go.mod
39
go.mod
@@ -1,32 +1,39 @@
|
||||
module github.com/fatedier/frp
|
||||
|
||||
go 1.12
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||
github.com/fatedier/kcp-go v0.0.0-20171023144637-cd167d2f15f4
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gorilla/websocket v1.2.0
|
||||
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0
|
||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo v1.12.3
|
||||
github.com/onsi/gomega v1.10.1
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v1.4.1
|
||||
github.com/rakyll/statik v0.1.1
|
||||
github.com/rodaine/table v1.0.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.1 // indirect
|
||||
github.com/stretchr/testify v1.2.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||
github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
k8s.io/apimachinery v0.18.3
|
||||
)
|
||||
|
||||
261
go.sum
261
go.sum
@@ -1,30 +1,263 @@
|
||||
cloud.google.com/go v0.34.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-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/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/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||
github.com/fatedier/kcp-go v0.0.0-20171023144637-cd167d2f15f4/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 h1:2p4W5xYizIYwhiGQgeHOQcRD2O84j0tjD40P6gUCRrk=
|
||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185/go.mod h1:MUs+IH/MGJNz5Cj2JVJBPZBKw2exON7LzO3HrJHmGiQ=
|
||||
github.com/fatedier/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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
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.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/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ=
|
||||
github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFYyOi8zA+Y8=
|
||||
github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76/go.mod h1:ToWcj2sZ6xHl14JjZiVDktYpFtrFZJXBlsu7TV23lNg=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
|
||||
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
||||
15
hack/run-e2e.sh
Executable file
15
hack/run-e2e.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
|
||||
|
||||
which ginkgo &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ginkgo not found, try to install..."
|
||||
go get -u github.com/onsi/ginkgo/ginkgo
|
||||
fi
|
||||
|
||||
debug=false
|
||||
if [ x${DEBUG} == x"true" ]; then
|
||||
debug=true
|
||||
fi
|
||||
ginkgo -nodes=4 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=debug -debug=${debug}
|
||||
@@ -1,228 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// client common config
|
||||
type ClientCommonConf struct {
|
||||
ServerAddr string `json:"server_addr"`
|
||||
ServerPort int `json:"server_port"`
|
||||
HttpProxy string `json:"http_proxy"`
|
||||
LogFile string `json:"log_file"`
|
||||
LogWay string `json:"log_way"`
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
AdminAddr string `json:"admin_addr"`
|
||||
AdminPort int `json:"admin_port"`
|
||||
AdminUser string `json:"admin_user"`
|
||||
AdminPwd string `json:"admin_pwd"`
|
||||
PoolCount int `json:"pool_count"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
User string `json:"user"`
|
||||
DnsServer string `json:"dns_server"`
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
Start map[string]struct{} `json:"start"`
|
||||
Protocol string `json:"protocol"`
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
}
|
||||
|
||||
func GetDefaultClientConf() *ClientCommonConf {
|
||||
return &ClientCommonConf{
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HttpProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
Token: "",
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
AdminPwd: "",
|
||||
PoolCount: 1,
|
||||
TcpMux: true,
|
||||
User: "",
|
||||
DnsServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
HeartBeatInterval: 30,
|
||||
HeartBeatTimeout: 90,
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
|
||||
cfg = defaultCfg
|
||||
if cfg == nil {
|
||||
cfg = GetDefaultClientConf()
|
||||
}
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
|
||||
cfg.ServerAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "server_port"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid server_port")
|
||||
return
|
||||
}
|
||||
cfg.ServerPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||
cfg.HttpProxy = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "token"); ok {
|
||||
cfg.Token = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
||||
cfg.AdminAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.AdminPort = int(v)
|
||||
} else {
|
||||
err = fmt.Errorf("Parse conf error: invalid admin_port")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
|
||||
cfg.AdminUser = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
|
||||
cfg.AdminPwd = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.PoolCount = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "user"); ok {
|
||||
cfg.User = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
|
||||
cfg.DnsServer = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "start"); ok {
|
||||
proxyNames := strings.Split(tmpStr, ",")
|
||||
for _, name := range proxyNames {
|
||||
cfg.Start[strings.TrimSpace(name)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
|
||||
cfg.LoginFailExit = false
|
||||
} else {
|
||||
cfg.LoginFailExit = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "protocol"); ok {
|
||||
// Now it only support tcp and kcp and websocket.
|
||||
if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
|
||||
err = fmt.Errorf("Parse conf error: invalid protocol")
|
||||
return
|
||||
}
|
||||
cfg.Protocol = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatTimeout = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatInterval = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Check() (err error) {
|
||||
if cfg.HeartBeatInterval <= 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
)
|
||||
|
||||
var (
|
||||
// server global configure used for generate proxy conf used in frps
|
||||
proxyBindAddr string
|
||||
subDomainHost string
|
||||
vhostHttpPort int
|
||||
vhostHttpsPort int
|
||||
)
|
||||
|
||||
func InitServerCfg(cfg *ServerCommonConf) {
|
||||
proxyBindAddr = cfg.ProxyBindAddr
|
||||
subDomainHost = cfg.SubDomainHost
|
||||
vhostHttpPort = cfg.VhostHttpPort
|
||||
vhostHttpsPort = cfg.VhostHttpsPort
|
||||
}
|
||||
|
||||
// common config
|
||||
type ServerCommonConf struct {
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUdpPort int `json:"bind_udp_port"`
|
||||
KcpBindPort int `json:"kcp_bind_port"`
|
||||
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||
|
||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
|
||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||
VhostHttpsPort int `json:"vhost_https_port"`
|
||||
|
||||
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||
|
||||
DashboardAddr string `json:"dashboard_addr"`
|
||||
|
||||
// if DashboardPort equals 0, dashboard is not available
|
||||
DashboardPort int `json:"dashboard_port"`
|
||||
DashboardUser string `json:"dashboard_user"`
|
||||
DashboardPwd string `json:"dashboard_pwd"`
|
||||
AssetsDir string `json:"asserts_dir"`
|
||||
LogFile string `json:"log_file"`
|
||||
LogWay string `json:"log_way"` // console or file
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
SubDomainHost string `json:"subdomain_host"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
|
||||
AllowPorts map[int]struct{}
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||
}
|
||||
|
||||
func GetDefaultServerConf() *ServerCommonConf {
|
||||
return &ServerCommonConf{
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUdpPort: 0,
|
||||
KcpBindPort: 0,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHttpPort: 0,
|
||||
VhostHttpsPort: 0,
|
||||
VhostHttpTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
Token: "",
|
||||
SubDomainHost: "",
|
||||
TcpMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
|
||||
cfg = defaultCfg
|
||||
if cfg == nil {
|
||||
cfg = GetDefaultServerConf()
|
||||
}
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
|
||||
cfg.BindAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_port")
|
||||
return
|
||||
} else {
|
||||
cfg.BindPort = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
|
||||
return
|
||||
} else {
|
||||
cfg.BindUdpPort = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
|
||||
return
|
||||
} else {
|
||||
cfg.KcpBindPort = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
|
||||
cfg.ProxyBindAddr = tmpStr
|
||||
} else {
|
||||
cfg.ProxyBindAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
|
||||
return
|
||||
} else {
|
||||
cfg.VhostHttpPort = int(v)
|
||||
}
|
||||
} else {
|
||||
cfg.VhostHttpPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
|
||||
return
|
||||
} else {
|
||||
cfg.VhostHttpsPort = int(v)
|
||||
}
|
||||
} else {
|
||||
cfg.VhostHttpsPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil || v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
|
||||
return
|
||||
} else {
|
||||
cfg.VhostHttpTimeout = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
|
||||
cfg.DashboardAddr = tmpStr
|
||||
} else {
|
||||
cfg.DashboardAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
|
||||
return
|
||||
} else {
|
||||
cfg.DashboardPort = int(v)
|
||||
}
|
||||
} else {
|
||||
cfg.DashboardPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
|
||||
cfg.DashboardUser = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
|
||||
cfg.DashboardPwd = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||
cfg.AssetsDir = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Token, _ = conf.Get("common", "token")
|
||||
|
||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
cfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
} else {
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
}
|
||||
cfg.MaxPoolCount = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
} else {
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
}
|
||||
cfg.MaxPortsPerClient = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
|
||||
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatTimeout = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Check() (err error) {
|
||||
return
|
||||
}
|
||||
12
package.sh
12
package.sh
@@ -11,12 +11,14 @@ echo "build version: $frp_version"
|
||||
# cross_compiles
|
||||
make -f ./Makefile.cross-compiles
|
||||
|
||||
rm -rf ./packages
|
||||
mkdir ./packages
|
||||
rm -rf ./release/packages
|
||||
mkdir -p ./release/packages
|
||||
|
||||
os_all='linux windows darwin freebsd'
|
||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
|
||||
|
||||
cd ./release
|
||||
|
||||
for os in $os_all; do
|
||||
for arch in $arch_all; do
|
||||
frp_dir_name="frp_${frp_version}_${os}_${arch}"
|
||||
@@ -43,8 +45,8 @@ for os in $os_all; do
|
||||
mv ./frpc_${os}_${arch} ${frp_path}/frpc
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
||||
cp ./LICENSE ${frp_path}
|
||||
cp ./conf/* ${frp_path}
|
||||
cp ../LICENSE ${frp_path}
|
||||
cp -rf ../conf/* ${frp_path}
|
||||
|
||||
# packages
|
||||
cd ./packages
|
||||
@@ -57,3 +59,5 @@ for os in $os_all; do
|
||||
rm -rf ${frp_path}
|
||||
done
|
||||
done
|
||||
|
||||
cd -
|
||||
|
||||
151
pkg/auth/auth.go
Normal file
151
pkg/auth/auth.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
type baseConfig struct {
|
||||
// AuthenticationMethod specifies what authentication method to use to
|
||||
// authenticate frpc with frps. If "token" is specified - token will be
|
||||
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||
// token will be issued using OIDC settings. By default, this value is "token".
|
||||
AuthenticationMethod string `json:"authentication_method"`
|
||||
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||
// heartbeats sent to frps. By default, this value is false.
|
||||
AuthenticateHeartBeats bool `json:"authenticate_heartbeats"`
|
||||
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||
// new work connections sent to frps. By default, this value is false.
|
||||
AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"`
|
||||
}
|
||||
|
||||
func getDefaultBaseConf() baseConfig {
|
||||
return baseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalBaseConfFromIni(conf ini.File) baseConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultBaseConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authentication_method"); ok {
|
||||
cfg.AuthenticationMethod = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authenticate_heartbeats"); ok && tmpStr == "true" {
|
||||
cfg.AuthenticateHeartBeats = true
|
||||
} else {
|
||||
cfg.AuthenticateHeartBeats = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" {
|
||||
cfg.AuthenticateNewWorkConns = true
|
||||
} else {
|
||||
cfg.AuthenticateNewWorkConns = false
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
baseConfig
|
||||
oidcClientConfig
|
||||
tokenConfig
|
||||
}
|
||||
|
||||
func GetDefaultClientConf() ClientConfig {
|
||||
return ClientConfig{
|
||||
baseConfig: getDefaultBaseConf(),
|
||||
oidcClientConfig: getDefaultOidcClientConf(),
|
||||
tokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalClientConfFromIni(conf ini.File) (cfg ClientConfig) {
|
||||
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
|
||||
cfg.oidcClientConfig = unmarshalOidcClientConfFromIni(conf)
|
||||
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
baseConfig
|
||||
oidcServerConfig
|
||||
tokenConfig
|
||||
}
|
||||
|
||||
func GetDefaultServerConf() ServerConfig {
|
||||
return ServerConfig{
|
||||
baseConfig: getDefaultBaseConf(),
|
||||
oidcServerConfig: getDefaultOidcServerConf(),
|
||||
tokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalServerConfFromIni(conf ini.File) (cfg ServerConfig) {
|
||||
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
|
||||
cfg.oidcServerConfig = unmarshalOidcServerConfFromIni(conf)
|
||||
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type Setter interface {
|
||||
SetLogin(*msg.Login) error
|
||||
SetPing(*msg.Ping) error
|
||||
SetNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
|
||||
switch cfg.AuthenticationMethod {
|
||||
case consts.TokenAuthMethod:
|
||||
authProvider = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
|
||||
case consts.OidcAuthMethod:
|
||||
authProvider = NewOidcAuthSetter(cfg.baseConfig, cfg.oidcClientConfig)
|
||||
default:
|
||||
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
|
||||
}
|
||||
|
||||
return authProvider
|
||||
}
|
||||
|
||||
type Verifier interface {
|
||||
VerifyLogin(*msg.Login) error
|
||||
VerifyPing(*msg.Ping) error
|
||||
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
|
||||
switch cfg.AuthenticationMethod {
|
||||
case consts.TokenAuthMethod:
|
||||
authVerifier = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
|
||||
case consts.OidcAuthMethod:
|
||||
authVerifier = NewOidcAuthVerifier(cfg.baseConfig, cfg.oidcServerConfig)
|
||||
}
|
||||
|
||||
return authVerifier
|
||||
}
|
||||
255
pkg/auth/oidc.go
Normal file
255
pkg/auth/oidc.go
Normal file
@@ -0,0 +1,255 @@
|
||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/vaughan0/go-ini"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
)
|
||||
|
||||
type oidcClientConfig struct {
|
||||
// OidcClientID specifies the client ID to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientID string `json:"oidc_client_id"`
|
||||
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientSecret string `json:"oidc_client_secret"`
|
||||
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||
//if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcAudience string `json:"oidc_audience"`
|
||||
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
OidcTokenEndpointURL string `json:"oidc_token_endpoint_url"`
|
||||
}
|
||||
|
||||
func getDefaultOidcClientConf() oidcClientConfig {
|
||||
return oidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultOidcClientConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok {
|
||||
cfg.OidcClientID = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_client_secret"); ok {
|
||||
cfg.OidcClientSecret = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_audience"); ok {
|
||||
cfg.OidcAudience = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_token_endpoint_url"); ok {
|
||||
cfg.OidcTokenEndpointURL = tmpStr
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type oidcServerConfig struct {
|
||||
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||
// will be used to load public keys to verify signature and will be compared
|
||||
// with the issuer claim in the OIDC token. It will be used if
|
||||
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcIssuer string `json:"oidc_issuer"`
|
||||
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is "".
|
||||
OidcAudience string `json:"oidc_audience"`
|
||||
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is false.
|
||||
OidcSkipExpiryCheck bool `json:"oidc_skip_expiry_check"`
|
||||
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||
OidcSkipIssuerCheck bool `json:"oidc_skip_issuer_check"`
|
||||
}
|
||||
|
||||
func getDefaultOidcServerConf() oidcServerConfig {
|
||||
return oidcServerConfig{
|
||||
OidcIssuer: "",
|
||||
OidcAudience: "",
|
||||
OidcSkipExpiryCheck: false,
|
||||
OidcSkipIssuerCheck: false,
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalOidcServerConfFromIni(conf ini.File) oidcServerConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultOidcServerConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok {
|
||||
cfg.OidcIssuer = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_audience"); ok {
|
||||
cfg.OidcAudience = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_skip_expiry_check"); ok && tmpStr == "true" {
|
||||
cfg.OidcSkipExpiryCheck = true
|
||||
} else {
|
||||
cfg.OidcSkipExpiryCheck = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "oidc_skip_issuer_check"); ok && tmpStr == "true" {
|
||||
cfg.OidcSkipIssuerCheck = true
|
||||
} else {
|
||||
cfg.OidcSkipIssuerCheck = false
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type OidcAuthProvider struct {
|
||||
baseConfig
|
||||
|
||||
tokenGenerator *clientcredentials.Config
|
||||
}
|
||||
|
||||
func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvider {
|
||||
tokenGenerator := &clientcredentials.Config{
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcAudience},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
}
|
||||
|
||||
return &OidcAuthProvider{
|
||||
baseConfig: baseCfg,
|
||||
tokenGenerator: tokenGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *OidcAuthProvider) generateAccessToken() (accessToken string, err error) {
|
||||
tokenObj, err := auth.tokenGenerator.Token(context.Background())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't generate OIDC token for login: %v", err)
|
||||
}
|
||||
return tokenObj.AccessToken, nil
|
||||
}
|
||||
|
||||
func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
|
||||
loginMsg.PrivilegeKey, err = auth.generateAccessToken()
|
||||
return err
|
||||
}
|
||||
|
||||
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
return nil
|
||||
}
|
||||
|
||||
pingMsg.PrivilegeKey, err = auth.generateAccessToken()
|
||||
return err
|
||||
}
|
||||
|
||||
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
return nil
|
||||
}
|
||||
|
||||
newWorkConnMsg.PrivilegeKey, err = auth.generateAccessToken()
|
||||
return err
|
||||
}
|
||||
|
||||
type OidcAuthConsumer struct {
|
||||
baseConfig
|
||||
|
||||
verifier *oidc.IDTokenVerifier
|
||||
subjectFromLogin string
|
||||
}
|
||||
|
||||
func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthConsumer {
|
||||
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
verifierConf := oidc.Config{
|
||||
ClientID: cfg.OidcAudience,
|
||||
SkipClientIDCheck: cfg.OidcAudience == "",
|
||||
SkipExpiryCheck: cfg.OidcSkipExpiryCheck,
|
||||
SkipIssuerCheck: cfg.OidcSkipIssuerCheck,
|
||||
}
|
||||
return &OidcAuthConsumer{
|
||||
baseConfig: baseCfg,
|
||||
verifier: provider.Verifier(&verifierConf),
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
|
||||
token, err := auth.verifier.Verify(context.Background(), loginMsg.PrivilegeKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid OIDC token in login: %v", err)
|
||||
}
|
||||
auth.subjectFromLogin = token.Subject
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) {
|
||||
token, err := auth.verifier.Verify(context.Background(), privilegeKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid OIDC token in ping: %v", err)
|
||||
}
|
||||
if token.Subject != auth.subjectFromLogin {
|
||||
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
||||
"original subject: %s, "+
|
||||
"new subject: %s",
|
||||
auth.subjectFromLogin, token.Subject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
return nil
|
||||
}
|
||||
|
||||
return auth.verifyPostLoginToken(pingMsg.PrivilegeKey)
|
||||
}
|
||||
|
||||
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
return nil
|
||||
}
|
||||
|
||||
return auth.verifyPostLoginToken(newWorkConnMsg.PrivilegeKey)
|
||||
}
|
||||
120
pkg/auth/token.go
Normal file
120
pkg/auth/token.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
type tokenConfig struct {
|
||||
// Token specifies the authorization token used to create keys to be sent
|
||||
// to the server. The server must have a matching token for authorization
|
||||
// to succeed. By default, this value is "".
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func getDefaultTokenConf() tokenConfig {
|
||||
return tokenConfig{
|
||||
Token: "",
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalTokenConfFromIni(conf ini.File) tokenConfig {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
|
||||
cfg := getDefaultTokenConf()
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "token"); ok {
|
||||
cfg.Token = tmpStr
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type TokenAuthSetterVerifier struct {
|
||||
baseConfig
|
||||
|
||||
token string
|
||||
}
|
||||
|
||||
func NewTokenAuth(baseCfg baseConfig, cfg tokenConfig) *TokenAuthSetterVerifier {
|
||||
return &TokenAuthSetterVerifier{
|
||||
baseConfig: baseCfg,
|
||||
token: cfg.Token,
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) {
|
||||
loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
return nil
|
||||
}
|
||||
|
||||
pingMsg.Timestamp = time.Now().Unix()
|
||||
pingMsg.PrivilegeKey = util.GetAuthKey(auth.token, pingMsg.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
return nil
|
||||
}
|
||||
|
||||
newWorkConnMsg.Timestamp = time.Now().Unix()
|
||||
newWorkConnMsg.PrivilegeKey = util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error {
|
||||
if util.GetAuthKey(auth.token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
return fmt.Errorf("token in login doesn't match token from configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyPing(pingMsg *msg.Ping) error {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
return nil
|
||||
}
|
||||
|
||||
if util.GetAuthKey(auth.token, pingMsg.Timestamp) != pingMsg.PrivilegeKey {
|
||||
return fmt.Errorf("token in heartbeat doesn't match token from configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
return nil
|
||||
}
|
||||
|
||||
if util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) != newWorkConnMsg.PrivilegeKey {
|
||||
return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
365
pkg/config/client_common.go
Normal file
365
pkg/config/client_common.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// ClientCommonConf contains information for a client service. It is
|
||||
// recommended to use GetDefaultClientConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ClientCommonConf struct {
|
||||
auth.ClientConfig
|
||||
// ServerAddr specifies the address of the server to connect to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
ServerAddr string `json:"server_addr"`
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `json:"server_port"`
|
||||
// HTTPProxy specifies a proxy address to connect to the server through. If
|
||||
// this value is "", the server will be connected to directly. By default,
|
||||
// this value is read from the "http_proxy" environment variable.
|
||||
HTTPProxy string `json:"http_proxy"`
|
||||
// LogFile specifies a file where logs will be written to. This value will
|
||||
// only be used if LogWay is set appropriately. By default, this value is
|
||||
// "console".
|
||||
LogFile string `json:"log_file"`
|
||||
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||
// is "console".
|
||||
LogWay string `json:"log_way"`
|
||||
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||
LogLevel string `json:"log_level"`
|
||||
// LogMaxDays specifies the maximum number of days to store log information
|
||||
// before deletion. This is only used if LogWay == "file". By default, this
|
||||
// value is 0.
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||
// true. By default, this value is false.
|
||||
DisableLogColor bool `json:"disable_log_color"`
|
||||
// AdminAddr specifies the address that the admin server binds to. By
|
||||
// default, this value is "127.0.0.1".
|
||||
AdminAddr string `json:"admin_addr"`
|
||||
// AdminPort specifies the port for the admin server to listen on. If this
|
||||
// value is 0, the admin server will not be started. By default, this value
|
||||
// is 0.
|
||||
AdminPort int `json:"admin_port"`
|
||||
// AdminUser specifies the username that the admin server will use for
|
||||
// login. By default, this value is "admin".
|
||||
AdminUser string `json:"admin_user"`
|
||||
// AdminPwd specifies the password that the admin server will use for
|
||||
// login. By default, this value is "admin".
|
||||
AdminPwd string `json:"admin_pwd"`
|
||||
// AssetsDir specifies the local directory that the admin server will load
|
||||
// resources from. If this value is "", assets will be loaded from the
|
||||
// bundled executable using statik. By default, this value is "".
|
||||
AssetsDir string `json:"assets_dir"`
|
||||
// PoolCount specifies the number of connections the client will make to
|
||||
// the server in advance. By default, this value is 0.
|
||||
PoolCount int `json:"pool_count"`
|
||||
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
|
||||
// from a client to share a single TCP connection. If this value is true,
|
||||
// the server must have TCP multiplexing enabled as well. By default, this
|
||||
// value is true.
|
||||
TCPMux bool `json:"tcp_mux"`
|
||||
// User specifies a prefix for proxy names to distinguish them from other
|
||||
// clients. If this value is not "", proxy names will automatically be
|
||||
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||
User string `json:"user"`
|
||||
// DNSServer specifies a DNS server address for FRPC to use. If this value
|
||||
// is "", the default DNS will be used. By default, this value is "".
|
||||
DNSServer string `json:"dns_server"`
|
||||
// LoginFailExit controls whether or not the client should exit after a
|
||||
// failed login attempt. If false, the client will retry until a login
|
||||
// attempt succeeds. By default, this value is true.
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
// Start specifies a set of enabled proxies by name. If this set is empty,
|
||||
// all supplied proxies are enabled. By default, this value is an empty
|
||||
// set.
|
||||
Start map[string]struct{} `json:"start"`
|
||||
// Protocol specifies the protocol to use when interacting with the server.
|
||||
// Valid values are "tcp", "kcp" and "websocket". By default, this value
|
||||
// is "tcp".
|
||||
Protocol string `json:"protocol"`
|
||||
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||
// with the server. If "tls_cert_file" and "tls_key_file" are valid,
|
||||
// client will load the supplied tls configuration.
|
||||
TLSEnable bool `json:"tls_enable"`
|
||||
// ClientTLSCertPath specifies the path of the cert file that client will
|
||||
// load. It only works when "tls_enable" is true and "tls_key_file" is valid.
|
||||
TLSCertFile string `json:"tls_cert_file"`
|
||||
// ClientTLSKeyPath specifies the path of the secret key file that client
|
||||
// will load. It only works when "tls_enable" is true and "tls_cert_file"
|
||||
// are valid.
|
||||
TLSKeyFile string `json:"tls_key_file"`
|
||||
// TrustedCaFile specifies the path of the trusted ca file that will load.
|
||||
// It only works when "tls_enable" is valid and tls configuration of server
|
||||
// has been specified.
|
||||
TLSTrustedCaFile string `json:"tls_trusted_ca_file"`
|
||||
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||
// server, in seconds. It is not recommended to change this value. By
|
||||
// default, this value is 30.
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||
// before the connection is terminated, in seconds. It is not recommended
|
||||
// to change this value. By default, this value is 90.
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
// Client meta info
|
||||
Metas map[string]string `json:"metas"`
|
||||
// UDPPacketSize specifies the udp packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `json:"udp_packet_size"`
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
AdminPwd: "",
|
||||
AssetsDir: "",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
User: "",
|
||||
DNSServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartBeatInterval: 30,
|
||||
HeartBeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
|
||||
cfg = GetDefaultClientConf()
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
|
||||
}
|
||||
|
||||
cfg.ClientConfig = auth.UnmarshalClientConfFromIni(conf)
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
|
||||
cfg.ServerAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "server_port"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid server_port")
|
||||
return
|
||||
}
|
||||
cfg.ServerPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||
cfg.DisableLogColor = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||
cfg.HTTPProxy = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
||||
cfg.AdminAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.AdminPort = int(v)
|
||||
} else {
|
||||
err = fmt.Errorf("Parse conf error: invalid admin_port")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
|
||||
cfg.AdminUser = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
|
||||
cfg.AdminPwd = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||
cfg.AssetsDir = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.PoolCount = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TCPMux = false
|
||||
} else {
|
||||
cfg.TCPMux = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "user"); ok {
|
||||
cfg.User = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
|
||||
cfg.DNSServer = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "start"); ok {
|
||||
proxyNames := strings.Split(tmpStr, ",")
|
||||
for _, name := range proxyNames {
|
||||
cfg.Start[strings.TrimSpace(name)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
|
||||
cfg.LoginFailExit = false
|
||||
} else {
|
||||
cfg.LoginFailExit = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "protocol"); ok {
|
||||
// Now it only support tcp and kcp and websocket.
|
||||
if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
|
||||
err = fmt.Errorf("Parse conf error: invalid protocol")
|
||||
return
|
||||
}
|
||||
cfg.Protocol = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
|
||||
cfg.TLSEnable = true
|
||||
} else {
|
||||
cfg.TLSEnable = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_cert_file"); ok {
|
||||
cfg.TLSCertFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_key_file"); ok {
|
||||
cfg.TLSKeyFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok {
|
||||
cfg.TLSTrustedCaFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||
return
|
||||
}
|
||||
cfg.HeartBeatTimeout = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
}
|
||||
cfg.HeartBeatInterval = v
|
||||
}
|
||||
for k, v := range conf.Section("common") {
|
||||
if strings.HasPrefix(k, "meta_") {
|
||||
cfg.Metas[strings.TrimPrefix(k, "meta_")] = v
|
||||
}
|
||||
}
|
||||
if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid udp_packet_size")
|
||||
return
|
||||
}
|
||||
cfg.UDPPacketSize = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Check() (err error) {
|
||||
if cfg.HeartBeatInterval <= 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.TLSEnable == false {
|
||||
if cfg.TLSCertFile != "" {
|
||||
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
|
||||
}
|
||||
|
||||
if cfg.TLSKeyFile != "" {
|
||||
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
|
||||
}
|
||||
|
||||
if cfg.TLSTrustedCaFile != "" {
|
||||
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
@@ -33,12 +33,14 @@ var (
|
||||
|
||||
func init() {
|
||||
proxyConfTypeMap = make(map[string]reflect.Type)
|
||||
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
|
||||
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
||||
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
||||
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
|
||||
proxyConfTypeMap[consts.TCPProxy] = reflect.TypeOf(TCPProxyConf{})
|
||||
proxyConfTypeMap[consts.TCPMuxProxy] = reflect.TypeOf(TCPMuxProxyConf{})
|
||||
proxyConfTypeMap[consts.UDPProxy] = reflect.TypeOf(UDPProxyConf{})
|
||||
proxyConfTypeMap[consts.HTTPProxy] = reflect.TypeOf(HTTPProxyConf{})
|
||||
proxyConfTypeMap[consts.HTTPSProxy] = reflect.TypeOf(HTTPSProxyConf{})
|
||||
proxyConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPProxyConf{})
|
||||
proxyConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPProxyConf{})
|
||||
proxyConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPProxyConf{})
|
||||
}
|
||||
|
||||
// NewConfByType creates a empty ProxyConf object by proxyType.
|
||||
@@ -58,13 +60,13 @@ type ProxyConf interface {
|
||||
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
||||
MarshalToMsg(pMsg *msg.NewProxy)
|
||||
CheckForCli() error
|
||||
CheckForSvr() error
|
||||
CheckForSvr(serverCfg ServerCommonConf) error
|
||||
Compare(conf ProxyConf) bool
|
||||
}
|
||||
|
||||
func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
||||
func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
|
||||
if pMsg.ProxyType == "" {
|
||||
pMsg.ProxyType = consts.TcpProxy
|
||||
pMsg.ProxyType = consts.TCPProxy
|
||||
}
|
||||
|
||||
cfg = NewConfByType(pMsg.ProxyType)
|
||||
@@ -73,15 +75,15 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
||||
return
|
||||
}
|
||||
cfg.UnmarshalFromMsg(pMsg)
|
||||
err = cfg.CheckForSvr()
|
||||
err = cfg.CheckForSvr(serverCfg)
|
||||
return
|
||||
}
|
||||
|
||||
func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg ProxyConf, err error) {
|
||||
proxyType := section["type"]
|
||||
if proxyType == "" {
|
||||
proxyType = consts.TcpProxy
|
||||
section["type"] = consts.TcpProxy
|
||||
proxyType = consts.TCPProxy
|
||||
section["type"] = consts.TCPProxy
|
||||
}
|
||||
cfg = NewConfByType(proxyType)
|
||||
if cfg == nil {
|
||||
@@ -97,18 +99,44 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
|
||||
return
|
||||
}
|
||||
|
||||
// BaseProxy info
|
||||
// BaseProxyConf provides configuration info that is common to all proxy types.
|
||||
type BaseProxyConf struct {
|
||||
// ProxyName is the name of this proxy.
|
||||
ProxyName string `json:"proxy_name"`
|
||||
// ProxyType specifies the type of this proxy. Valid values include "tcp",
|
||||
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||
// "tcp".
|
||||
ProxyType string `json:"proxy_type"`
|
||||
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
// UseEncryption controls whether or not communication with the server will
|
||||
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||
// and client configuration. By default, this value is false.
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
// UseCompression controls whether or not communication with the server
|
||||
// will be compressed. By default, this value is false.
|
||||
UseCompression bool `json:"use_compression"`
|
||||
// Group specifies which group the proxy is a part of. The server will use
|
||||
// this information to load balance proxies in the same group. If the value
|
||||
// is "", this proxy will not be in a group. By default, this value is "".
|
||||
Group string `json:"group"`
|
||||
// GroupKey specifies a group key, which should be the same among proxies
|
||||
// of the same group. By default, this value is "".
|
||||
GroupKey string `json:"group_key"`
|
||||
|
||||
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||
// version will be automatically selected. By default, this value is "".
|
||||
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||
|
||||
// BandwidthLimit limit the proxy bandwidth
|
||||
// 0 means no limit
|
||||
BandwidthLimit BandwidthQuantity `json:"bandwidth_limit"`
|
||||
|
||||
// meta info for each proxy
|
||||
Metas map[string]string `json:"metas"`
|
||||
|
||||
LocalSvrConf
|
||||
HealthCheckConf // only used for client
|
||||
HealthCheckConf
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||
@@ -121,7 +149,10 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
||||
cfg.UseEncryption != cmp.UseEncryption ||
|
||||
cfg.UseCompression != cmp.UseCompression ||
|
||||
cfg.Group != cmp.Group ||
|
||||
cfg.GroupKey != cmp.GroupKey {
|
||||
cfg.GroupKey != cmp.GroupKey ||
|
||||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion ||
|
||||
!cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) ||
|
||||
!reflect.DeepEqual(cfg.Metas, cmp.Metas) {
|
||||
return false
|
||||
}
|
||||
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||
@@ -140,12 +171,14 @@ func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.UseCompression = pMsg.UseCompression
|
||||
cfg.Group = pMsg.Group
|
||||
cfg.GroupKey = pMsg.GroupKey
|
||||
cfg.Metas = pMsg.Metas
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
cfg.ProxyName = prefix + name
|
||||
cfg.ProxyType = section["type"]
|
||||
@@ -162,24 +195,36 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
|
||||
cfg.Group = section["group"]
|
||||
cfg.GroupKey = section["group_key"]
|
||||
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
||||
|
||||
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
if cfg.BandwidthLimit, err = NewBandwidthQuantity(section["bandwidth_limit"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
|
||||
cfg.HealthCheckAddr = cfg.LocalIp + fmt.Sprintf(":%d", cfg.LocalPort)
|
||||
cfg.HealthCheckAddr = cfg.LocalIP + fmt.Sprintf(":%d", cfg.LocalPort)
|
||||
}
|
||||
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckUrl != "" {
|
||||
s := fmt.Sprintf("http://%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
if !strings.HasPrefix(cfg.HealthCheckUrl, "/") {
|
||||
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
|
||||
s := fmt.Sprintf("http://%s:%d", cfg.LocalIP, cfg.LocalPort)
|
||||
if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
|
||||
s += "/"
|
||||
}
|
||||
cfg.HealthCheckUrl = s + cfg.HealthCheckUrl
|
||||
cfg.HealthCheckURL = s + cfg.HealthCheckURL
|
||||
}
|
||||
|
||||
cfg.Metas = make(map[string]string)
|
||||
for k, v := range section {
|
||||
if strings.HasPrefix(k, "meta_") {
|
||||
cfg.Metas[strings.TrimPrefix(k, "meta_")] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -191,9 +236,16 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.UseCompression = cfg.UseCompression
|
||||
pMsg.Group = cfg.Group
|
||||
pMsg.GroupKey = cfg.GroupKey
|
||||
pMsg.Metas = cfg.Metas
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||
if cfg.ProxyProtocolVersion != "" {
|
||||
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -228,9 +280,8 @@ func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
if tmpStr, ok = section["remote_port"]; ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
|
||||
} else {
|
||||
cfg.RemotePort = int(v)
|
||||
}
|
||||
cfg.RemotePort = int(v)
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
|
||||
}
|
||||
@@ -298,21 +349,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) checkForSvr() (err error) {
|
||||
func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if err = cfg.check(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, domain := range cfg.CustomDomains {
|
||||
if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, subDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
|
||||
if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, serverCfg.SubDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.SubDomain != "" {
|
||||
if subDomainHost == "" {
|
||||
if serverCfg.SubDomainHost == "" {
|
||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
||||
}
|
||||
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
||||
@@ -322,17 +373,25 @@ func (cfg *DomainConf) checkForSvr() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Local service info
|
||||
// LocalSvrConf configures what location the client will proxy to, or what
|
||||
// plugin will be used.
|
||||
type LocalSvrConf struct {
|
||||
LocalIp string `json:"local_ip"`
|
||||
LocalPort int `json:"local_port"`
|
||||
// LocalIP specifies the IP address or host name to proxy to.
|
||||
LocalIP string `json:"local_ip"`
|
||||
// LocalPort specifies the port to proxy to.
|
||||
LocalPort int `json:"local_port"`
|
||||
|
||||
Plugin string `json:"plugin"`
|
||||
// Plugin specifies what plugin should be used for proxying. If this value
|
||||
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||
// this value is "".
|
||||
Plugin string `json:"plugin"`
|
||||
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||
// being used. By default, this value is an empty map.
|
||||
PluginParams map[string]string `json:"plugin_params"`
|
||||
}
|
||||
|
||||
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
|
||||
if cfg.LocalIp != cmp.LocalIp ||
|
||||
if cfg.LocalIP != cmp.LocalIP ||
|
||||
cfg.LocalPort != cmp.LocalPort {
|
||||
return false
|
||||
}
|
||||
@@ -360,8 +419,8 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
|
||||
cfg.LocalIp = "127.0.0.1"
|
||||
if cfg.LocalIP = section["local_ip"]; cfg.LocalIP == "" {
|
||||
cfg.LocalIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
if tmpStr, ok := section["local_port"]; ok {
|
||||
@@ -377,7 +436,7 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
|
||||
func (cfg *LocalSvrConf) checkForCli() (err error) {
|
||||
if cfg.Plugin == "" {
|
||||
if cfg.LocalIp == "" {
|
||||
if cfg.LocalIP == "" {
|
||||
err = fmt.Errorf("local ip or plugin is required")
|
||||
return
|
||||
}
|
||||
@@ -389,15 +448,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Health check info
|
||||
// HealthCheckConf configures health checking. This can be useful for load
|
||||
// balancing purposes to detect and remove proxies to failing services.
|
||||
type HealthCheckConf struct {
|
||||
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||
HealthCheckUrl string `json:"health_check_url"`
|
||||
|
||||
// local_ip + local_port
|
||||
// HealthCheckType specifies what protocol to use for health checking.
|
||||
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||
// checking will not be performed. By default, this value is "".
|
||||
//
|
||||
// If the type is "tcp", a connection will be attempted to the target
|
||||
// server. If a connection cannot be established, the health check fails.
|
||||
//
|
||||
// If the type is "http", a GET request will be made to the endpoint
|
||||
// specified by HealthCheckURL. If the response is not a 200, the health
|
||||
// check fails.
|
||||
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
|
||||
// check attempt to connect. If the timeout is reached, this counts as a
|
||||
// health check failure. By default, this value is 3.
|
||||
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||
// proxy is stopped. By default, this value is 1.
|
||||
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||
// HealthCheckIntervalS specifies the time in seconds between health
|
||||
// checks. By default, this value is 10.
|
||||
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||
// HealthCheckURL specifies the address to send health checks to if the
|
||||
// health check type is "http".
|
||||
HealthCheckURL string `json:"health_check_url"`
|
||||
// HealthCheckAddr specifies the address to connect to if the health check
|
||||
// type is "tcp".
|
||||
HealthCheckAddr string `json:"-"`
|
||||
}
|
||||
|
||||
@@ -406,7 +485,7 @@ func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
|
||||
cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS ||
|
||||
cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed ||
|
||||
cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS ||
|
||||
cfg.HealthCheckUrl != cmp.HealthCheckUrl {
|
||||
cfg.HealthCheckURL != cmp.HealthCheckURL {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -414,7 +493,7 @@ func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
|
||||
|
||||
func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
cfg.HealthCheckType = section["health_check_type"]
|
||||
cfg.HealthCheckUrl = section["health_check_url"]
|
||||
cfg.HealthCheckURL = section["health_check_url"]
|
||||
|
||||
if tmpStr, ok := section["health_check_timeout_s"]; ok {
|
||||
if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil {
|
||||
@@ -441,7 +520,7 @@ func (cfg *HealthCheckConf) checkForCli() error {
|
||||
return fmt.Errorf("unsupport health check type")
|
||||
}
|
||||
if cfg.HealthCheckType != "" {
|
||||
if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" {
|
||||
if cfg.HealthCheckType == "http" && cfg.HealthCheckURL == "" {
|
||||
return fmt.Errorf("health_check_url is required for health check type 'http'")
|
||||
}
|
||||
}
|
||||
@@ -449,13 +528,13 @@ func (cfg *HealthCheckConf) checkForCli() error {
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxyConf struct {
|
||||
type TCPProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*TcpProxyConf)
|
||||
func (cfg *TCPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*TCPProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -467,12 +546,12 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *TCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -482,28 +561,106 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *TCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
||||
func (cfg *TCPProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
||||
func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||
|
||||
// TCP Multiplexer
|
||||
type TCPMuxProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
|
||||
Multiplexer string `json:"multiplexer"`
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*TCPMuxProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||
cfg.Multiplexer != cmpConf.Multiplexer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.Multiplexer = pMsg.Multiplexer
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Multiplexer = section["multiplexer"]
|
||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
||||
return fmt.Errorf("parse conf error: proxy [%s] incorrect multiplexer [%s]", name, cfg.Multiplexer)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||
pMsg.Multiplexer = cfg.Multiplexer
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = cfg.DomainConf.checkForCli(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
||||
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
||||
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
|
||||
}
|
||||
|
||||
if cfg.Multiplexer == consts.HTTPConnectTCPMultiplexer && serverCfg.TCPMuxHTTPConnectPort == 0 {
|
||||
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
|
||||
}
|
||||
|
||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxyConf struct {
|
||||
type UDPProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*UdpProxyConf)
|
||||
func (cfg *UDPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*UDPProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -515,12 +672,12 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *UDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -530,34 +687,34 @@ func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *UDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) CheckForCli() (err error) {
|
||||
func (cfg *UDPProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
|
||||
func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||
|
||||
// HTTP
|
||||
type HttpProxyConf struct {
|
||||
type HTTPProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
|
||||
Locations []string `json:"locations"`
|
||||
HttpUser string `json:"http_user"`
|
||||
HttpPwd string `json:"http_pwd"`
|
||||
HTTPUser string `json:"http_user"`
|
||||
HTTPPwd string `json:"http_pwd"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*HttpProxyConf)
|
||||
func (cfg *HTTPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*HTTPProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -566,36 +723,36 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
|
||||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
|
||||
cfg.HttpUser != cmpConf.HttpUser ||
|
||||
cfg.HttpPwd != cmpConf.HttpPwd ||
|
||||
cfg.HTTPUser != cmpConf.HTTPUser ||
|
||||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
|
||||
len(cfg.Headers) != len(cmpConf.Headers) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k, v := range cfg.Headers {
|
||||
if v2, ok := cmpConf.Headers[k]; !ok {
|
||||
v2, ok := cmpConf.Headers[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if v != v2 {
|
||||
return false
|
||||
} else {
|
||||
if v != v2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||
|
||||
cfg.Locations = pMsg.Locations
|
||||
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
|
||||
cfg.HttpUser = pMsg.HttpUser
|
||||
cfg.HttpPwd = pMsg.HttpPwd
|
||||
cfg.HTTPUser = pMsg.HTTPUser
|
||||
cfg.HTTPPwd = pMsg.HTTPPwd
|
||||
cfg.Headers = pMsg.Headers
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -614,8 +771,8 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
}
|
||||
|
||||
cfg.HostHeaderRewrite = section["host_header_rewrite"]
|
||||
cfg.HttpUser = section["http_user"]
|
||||
cfg.HttpPwd = section["http_pwd"]
|
||||
cfg.HTTPUser = section["http_user"]
|
||||
cfg.HTTPPwd = section["http_pwd"]
|
||||
cfg.Headers = make(map[string]string)
|
||||
|
||||
for k, v := range section {
|
||||
@@ -626,18 +783,18 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||
|
||||
pMsg.Locations = cfg.Locations
|
||||
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
|
||||
pMsg.HttpUser = cfg.HttpUser
|
||||
pMsg.HttpPwd = cfg.HttpPwd
|
||||
pMsg.HTTPUser = cfg.HTTPUser
|
||||
pMsg.HTTPPwd = cfg.HTTPPwd
|
||||
pMsg.Headers = cfg.Headers
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
||||
func (cfg *HTTPProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -647,11 +804,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
||||
if vhostHttpPort == 0 {
|
||||
func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if serverCfg.VhostHTTPPort == 0 {
|
||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
}
|
||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
@@ -659,13 +816,13 @@ func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxyConf struct {
|
||||
type HTTPSProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*HttpsProxyConf)
|
||||
func (cfg *HTTPSProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*HTTPSProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -677,12 +834,12 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *HTTPSProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -692,12 +849,12 @@ func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *HTTPSProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
||||
func (cfg *HTTPSProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -707,27 +864,27 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
|
||||
if vhostHttpsPort == 0 {
|
||||
func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
if serverCfg.VhostHTTPSPort == 0 {
|
||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||
}
|
||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// STCP
|
||||
type StcpProxyConf struct {
|
||||
// SUDP
|
||||
type SUDPProxyConf struct {
|
||||
BaseProxyConf
|
||||
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*StcpProxyConf)
|
||||
func (cfg *SUDPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*SUDPProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -740,13 +897,7 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Only for role server.
|
||||
func (cfg *StcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.Sk = pMsg.Sk
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -764,12 +915,12 @@ func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *SUDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
pMsg.Sk = cfg.Sk
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
||||
func (cfg *SUDPProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -780,20 +931,92 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
|
||||
func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XtcpProxyConf struct {
|
||||
// Only for role server.
|
||||
func (cfg *SUDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.Sk = pMsg.Sk
|
||||
}
|
||||
|
||||
// STCP
|
||||
type STCPProxyConf struct {
|
||||
BaseProxyConf
|
||||
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*XtcpProxyConf)
|
||||
func (cfg *STCPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*STCPProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
cfg.Role != cmpConf.Role ||
|
||||
cfg.Sk != cmpConf.Sk {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Only for role server.
|
||||
func (cfg *STCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.Sk = pMsg.Sk
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Role = section["role"]
|
||||
if cfg.Role != "server" {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||
}
|
||||
|
||||
cfg.Sk = section["sk"]
|
||||
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
pMsg.Sk = cfg.Sk
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
if cfg.Role != "server" {
|
||||
err = fmt.Errorf("role should be 'server'")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XTCPProxyConf struct {
|
||||
BaseProxyConf
|
||||
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) Compare(cmp ProxyConf) bool {
|
||||
cmpConf, ok := cmp.(*XTCPProxyConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -808,12 +1031,12 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
}
|
||||
|
||||
// Only for role server.
|
||||
func (cfg *XtcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *XTCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||
cfg.Sk = pMsg.Sk
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -831,12 +1054,12 @@ func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
func (cfg *XTCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||
pMsg.Sk = cfg.Sk
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
||||
func (cfg *XTCPProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -847,7 +1070,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
|
||||
func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
477
pkg/config/server_common.go
Normal file
477
pkg/config/server_common.go
Normal file
@@ -0,0 +1,477 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// ServerCommonConf contains information for a server service. It is
|
||||
// recommended to use GetDefaultServerConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ServerCommonConf struct {
|
||||
auth.ServerConfig
|
||||
// BindAddr specifies the address that the server binds to. By default,
|
||||
// this value is "0.0.0.0".
|
||||
BindAddr string `json:"bind_addr"`
|
||||
// BindPort specifies the port that the server listens on. By default, this
|
||||
// value is 7000.
|
||||
BindPort int `json:"bind_port"`
|
||||
// BindUDPPort specifies the UDP port that the server listens on. If this
|
||||
// value is 0, the server will not listen for UDP connections. By default,
|
||||
// this value is 0
|
||||
BindUDPPort int `json:"bind_udp_port"`
|
||||
// KCPBindPort specifies the KCP port that the server listens on. If this
|
||||
// value is 0, the server will not listen for KCP connections. By default,
|
||||
// this value is 0.
|
||||
KCPBindPort int `json:"kcp_bind_port"`
|
||||
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||
// may be the same as BindAddr. By default, this value is "0.0.0.0".
|
||||
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
|
||||
// requests. If this value is 0, the server will not listen for HTTP
|
||||
// requests. By default, this value is 0.
|
||||
VhostHTTPPort int `json:"vhost_http_port"`
|
||||
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
||||
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||
// requests. By default, this value is 0.
|
||||
VhostHTTPSPort int `json:"vhost_https_port"`
|
||||
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
// requests on one single port. If it's not - it will listen on this value for
|
||||
// HTTP CONNECT requests. By default, this value is 0.
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
|
||||
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
||||
// HTTP server, in seconds. By default, this value is 60.
|
||||
VhostHTTPTimeout int64 `json:"vhost_http_timeout"`
|
||||
// DashboardAddr specifies the address that the dashboard binds to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
DashboardAddr string `json:"dashboard_addr"`
|
||||
// DashboardPort specifies the port that the dashboard listens on. If this
|
||||
// value is 0, the dashboard will not be started. By default, this value is
|
||||
// 0.
|
||||
DashboardPort int `json:"dashboard_port"`
|
||||
// DashboardUser specifies the username that the dashboard will use for
|
||||
// login. By default, this value is "admin".
|
||||
DashboardUser string `json:"dashboard_user"`
|
||||
// DashboardUser specifies the password that the dashboard will use for
|
||||
// login. By default, this value is "admin".
|
||||
DashboardPwd string `json:"dashboard_pwd"`
|
||||
// EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port}
|
||||
// in /metrics api.
|
||||
EnablePrometheus bool `json:"enable_prometheus"`
|
||||
// AssetsDir specifies the local directory that the dashboard will load
|
||||
// resources from. If this value is "", assets will be loaded from the
|
||||
// bundled executable using statik. By default, this value is "".
|
||||
AssetsDir string `json:"asserts_dir"`
|
||||
// LogFile specifies a file where logs will be written to. This value will
|
||||
// only be used if LogWay is set appropriately. By default, this value is
|
||||
// "console".
|
||||
LogFile string `json:"log_file"`
|
||||
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||
// is "console".
|
||||
LogWay string `json:"log_way"`
|
||||
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||
LogLevel string `json:"log_level"`
|
||||
// LogMaxDays specifies the maximum number of days to store log information
|
||||
// before deletion. This is only used if LogWay == "file". By default, this
|
||||
// value is 0.
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||
// true. By default, this value is false.
|
||||
DisableLogColor bool `json:"disable_log_color"`
|
||||
// DetailedErrorsToClient defines whether to send the specific error (with
|
||||
// debug info) to frpc. By default, this value is true.
|
||||
DetailedErrorsToClient bool `json:"detailed_errors_to_client"`
|
||||
|
||||
// SubDomainHost specifies the domain that will be attached to sub-domains
|
||||
// requested by the client when using Vhost proxying. For example, if this
|
||||
// value is set to "frps.com" and the client requested the subdomain
|
||||
// "test", the resulting URL would be "test.frps.com". By default, this
|
||||
// value is "".
|
||||
SubDomainHost string `json:"subdomain_host"`
|
||||
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
|
||||
// from a client to share a single TCP connection. By default, this value
|
||||
// is true.
|
||||
TCPMux bool `json:"tcp_mux"`
|
||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||
// value is "", a default page will be displayed. By default, this value is
|
||||
// "".
|
||||
Custom404Page string `json:"custom_404_page"`
|
||||
|
||||
// AllowPorts specifies a set of ports that clients are able to proxy to.
|
||||
// If the length of this value is 0, all ports are allowed. By default,
|
||||
// this value is an empty set.
|
||||
AllowPorts map[int]struct{}
|
||||
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||
// this value is 5.
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
// MaxPortsPerClient specifies the maximum number of ports a single client
|
||||
// may proxy to. If this value is 0, no limit will be applied. By default,
|
||||
// this value is 0.
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
// TLSOnly specifies whether to only accept TLS-encrypted connections.
|
||||
// By default, the value is false.
|
||||
TLSOnly bool `json:"tls_only"`
|
||||
// TLSCertFile specifies the path of the cert file that the server will
|
||||
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration. Otherwise, the server will use the tls
|
||||
// configuration generated by itself.
|
||||
TLSCertFile string `json:"tls_cert_file"`
|
||||
// TLSKeyFile specifies the path of the secret key that the server will
|
||||
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
|
||||
// supplied tls configuration. Otherwise, the server will use the tls
|
||||
// configuration generated by itself.
|
||||
TLSKeyFile string `json:"tls_key_file"`
|
||||
// TLSTrustedCaFile specifies the paths of the client cert files that the
|
||||
// server will load. It only works when "tls_only" is true. If
|
||||
// "tls_trusted_ca_file" is valid, the server will verify each client's
|
||||
// certificate.
|
||||
TLSTrustedCaFile string `json:"tls_trusted_ca_file"`
|
||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||
// before terminating the connection. It is not recommended to change this
|
||||
// value. By default, this value is 90.
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||
// HTTPPlugins specify the server plugins support HTTP protocol.
|
||||
HTTPPlugins map[string]plugin.HTTPPluginOptions `json:"http_plugins"`
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `json:"udp_packet_size"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUDPPort: 0,
|
||||
KCPBindPort: 0,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHTTPPort: 0,
|
||||
VhostHTTPSPort: 0,
|
||||
TCPMuxHTTPConnectPort: 0,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
EnablePrometheus: false,
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalServerConfFromIni parses the contents of a server configuration ini
|
||||
// file and returns the resulting server configuration.
|
||||
func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
|
||||
cfg = GetDefaultServerConf()
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return ServerCommonConf{}, err
|
||||
}
|
||||
|
||||
UnmarshalPluginsFromIni(conf, &cfg)
|
||||
|
||||
cfg.ServerConfig = auth.UnmarshalServerConfFromIni(conf)
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
|
||||
cfg.BindAddr = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_port")
|
||||
return
|
||||
}
|
||||
cfg.BindPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
|
||||
return
|
||||
}
|
||||
cfg.BindUDPPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
|
||||
return
|
||||
}
|
||||
cfg.KCPBindPort = int(v)
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
|
||||
cfg.ProxyBindAddr = tmpStr
|
||||
} else {
|
||||
cfg.ProxyBindAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
|
||||
return
|
||||
}
|
||||
cfg.VhostHTTPPort = int(v)
|
||||
} else {
|
||||
cfg.VhostHTTPPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
|
||||
return
|
||||
}
|
||||
cfg.VhostHTTPSPort = int(v)
|
||||
} else {
|
||||
cfg.VhostHTTPSPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcpmux_httpconnect_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid tcpmux_httpconnect_port")
|
||||
return
|
||||
}
|
||||
cfg.TCPMuxHTTPConnectPort = int(v)
|
||||
} else {
|
||||
cfg.TCPMuxHTTPConnectPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil || v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
|
||||
return
|
||||
}
|
||||
cfg.VhostHTTPTimeout = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
|
||||
cfg.DashboardAddr = tmpStr
|
||||
} else {
|
||||
cfg.DashboardAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
|
||||
return
|
||||
}
|
||||
cfg.DashboardPort = int(v)
|
||||
} else {
|
||||
cfg.DashboardPort = 0
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
|
||||
cfg.DashboardUser = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
|
||||
cfg.DashboardPwd = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "enable_prometheus"); ok && tmpStr == "true" {
|
||||
cfg.EnablePrometheus = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||
cfg.AssetsDir = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||
cfg.DisableLogColor = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "detailed_errors_to_client"); ok && tmpStr == "false" {
|
||||
cfg.DetailedErrorsToClient = false
|
||||
} else {
|
||||
cfg.DetailedErrorsToClient = true
|
||||
}
|
||||
|
||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
cfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
}
|
||||
cfg.MaxPoolCount = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
}
|
||||
cfg.MaxPortsPerClient = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
|
||||
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TCPMux = false
|
||||
} else {
|
||||
cfg.TCPMux = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
|
||||
cfg.Custom404Page = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||
return
|
||||
}
|
||||
cfg.HeartBeatTimeout = v
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" {
|
||||
cfg.TLSOnly = true
|
||||
} else {
|
||||
cfg.TLSOnly = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid udp_packet_size")
|
||||
return
|
||||
}
|
||||
cfg.UDPPacketSize = v
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_cert_file"); ok {
|
||||
cfg.TLSCertFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_key_file"); ok {
|
||||
cfg.TLSKeyFile = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok {
|
||||
cfg.TLSTrustedCaFile = tmpStr
|
||||
cfg.TLSOnly = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) {
|
||||
for name, section := range sections {
|
||||
if strings.HasPrefix(name, "plugin.") {
|
||||
name = strings.TrimSpace(strings.TrimPrefix(name, "plugin."))
|
||||
options := plugin.HTTPPluginOptions{
|
||||
Name: name,
|
||||
Addr: section["addr"],
|
||||
Path: section["path"],
|
||||
Ops: strings.Split(section["ops"], ","),
|
||||
}
|
||||
for i := range options.Ops {
|
||||
options.Ops[i] = strings.TrimSpace(options.Ops[i])
|
||||
}
|
||||
cfg.HTTPPlugins[name] = options
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Check() error {
|
||||
return nil
|
||||
}
|
||||
112
pkg/config/types.go
Normal file
112
pkg/config/types.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MB = 1024 * 1024
|
||||
KB = 1024
|
||||
)
|
||||
|
||||
type BandwidthQuantity struct {
|
||||
s string // MB or KB
|
||||
|
||||
i int64 // bytes
|
||||
}
|
||||
|
||||
func NewBandwidthQuantity(s string) (BandwidthQuantity, error) {
|
||||
q := BandwidthQuantity{}
|
||||
err := q.UnmarshalString(s)
|
||||
if err != nil {
|
||||
return q, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
|
||||
if q == nil && u == nil {
|
||||
return true
|
||||
}
|
||||
if q != nil && u != nil {
|
||||
return q.i == u.i
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) String() string {
|
||||
return q.s
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) UnmarshalString(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
base int64
|
||||
f float64
|
||||
err error
|
||||
)
|
||||
if strings.HasSuffix(s, "MB") {
|
||||
base = MB
|
||||
fstr := strings.TrimSuffix(s, "MB")
|
||||
f, err = strconv.ParseFloat(fstr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasSuffix(s, "KB") {
|
||||
base = KB
|
||||
fstr := strings.TrimSuffix(s, "KB")
|
||||
f, err = strconv.ParseFloat(fstr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("unit not support")
|
||||
}
|
||||
|
||||
q.s = s
|
||||
q.i = int64(f * float64(base))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 4 && string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var str string
|
||||
err := json.Unmarshal(b, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.UnmarshalString(str)
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
|
||||
return []byte("\"" + q.s + "\""), nil
|
||||
}
|
||||
|
||||
func (q *BandwidthQuantity) Bytes() int64 {
|
||||
return q.i
|
||||
}
|
||||
40
pkg/config/types_test.go
Normal file
40
pkg/config/types_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Wrap struct {
|
||||
B BandwidthQuantity `json:"b"`
|
||||
Int int `json:"int"`
|
||||
}
|
||||
|
||||
func TestBandwidthQuantity(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var w Wrap
|
||||
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
|
||||
assert.NoError(err)
|
||||
assert.EqualValues(1*KB, w.B.Bytes())
|
||||
|
||||
buf, err := json.Marshal(&w)
|
||||
assert.NoError(err)
|
||||
assert.Equal(`{"b":"1KB","int":5}`, string(buf))
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
@@ -30,8 +30,9 @@ var (
|
||||
|
||||
func init() {
|
||||
visitorConfTypeMap = make(map[string]reflect.Type)
|
||||
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
|
||||
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
|
||||
visitorConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPVisitorConf{})
|
||||
visitorConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPVisitorConf{})
|
||||
visitorConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPVisitorConf{})
|
||||
}
|
||||
|
||||
type VisitorConf interface {
|
||||
@@ -152,12 +153,12 @@ func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
||||
return nil
|
||||
}
|
||||
|
||||
type StcpVisitorConf struct {
|
||||
type SUDPVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
}
|
||||
|
||||
func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*StcpVisitorConf)
|
||||
func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*SUDPVisitorConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -168,26 +169,26 @@ func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *StcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *StcpVisitorConf) Check() (err error) {
|
||||
func (cfg *SUDPVisitorConf) Check() (err error) {
|
||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type XtcpVisitorConf struct {
|
||||
type STCPVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
}
|
||||
|
||||
func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*XtcpVisitorConf)
|
||||
func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*STCPVisitorConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -198,14 +199,44 @@ func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *XtcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XtcpVisitorConf) Check() (err error) {
|
||||
func (cfg *STCPVisitorConf) Check() (err error) {
|
||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type XTCPVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
}
|
||||
|
||||
func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*XTCPVisitorConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XTCPVisitorConf) Check() (err error) {
|
||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -23,10 +23,19 @@ var (
|
||||
Offline string = "offline"
|
||||
|
||||
// proxy type
|
||||
TcpProxy string = "tcp"
|
||||
UdpProxy string = "udp"
|
||||
HttpProxy string = "http"
|
||||
HttpsProxy string = "https"
|
||||
StcpProxy string = "stcp"
|
||||
XtcpProxy string = "xtcp"
|
||||
TCPProxy string = "tcp"
|
||||
UDPProxy string = "udp"
|
||||
TCPMuxProxy string = "tcpmux"
|
||||
HTTPProxy string = "http"
|
||||
HTTPSProxy string = "https"
|
||||
STCPProxy string = "stcp"
|
||||
XTCPProxy string = "xtcp"
|
||||
SUDPProxy string = "sudp"
|
||||
|
||||
// authentication method
|
||||
TokenAuthMethod string = "token"
|
||||
OidcAuthMethod string = "oidc"
|
||||
|
||||
// TCP multiplexer
|
||||
HTTPConnectTCPMultiplexer string = "httpconnect"
|
||||
)
|
||||
93
pkg/metrics/aggregate/server.go
Normal file
93
pkg/metrics/aggregate/server.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2020 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package aggregate
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/pkg/metrics/mem"
|
||||
"github.com/fatedier/frp/pkg/metrics/prometheus"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
)
|
||||
|
||||
// EnableMem start to mark metrics to memory monitor system.
|
||||
func EnableMem() {
|
||||
sm.Add(mem.ServerMetrics)
|
||||
}
|
||||
|
||||
// EnablePrometheus start to mark metrics to prometheus.
|
||||
func EnablePrometheus() {
|
||||
sm.Add(prometheus.ServerMetrics)
|
||||
}
|
||||
|
||||
var sm *serverMetrics = &serverMetrics{}
|
||||
|
||||
func init() {
|
||||
metrics.Register(sm)
|
||||
}
|
||||
|
||||
type serverMetrics struct {
|
||||
ms []metrics.ServerMetrics
|
||||
}
|
||||
|
||||
func (m *serverMetrics) Add(sm metrics.ServerMetrics) {
|
||||
m.ms = append(m.ms, sm)
|
||||
}
|
||||
|
||||
func (m *serverMetrics) NewClient() {
|
||||
for _, v := range m.ms {
|
||||
v.NewClient()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseClient() {
|
||||
for _, v := range m.ms {
|
||||
v.CloseClient()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||
for _, v := range m.ms {
|
||||
v.NewProxy(name, proxyType)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseProxy(name string, proxyType string) {
|
||||
for _, v := range m.ms {
|
||||
v.CloseProxy(name, proxyType)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) OpenConnection(name string, proxyType string) {
|
||||
for _, v := range m.ms {
|
||||
v.OpenConnection(name, proxyType)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseConnection(name string, proxyType string) {
|
||||
for _, v := range m.ms {
|
||||
v.CloseConnection(name, proxyType)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
|
||||
for _, v := range m.ms {
|
||||
v.AddTrafficIn(name, proxyType, trafficBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
|
||||
for _, v := range m.ms {
|
||||
v.AddTrafficOut(name, proxyType, trafficBytes)
|
||||
}
|
||||
}
|
||||
264
pkg/metrics/mem/server.go
Normal file
264
pkg/metrics/mem/server.go
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/metric"
|
||||
server "github.com/fatedier/frp/server/metrics"
|
||||
)
|
||||
|
||||
var sm *serverMetrics = newServerMetrics()
|
||||
var ServerMetrics server.ServerMetrics
|
||||
var StatsCollector Collector
|
||||
|
||||
func init() {
|
||||
ServerMetrics = sm
|
||||
StatsCollector = sm
|
||||
sm.run()
|
||||
}
|
||||
|
||||
type serverMetrics struct {
|
||||
info *ServerStatistics
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newServerMetrics() *serverMetrics {
|
||||
return &serverMetrics{
|
||||
info: &ServerStatistics{
|
||||
TotalTrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TotalTrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
CurConns: metric.NewCounter(),
|
||||
|
||||
ClientCounts: metric.NewCounter(),
|
||||
ProxyTypeCounts: make(map[string]metric.Counter),
|
||||
|
||||
ProxyStatistics: make(map[string]*ProxyStatistics),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) run() {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(12 * time.Hour)
|
||||
log.Debug("start to clear useless proxy statistics data...")
|
||||
m.clearUselessInfo()
|
||||
log.Debug("finish to clear useless proxy statistics data")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) clearUselessInfo() {
|
||||
// To check if there are proxies that closed than 7 days and drop them.
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for name, data := range m.info.ProxyStatistics {
|
||||
if !data.LastCloseTime.IsZero() &&
|
||||
data.LastStartTime.Before(data.LastCloseTime) &&
|
||||
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||
delete(m.info.ProxyStatistics, name)
|
||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) NewClient() {
|
||||
m.info.ClientCounts.Inc(1)
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseClient() {
|
||||
m.info.ClientCounts.Dec(1)
|
||||
}
|
||||
|
||||
func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
counter, ok := m.info.ProxyTypeCounts[proxyType]
|
||||
if !ok {
|
||||
counter = metric.NewCounter()
|
||||
}
|
||||
counter.Inc(1)
|
||||
m.info.ProxyTypeCounts[proxyType] = counter
|
||||
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if !(ok && proxyStats.ProxyType == proxyType) {
|
||||
proxyStats = &ProxyStatistics{
|
||||
Name: name,
|
||||
ProxyType: proxyType,
|
||||
CurConns: metric.NewCounter(),
|
||||
TrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
}
|
||||
m.info.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
proxyStats.LastStartTime = time.Now()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseProxy(name string, proxyType string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if counter, ok := m.info.ProxyTypeCounts[proxyType]; ok {
|
||||
counter.Dec(1)
|
||||
}
|
||||
if proxyStats, ok := m.info.ProxyStatistics[name]; ok {
|
||||
proxyStats.LastCloseTime = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) OpenConnection(name string, proxyType string) {
|
||||
m.info.CurConns.Inc(1)
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.CurConns.Inc(1)
|
||||
m.info.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseConnection(name string, proxyType string) {
|
||||
m.info.CurConns.Dec(1)
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.CurConns.Dec(1)
|
||||
m.info.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
|
||||
m.info.TotalTrafficIn.Inc(trafficBytes)
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.TrafficIn.Inc(trafficBytes)
|
||||
m.info.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
|
||||
m.info.TotalTrafficOut.Inc(trafficBytes)
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.TrafficOut.Inc(trafficBytes)
|
||||
m.info.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
// Get stats data api.
|
||||
|
||||
func (m *serverMetrics) GetServer() *ServerStats {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
s := &ServerStats{
|
||||
TotalTrafficIn: m.info.TotalTrafficIn.TodayCount(),
|
||||
TotalTrafficOut: m.info.TotalTrafficOut.TodayCount(),
|
||||
CurConns: int64(m.info.CurConns.Count()),
|
||||
ClientCounts: int64(m.info.ClientCounts.Count()),
|
||||
ProxyTypeCounts: make(map[string]int64),
|
||||
}
|
||||
for k, v := range m.info.ProxyTypeCounts {
|
||||
s.ProxyTypeCounts[k] = int64(v.Count())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (m *serverMetrics) GetProxiesByType(proxyType string) []*ProxyStats {
|
||||
res := make([]*ProxyStats, 0)
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range m.info.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
ps := &ProxyStats{
|
||||
Name: name,
|
||||
Type: proxyStats.ProxyType,
|
||||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||
CurConns: int64(proxyStats.CurConns.Count()),
|
||||
}
|
||||
if !proxyStats.LastStartTime.IsZero() {
|
||||
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
|
||||
}
|
||||
if !proxyStats.LastCloseTime.IsZero() {
|
||||
ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
|
||||
}
|
||||
res = append(res, ps)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *serverMetrics) GetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range m.info.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
if name != proxyName {
|
||||
continue
|
||||
}
|
||||
|
||||
res = &ProxyStats{
|
||||
Name: name,
|
||||
Type: proxyStats.ProxyType,
|
||||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||
CurConns: int64(proxyStats.CurConns.Count()),
|
||||
}
|
||||
if !proxyStats.LastStartTime.IsZero() {
|
||||
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
|
||||
}
|
||||
if !proxyStats.LastCloseTime.IsZero() {
|
||||
res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *serverMetrics) GetProxyTraffic(name string) (res *ProxyTrafficInfo) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if ok {
|
||||
res = &ProxyTrafficInfo{
|
||||
Name: name,
|
||||
}
|
||||
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
|
||||
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -12,31 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stats
|
||||
package mem
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/metric"
|
||||
"github.com/fatedier/frp/pkg/util/metric"
|
||||
)
|
||||
|
||||
const (
|
||||
ReserveDays = 7
|
||||
)
|
||||
|
||||
type StatsType int
|
||||
|
||||
const (
|
||||
TypeNewClient StatsType = iota
|
||||
TypeCloseClient
|
||||
TypeNewProxy
|
||||
TypeCloseProxy
|
||||
TypeOpenConnection
|
||||
TypeCloseConnection
|
||||
TypeAddTrafficIn
|
||||
TypeAddTrafficOut
|
||||
)
|
||||
|
||||
type ServerStats struct {
|
||||
TotalTrafficIn int64
|
||||
TotalTrafficOut int64
|
||||
@@ -88,42 +75,8 @@ type ServerStatistics struct {
|
||||
}
|
||||
|
||||
type Collector interface {
|
||||
Mark(statsType StatsType, payload interface{})
|
||||
Run() error
|
||||
GetServer() *ServerStats
|
||||
GetProxiesByType(proxyType string) []*ProxyStats
|
||||
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
||||
GetProxyTraffic(name string) *ProxyTrafficInfo
|
||||
}
|
||||
|
||||
type NewClientPayload struct{}
|
||||
|
||||
type CloseClientPayload struct{}
|
||||
|
||||
type NewProxyPayload struct {
|
||||
Name string
|
||||
ProxyType string
|
||||
}
|
||||
|
||||
type CloseProxyPayload struct {
|
||||
Name string
|
||||
ProxyType string
|
||||
}
|
||||
|
||||
type OpenConnectionPayload struct {
|
||||
ProxyName string
|
||||
}
|
||||
|
||||
type CloseConnectionPayload struct {
|
||||
ProxyName string
|
||||
}
|
||||
|
||||
type AddTrafficInPayload struct {
|
||||
ProxyName string
|
||||
TrafficBytes int64
|
||||
}
|
||||
|
||||
type AddTrafficOutPayload struct {
|
||||
ProxyName string
|
||||
TrafficBytes int64
|
||||
}
|
||||
8
pkg/metrics/metrics.go
Normal file
8
pkg/metrics/metrics.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/pkg/metrics/aggregate"
|
||||
)
|
||||
|
||||
var EnableMem = aggregate.EnableMem
|
||||
var EnablePrometheus = aggregate.EnablePrometheus
|
||||
95
pkg/metrics/prometheus/server.go
Normal file
95
pkg/metrics/prometheus/server.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "frp"
|
||||
serverSubsystem = "server"
|
||||
)
|
||||
|
||||
var ServerMetrics metrics.ServerMetrics = newServerMetrics()
|
||||
|
||||
type serverMetrics struct {
|
||||
clientCount prometheus.Gauge
|
||||
proxyCount *prometheus.GaugeVec
|
||||
connectionCount *prometheus.GaugeVec
|
||||
trafficIn *prometheus.CounterVec
|
||||
trafficOut *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func (m *serverMetrics) NewClient() {
|
||||
m.clientCount.Inc()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseClient() {
|
||||
m.clientCount.Dec()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||
m.proxyCount.WithLabelValues(proxyType).Inc()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseProxy(name string, proxyType string) {
|
||||
m.proxyCount.WithLabelValues(proxyType).Dec()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) OpenConnection(name string, proxyType string) {
|
||||
m.connectionCount.WithLabelValues(name, proxyType).Inc()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) CloseConnection(name string, proxyType string) {
|
||||
m.connectionCount.WithLabelValues(name, proxyType).Dec()
|
||||
}
|
||||
|
||||
func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
|
||||
m.trafficIn.WithLabelValues(name, proxyType).Add(float64(trafficBytes))
|
||||
}
|
||||
|
||||
func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
|
||||
m.trafficOut.WithLabelValues(name, proxyType).Add(float64(trafficBytes))
|
||||
}
|
||||
|
||||
func newServerMetrics() *serverMetrics {
|
||||
m := &serverMetrics{
|
||||
clientCount: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: serverSubsystem,
|
||||
Name: "client_counts",
|
||||
Help: "The current client counts of frps",
|
||||
}),
|
||||
proxyCount: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: serverSubsystem,
|
||||
Name: "proxy_counts",
|
||||
Help: "The current proxy counts",
|
||||
}, []string{"type"}),
|
||||
connectionCount: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: serverSubsystem,
|
||||
Name: "connection_counts",
|
||||
Help: "The current connection counts",
|
||||
}, []string{"name", "type"}),
|
||||
trafficIn: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: serverSubsystem,
|
||||
Name: "traffic_in",
|
||||
Help: "The total in traffic",
|
||||
}, []string{"name", "type"}),
|
||||
trafficOut: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: serverSubsystem,
|
||||
Name: "traffic_out",
|
||||
Help: "The total out traffic",
|
||||
}, []string{"name", "type"}),
|
||||
}
|
||||
prometheus.MustRegister(m.clientCount)
|
||||
prometheus.MustRegister(m.proxyCount)
|
||||
prometheus.MustRegister(m.connectionCount)
|
||||
prometheus.MustRegister(m.trafficIn)
|
||||
prometheus.MustRegister(m.trafficOut)
|
||||
return m
|
||||
}
|
||||
@@ -17,57 +17,60 @@ package msg
|
||||
import "net"
|
||||
|
||||
const (
|
||||
TypeLogin = 'o'
|
||||
TypeLoginResp = '1'
|
||||
TypeNewProxy = 'p'
|
||||
TypeNewProxyResp = '2'
|
||||
TypeCloseProxy = 'c'
|
||||
TypeNewWorkConn = 'w'
|
||||
TypeReqWorkConn = 'r'
|
||||
TypeStartWorkConn = 's'
|
||||
TypeNewVisitorConn = 'v'
|
||||
TypeNewVisitorConnResp = '3'
|
||||
TypePing = 'h'
|
||||
TypePong = '4'
|
||||
TypeUdpPacket = 'u'
|
||||
TypeNatHoleVisitor = 'i'
|
||||
TypeNatHoleClient = 'n'
|
||||
TypeNatHoleResp = 'm'
|
||||
TypeNatHoleSid = '5'
|
||||
TypeLogin = 'o'
|
||||
TypeLoginResp = '1'
|
||||
TypeNewProxy = 'p'
|
||||
TypeNewProxyResp = '2'
|
||||
TypeCloseProxy = 'c'
|
||||
TypeNewWorkConn = 'w'
|
||||
TypeReqWorkConn = 'r'
|
||||
TypeStartWorkConn = 's'
|
||||
TypeNewVisitorConn = 'v'
|
||||
TypeNewVisitorConnResp = '3'
|
||||
TypePing = 'h'
|
||||
TypePong = '4'
|
||||
TypeUDPPacket = 'u'
|
||||
TypeNatHoleVisitor = 'i'
|
||||
TypeNatHoleClient = 'n'
|
||||
TypeNatHoleResp = 'm'
|
||||
TypeNatHoleClientDetectOK = 'd'
|
||||
TypeNatHoleSid = '5'
|
||||
)
|
||||
|
||||
var (
|
||||
msgTypeMap = map[byte]interface{}{
|
||||
TypeLogin: Login{},
|
||||
TypeLoginResp: LoginResp{},
|
||||
TypeNewProxy: NewProxy{},
|
||||
TypeNewProxyResp: NewProxyResp{},
|
||||
TypeCloseProxy: CloseProxy{},
|
||||
TypeNewWorkConn: NewWorkConn{},
|
||||
TypeReqWorkConn: ReqWorkConn{},
|
||||
TypeStartWorkConn: StartWorkConn{},
|
||||
TypeNewVisitorConn: NewVisitorConn{},
|
||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||
TypePing: Ping{},
|
||||
TypePong: Pong{},
|
||||
TypeUdpPacket: UdpPacket{},
|
||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||
TypeNatHoleClient: NatHoleClient{},
|
||||
TypeNatHoleResp: NatHoleResp{},
|
||||
TypeNatHoleSid: NatHoleSid{},
|
||||
TypeLogin: Login{},
|
||||
TypeLoginResp: LoginResp{},
|
||||
TypeNewProxy: NewProxy{},
|
||||
TypeNewProxyResp: NewProxyResp{},
|
||||
TypeCloseProxy: CloseProxy{},
|
||||
TypeNewWorkConn: NewWorkConn{},
|
||||
TypeReqWorkConn: ReqWorkConn{},
|
||||
TypeStartWorkConn: StartWorkConn{},
|
||||
TypeNewVisitorConn: NewVisitorConn{},
|
||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||
TypePing: Ping{},
|
||||
TypePong: Pong{},
|
||||
TypeUDPPacket: UDPPacket{},
|
||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||
TypeNatHoleClient: NatHoleClient{},
|
||||
TypeNatHoleResp: NatHoleResp{},
|
||||
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
||||
TypeNatHoleSid: NatHoleSid{},
|
||||
}
|
||||
)
|
||||
|
||||
// When frpc start, client send this message to login to server.
|
||||
type Login struct {
|
||||
Version string `json:"version"`
|
||||
Hostname string `json:"hostname"`
|
||||
Os string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
User string `json:"user"`
|
||||
PrivilegeKey string `json:"privilege_key"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
RunId string `json:"run_id"`
|
||||
Version string `json:"version"`
|
||||
Hostname string `json:"hostname"`
|
||||
Os string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
User string `json:"user"`
|
||||
PrivilegeKey string `json:"privilege_key"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
RunID string `json:"run_id"`
|
||||
Metas map[string]string `json:"metas"`
|
||||
|
||||
// Some global configures.
|
||||
PoolCount int `json:"pool_count"`
|
||||
@@ -75,19 +78,20 @@ type Login struct {
|
||||
|
||||
type LoginResp struct {
|
||||
Version string `json:"version"`
|
||||
RunId string `json:"run_id"`
|
||||
ServerUdpPort int `json:"server_udp_port"`
|
||||
RunID string `json:"run_id"`
|
||||
ServerUDPPort int `json:"server_udp_port"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// When frpc login success, send this message to frps for running a new proxy.
|
||||
type NewProxy struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
Metas map[string]string `json:"metas"`
|
||||
|
||||
// tcp and udp only
|
||||
RemotePort int `json:"remote_port"`
|
||||
@@ -96,13 +100,16 @@ type NewProxy struct {
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
SubDomain string `json:"subdomain"`
|
||||
Locations []string `json:"locations"`
|
||||
HttpUser string `json:"http_user"`
|
||||
HttpPwd string `json:"http_pwd"`
|
||||
HTTPUser string `json:"http_user"`
|
||||
HTTPPwd string `json:"http_pwd"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
|
||||
// stcp
|
||||
Sk string `json:"sk"`
|
||||
|
||||
// tcpmux
|
||||
Multiplexer string `json:"multiplexer"`
|
||||
}
|
||||
|
||||
type NewProxyResp struct {
|
||||
@@ -116,7 +123,9 @@ type CloseProxy struct {
|
||||
}
|
||||
|
||||
type NewWorkConn struct {
|
||||
RunId string `json:"run_id"`
|
||||
RunID string `json:"run_id"`
|
||||
PrivilegeKey string `json:"privilege_key"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type ReqWorkConn struct {
|
||||
@@ -124,6 +133,11 @@ type ReqWorkConn struct {
|
||||
|
||||
type StartWorkConn struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
SrcAddr string `json:"src_addr"`
|
||||
DstAddr string `json:"dst_addr"`
|
||||
SrcPort uint16 `json:"src_port"`
|
||||
DstPort uint16 `json:"dst_port"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type NewVisitorConn struct {
|
||||
@@ -140,12 +154,15 @@ type NewVisitorConnResp struct {
|
||||
}
|
||||
|
||||
type Ping struct {
|
||||
PrivilegeKey string `json:"privilege_key"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type Pong struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type UdpPacket struct {
|
||||
type UDPPacket struct {
|
||||
Content string `json:"c"`
|
||||
LocalAddr *net.UDPAddr `json:"l"`
|
||||
RemoteAddr *net.UDPAddr `json:"r"`
|
||||
@@ -169,6 +186,9 @@ type NatHoleResp struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type NatHoleClientDetectOK struct {
|
||||
}
|
||||
|
||||
type NatHoleSid struct {
|
||||
Sid string `json:"sid"`
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
"github.com/fatedier/golib/pool"
|
||||
@@ -18,16 +18,21 @@ import (
|
||||
// Timeout seconds.
|
||||
var NatHoleTimeout int64 = 10
|
||||
|
||||
type NatHoleController struct {
|
||||
type SidRequest struct {
|
||||
Sid string
|
||||
NotifyCh chan struct{}
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
listener *net.UDPConn
|
||||
|
||||
clientCfgs map[string]*NatHoleClientCfg
|
||||
sessions map[string]*NatHoleSession
|
||||
clientCfgs map[string]*ClientCfg
|
||||
sessions map[string]*Session
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
|
||||
func NewController(udpBindAddr string) (nc *Controller, err error) {
|
||||
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -36,19 +41,19 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nc = &NatHoleController{
|
||||
nc = &Controller{
|
||||
listener: lconn,
|
||||
clientCfgs: make(map[string]*NatHoleClientCfg),
|
||||
sessions: make(map[string]*NatHoleSession),
|
||||
clientCfgs: make(map[string]*ClientCfg),
|
||||
sessions: make(map[string]*Session),
|
||||
}
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
||||
clientCfg := &NatHoleClientCfg{
|
||||
func (nc *Controller) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
||||
clientCfg := &ClientCfg{
|
||||
Name: name,
|
||||
Sk: sk,
|
||||
SidCh: make(chan string),
|
||||
SidCh: make(chan *SidRequest),
|
||||
}
|
||||
nc.mu.Lock()
|
||||
nc.clientCfgs[name] = clientCfg
|
||||
@@ -56,13 +61,13 @@ func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan st
|
||||
return clientCfg.SidCh
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) CloseClient(name string) {
|
||||
func (nc *Controller) CloseClient(name string) {
|
||||
nc.mu.Lock()
|
||||
defer nc.mu.Unlock()
|
||||
delete(nc.clientCfgs, name)
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) Run() {
|
||||
func (nc *Controller) Run() {
|
||||
for {
|
||||
buf := pool.GetBuf(1024)
|
||||
n, raddr, err := nc.listener.ReadFromUDP(buf)
|
||||
@@ -91,15 +96,15 @@ func (nc *NatHoleController) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) GenSid() string {
|
||||
func (nc *Controller) GenSid() string {
|
||||
t := time.Now().Unix()
|
||||
id, _ := util.RandId()
|
||||
id, _ := util.RandID()
|
||||
return fmt.Sprintf("%d%s", t, id)
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
||||
func (nc *Controller) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
||||
sid := nc.GenSid()
|
||||
session := &NatHoleSession{
|
||||
session := &Session{
|
||||
Sid: sid,
|
||||
VisitorAddr: raddr,
|
||||
NotifyCh: make(chan struct{}, 0),
|
||||
@@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
||||
}()
|
||||
|
||||
err := errors.PanicToError(func() {
|
||||
clientCfg.SidCh <- sid
|
||||
clientCfg.SidCh <- &SidRequest{
|
||||
Sid: sid,
|
||||
NotifyCh: session.NotifyCh,
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
@@ -149,7 +157,7 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
||||
}
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
||||
func (nc *Controller) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
||||
nc.mu.RLock()
|
||||
session, ok := nc.sessions[m.Sid]
|
||||
nc.mu.RUnlock()
|
||||
@@ -158,14 +166,13 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
|
||||
}
|
||||
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||
session.ClientAddr = raddr
|
||||
session.NotifyCh <- struct{}{}
|
||||
|
||||
resp := nc.GenNatHoleResponse(session, "")
|
||||
log.Trace("send nat hole response to client")
|
||||
nc.listener.WriteToUDP(resp, raddr)
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte {
|
||||
func (nc *Controller) GenNatHoleResponse(session *Session, errInfo string) []byte {
|
||||
var (
|
||||
sid string
|
||||
visitorAddr string
|
||||
@@ -190,7 +197,7 @@ func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
type NatHoleSession struct {
|
||||
type Session struct {
|
||||
Sid string
|
||||
VisitorAddr *net.UDPAddr
|
||||
ClientAddr *net.UDPAddr
|
||||
@@ -198,8 +205,8 @@ type NatHoleSession struct {
|
||||
NotifyCh chan struct{}
|
||||
}
|
||||
|
||||
type NatHoleClientCfg struct {
|
||||
type ClientCfg struct {
|
||||
Name string
|
||||
Sk string
|
||||
SidCh chan string
|
||||
SidCh chan *SidRequest
|
||||
}
|
||||
111
pkg/plugin/client/http2https.go
Normal file
111
pkg/plugin/client/http2https.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginHTTP2HTTPS = "http2https"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
|
||||
}
|
||||
|
||||
type HTTP2HTTPSPlugin struct {
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
localAddr := params["plugin_local_addr"]
|
||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||
headers := make(map[string]string)
|
||||
for k, v := range params {
|
||||
if !strings.HasPrefix(k, "plugin_header_") {
|
||||
continue
|
||||
}
|
||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if localAddr == "" {
|
||||
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||
}
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTPS2HTTPPlugin{
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: rp,
|
||||
}
|
||||
|
||||
go p.s.Serve(listener)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPSPlugin) Name() string {
|
||||
return PluginHTTP2HTTPS
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPSPlugin) Close() error {
|
||||
if err := p.s.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -22,31 +22,31 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
gnet "github.com/fatedier/golib/net"
|
||||
)
|
||||
|
||||
const PluginHttpProxy = "http_proxy"
|
||||
const PluginHTTPProxy = "http_proxy"
|
||||
|
||||
func init() {
|
||||
Register(PluginHttpProxy, NewHttpProxyPlugin)
|
||||
Register(PluginHTTPProxy, NewHTTPProxyPlugin)
|
||||
}
|
||||
|
||||
type HttpProxy struct {
|
||||
type HTTPProxy struct {
|
||||
l *Listener
|
||||
s *http.Server
|
||||
AuthUser string
|
||||
AuthPasswd string
|
||||
}
|
||||
|
||||
func NewHttpProxyPlugin(params map[string]string) (Plugin, error) {
|
||||
func NewHTTPProxyPlugin(params map[string]string) (Plugin, error) {
|
||||
user := params["plugin_http_user"]
|
||||
passwd := params["plugin_http_passwd"]
|
||||
listener := NewProxyListener()
|
||||
|
||||
hp := &HttpProxy{
|
||||
hp := &HTTPProxy{
|
||||
l: listener,
|
||||
AuthUser: user,
|
||||
AuthPasswd: passwd,
|
||||
@@ -60,11 +60,11 @@ func NewHttpProxyPlugin(params map[string]string) (Plugin, error) {
|
||||
return hp, nil
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Name() string {
|
||||
return PluginHttpProxy
|
||||
func (hp *HTTPProxy) Name() string {
|
||||
return PluginHTTPProxy
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
|
||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||
@@ -90,13 +90,13 @@ func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Close() error {
|
||||
func (hp *HTTPProxy) Close() error {
|
||||
hp.s.Close()
|
||||
hp.l.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
func (hp *HTTPProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if ok := hp.Auth(req); !ok {
|
||||
rw.Header().Set("Proxy-Authenticate", "Basic")
|
||||
rw.WriteHeader(http.StatusProxyAuthRequired)
|
||||
@@ -108,11 +108,11 @@ func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Connect request is handled in Handle function.
|
||||
hp.ConnectHandler(rw, req)
|
||||
} else {
|
||||
hp.HttpHandler(rw, req)
|
||||
hp.HTTPHandler(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
func (hp *HTTPProxy) HTTPHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
removeProxyHeaders(req)
|
||||
|
||||
resp, err := http.DefaultTransport.RoundTrip(req)
|
||||
@@ -134,7 +134,7 @@ func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
// deprecated
|
||||
// Hijack needs to SetReadDeadline on the Conn of the request, but if we use stream compression here,
|
||||
// we may always get i/o timeout error.
|
||||
func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
func (hp *HTTPProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
hj, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -158,7 +158,7 @@ func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
go frpIo.Join(remote, client)
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Auth(req *http.Request) bool {
|
||||
func (hp *HTTPProxy) Auth(req *http.Request) bool {
|
||||
if hp.AuthUser == "" && hp.AuthPasswd == "" {
|
||||
return true
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func (hp *HttpProxy) Auth(req *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
|
||||
func (hp *HTTPProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
|
||||
defer rwc.Close()
|
||||
if ok := hp.Auth(req); !ok {
|
||||
res := getBadResponse()
|
||||
@@ -231,6 +231,7 @@ func removeProxyHeaders(req *http.Request) {
|
||||
func getBadResponse() *http.Response {
|
||||
header := make(map[string][]string)
|
||||
header["Proxy-Authenticate"] = []string{"Basic"}
|
||||
header["Connection"] = []string{"close"}
|
||||
res := &http.Response{
|
||||
Status: "407 Not authorized",
|
||||
StatusCode: 407,
|
||||
133
pkg/plugin/client/https2http.go
Normal file
133
pkg/plugin/client/https2http.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginHTTPS2HTTP = "https2http"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||
}
|
||||
|
||||
type HTTPS2HTTPPlugin struct {
|
||||
crtPath string
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
crtPath := params["plugin_crt_path"]
|
||||
keyPath := params["plugin_key_path"]
|
||||
localAddr := params["plugin_local_addr"]
|
||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||
headers := make(map[string]string)
|
||||
for k, v := range params {
|
||||
if !strings.HasPrefix(k, "plugin_header_") {
|
||||
continue
|
||||
}
|
||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if crtPath == "" {
|
||||
return nil, fmt.Errorf("plugin_crt_path is required")
|
||||
}
|
||||
if keyPath == "" {
|
||||
return nil, fmt.Errorf("plugin_key_path is required")
|
||||
}
|
||||
if localAddr == "" {
|
||||
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||
}
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTPS2HTTPPlugin{
|
||||
crtPath: crtPath,
|
||||
keyPath: keyPath,
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: rp,
|
||||
}
|
||||
|
||||
tlsConfig, err := p.genTLSConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||
}
|
||||
ln := tls.NewListener(listener, tlsConfig)
|
||||
|
||||
go p.s.Serve(ln)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||
return PluginHTTPS2HTTP
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||
if err := p.s.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
@@ -46,7 +44,9 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
||||
|
||||
// extraBufToLocal will send to local connection first, then join conn with local connection
|
||||
Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte)
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
|
||||
gosocks5 "github.com/armon/go-socks5"
|
||||
)
|
||||
@@ -53,7 +54,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
defer conn.Close()
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.Server.ServeConn(wrapConn)
|
||||
@@ -16,9 +16,10 @@ package plugin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -63,8 +64,8 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.Use(frpNet.NewHttpAuthMiddleware(httpUser, httpPasswd).Middleware)
|
||||
router.PathPrefix(prefix).Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
|
||||
router.Use(frpNet.NewHTTPAuthMiddleware(httpUser, httpPasswd).Middleware)
|
||||
router.PathPrefix(prefix).Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
|
||||
sp.s = &http.Server{
|
||||
Handler: router,
|
||||
}
|
||||
@@ -72,7 +73,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.l.PutConn(wrapConn)
|
||||
}
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
)
|
||||
|
||||
@@ -53,11 +51,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(extraBufToLocal) > 0 {
|
||||
localConn.Write(extraBufToLocal)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, conn)
|
||||
}
|
||||
109
pkg/plugin/server/http.go
Normal file
109
pkg/plugin/server/http.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type HTTPPluginOptions struct {
|
||||
Name string
|
||||
Addr string
|
||||
Path string
|
||||
Ops []string
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
options HTTPPluginOptions
|
||||
|
||||
url string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin {
|
||||
return &httpPlugin{
|
||||
options: options,
|
||||
url: fmt.Sprintf("http://%s%s", options.Addr, options.Path),
|
||||
client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *httpPlugin) Name() string {
|
||||
return p.options.Name
|
||||
}
|
||||
|
||||
func (p *httpPlugin) IsSupport(op string) bool {
|
||||
for _, v := range p.options.Ops {
|
||||
if v == op {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *httpPlugin) Handle(ctx context.Context, op string, content interface{}) (*Response, interface{}, error) {
|
||||
r := &Request{
|
||||
Version: APIVersion,
|
||||
Op: op,
|
||||
Content: content,
|
||||
}
|
||||
var res Response
|
||||
res.Content = reflect.New(reflect.TypeOf(content)).Interface()
|
||||
if err := p.do(ctx, r, &res); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &res, res.Content, nil
|
||||
}
|
||||
|
||||
func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error {
|
||||
buf, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("version", r.Version)
|
||||
v.Set("op", r.Op)
|
||||
req, err := http.NewRequest("POST", p.url+"?"+v.Encode(), bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.Header.Set("X-Frp-Reqid", GetReqidFromContext(ctx))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("do http request error code: %d", resp.StatusCode)
|
||||
}
|
||||
buf, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = json.Unmarshal(buf, res); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
230
pkg/plugin/server/manager.go
Normal file
230
pkg/plugin/server/manager.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
loginPlugins []Plugin
|
||||
newProxyPlugins []Plugin
|
||||
pingPlugins []Plugin
|
||||
newWorkConnPlugins []Plugin
|
||||
newUserConnPlugins []Plugin
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
loginPlugins: make([]Plugin, 0),
|
||||
newProxyPlugins: make([]Plugin, 0),
|
||||
pingPlugins: make([]Plugin, 0),
|
||||
newWorkConnPlugins: make([]Plugin, 0),
|
||||
newUserConnPlugins: make([]Plugin, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Register(p Plugin) {
|
||||
if p.IsSupport(OpLogin) {
|
||||
m.loginPlugins = append(m.loginPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpNewProxy) {
|
||||
m.newProxyPlugins = append(m.newProxyPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpPing) {
|
||||
m.pingPlugins = append(m.pingPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpNewWorkConn) {
|
||||
m.newWorkConnPlugins = append(m.newWorkConnPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpNewUserConn) {
|
||||
m.newUserConnPlugins = append(m.newUserConnPlugins, p)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
|
||||
if len(m.loginPlugins) == 0 {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
var (
|
||||
res = &Response{
|
||||
Reject: false,
|
||||
Unchange: true,
|
||||
}
|
||||
retContent interface{}
|
||||
err error
|
||||
)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.loginPlugins {
|
||||
res, retContent, err = p.Handle(ctx, OpLogin, *content)
|
||||
if err != nil {
|
||||
xl.Warn("send Login request to plugin [%s] error: %v", p.Name(), err)
|
||||
return nil, errors.New("send Login request to plugin error")
|
||||
}
|
||||
if res.Reject {
|
||||
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||
}
|
||||
if !res.Unchange {
|
||||
content = retContent.(*LoginContent)
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
|
||||
if len(m.newProxyPlugins) == 0 {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
var (
|
||||
res = &Response{
|
||||
Reject: false,
|
||||
Unchange: true,
|
||||
}
|
||||
retContent interface{}
|
||||
err error
|
||||
)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.newProxyPlugins {
|
||||
res, retContent, err = p.Handle(ctx, OpNewProxy, *content)
|
||||
if err != nil {
|
||||
xl.Warn("send NewProxy request to plugin [%s] error: %v", p.Name(), err)
|
||||
return nil, errors.New("send NewProxy request to plugin error")
|
||||
}
|
||||
if res.Reject {
|
||||
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||
}
|
||||
if !res.Unchange {
|
||||
content = retContent.(*NewProxyContent)
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
|
||||
if len(m.pingPlugins) == 0 {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
var (
|
||||
res = &Response{
|
||||
Reject: false,
|
||||
Unchange: true,
|
||||
}
|
||||
retContent interface{}
|
||||
err error
|
||||
)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.pingPlugins {
|
||||
res, retContent, err = p.Handle(ctx, OpPing, *content)
|
||||
if err != nil {
|
||||
xl.Warn("send Ping request to plugin [%s] error: %v", p.Name(), err)
|
||||
return nil, errors.New("send Ping request to plugin error")
|
||||
}
|
||||
if res.Reject {
|
||||
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||
}
|
||||
if !res.Unchange {
|
||||
content = retContent.(*PingContent)
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent, error) {
|
||||
if len(m.newWorkConnPlugins) == 0 {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
var (
|
||||
res = &Response{
|
||||
Reject: false,
|
||||
Unchange: true,
|
||||
}
|
||||
retContent interface{}
|
||||
err error
|
||||
)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.pingPlugins {
|
||||
res, retContent, err = p.Handle(ctx, OpPing, *content)
|
||||
if err != nil {
|
||||
xl.Warn("send NewWorkConn request to plugin [%s] error: %v", p.Name(), err)
|
||||
return nil, errors.New("send NewWorkConn request to plugin error")
|
||||
}
|
||||
if res.Reject {
|
||||
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||
}
|
||||
if !res.Unchange {
|
||||
content = retContent.(*NewWorkConnContent)
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserConnContent, error) {
|
||||
if len(m.newUserConnPlugins) == 0 {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
var (
|
||||
res = &Response{
|
||||
Reject: false,
|
||||
Unchange: true,
|
||||
}
|
||||
retContent interface{}
|
||||
err error
|
||||
)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.newUserConnPlugins {
|
||||
res, retContent, err = p.Handle(ctx, OpNewUserConn, *content)
|
||||
if err != nil {
|
||||
xl.Info("send NewUserConn request to plugin [%s] error: %v", p.Name(), err)
|
||||
return nil, errors.New("send NewUserConn request to plugin error")
|
||||
}
|
||||
if res.Reject {
|
||||
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||
}
|
||||
if !res.Unchange {
|
||||
content = retContent.(*NewUserConnContent)
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
35
pkg/plugin/server/plugin.go
Normal file
35
pkg/plugin/server/plugin.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const (
|
||||
APIVersion = "0.1.0"
|
||||
|
||||
OpLogin = "Login"
|
||||
OpNewProxy = "NewProxy"
|
||||
OpPing = "Ping"
|
||||
OpNewWorkConn = "NewWorkConn"
|
||||
OpNewUserConn = "NewUserConn"
|
||||
)
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
IsSupport(op string) bool
|
||||
Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,19 +12,23 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package errors
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
)
|
||||
|
||||
func PanicToError(fn func()) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("Panic error: %v", r)
|
||||
}
|
||||
}()
|
||||
type key int
|
||||
|
||||
fn()
|
||||
return
|
||||
const (
|
||||
reqidKey key = 0
|
||||
)
|
||||
|
||||
func NewReqidContext(ctx context.Context, reqid string) context.Context {
|
||||
return context.WithValue(ctx, reqidKey, reqid)
|
||||
}
|
||||
|
||||
func GetReqidFromContext(ctx context.Context) string {
|
||||
ret, _ := ctx.Value(reqidKey).(string)
|
||||
return ret
|
||||
}
|
||||
64
pkg/plugin/server/types.go
Normal file
64
pkg/plugin/server/types.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Version string `json:"version"`
|
||||
Op string `json:"op"`
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Reject bool `json:"reject"`
|
||||
RejectReason string `json:"reject_reason"`
|
||||
Unchange bool `json:"unchange"`
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
type LoginContent struct {
|
||||
msg.Login
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
User string `json:"user"`
|
||||
Metas map[string]string `json:"metas"`
|
||||
RunID string `json:"run_id"`
|
||||
}
|
||||
|
||||
type NewProxyContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.NewProxy
|
||||
}
|
||||
|
||||
type PingContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.Ping
|
||||
}
|
||||
|
||||
type NewWorkConnContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.NewWorkConn
|
||||
}
|
||||
|
||||
type NewUserConnContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
@@ -20,26 +20,26 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
|
||||
return &msg.UdpPacket{
|
||||
func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket {
|
||||
return &msg.UDPPacket{
|
||||
Content: base64.StdEncoding.EncodeToString(buf),
|
||||
LocalAddr: laddr,
|
||||
RemoteAddr: raddr,
|
||||
}
|
||||
}
|
||||
|
||||
func GetContent(m *msg.UdpPacket) (buf []byte, err error) {
|
||||
func GetContent(m *msg.UDPPacket) (buf []byte, err error) {
|
||||
buf, err = base64.StdEncoding.DecodeString(m.Content)
|
||||
return
|
||||
}
|
||||
|
||||
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh chan<- *msg.UdpPacket) {
|
||||
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UDPPacket, sendCh chan<- *msg.UDPPacket, bufSize int) {
|
||||
// read
|
||||
go func() {
|
||||
for udpMsg := range readCh {
|
||||
@@ -52,16 +52,16 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
||||
}()
|
||||
|
||||
// write
|
||||
buf := pool.GetBuf(1500)
|
||||
buf := pool.GetBuf(bufSize)
|
||||
defer pool.PutBuf(buf)
|
||||
for {
|
||||
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
return
|
||||
}
|
||||
// buf[:n] will be encoded to string, so the bytes can be reused
|
||||
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
|
||||
udpMsg := NewUDPPacket(buf[:n], nil, remoteAddr)
|
||||
|
||||
select {
|
||||
case sendCh <- udpMsg:
|
||||
default:
|
||||
@@ -69,7 +69,7 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
||||
}
|
||||
}
|
||||
|
||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<- msg.Message, bufSize int) {
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
)
|
||||
@@ -85,7 +85,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
|
||||
udpConn.Close()
|
||||
}()
|
||||
|
||||
buf := pool.GetBuf(1500)
|
||||
buf := pool.GetBuf(bufSize)
|
||||
for {
|
||||
udpConn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||
n, _, err := udpConn.ReadFromUDP(buf)
|
||||
@@ -93,7 +93,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
|
||||
return
|
||||
}
|
||||
|
||||
udpMsg := NewUdpPacket(buf[:n], nil, raddr)
|
||||
udpMsg := NewUDPPacket(buf[:n], nil, raddr)
|
||||
if err = errors.PanicToError(func() {
|
||||
select {
|
||||
case sendCh <- udpMsg:
|
||||
@@ -117,6 +117,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
|
||||
if !ok {
|
||||
udpConn, err = net.DialUDP("udp", nil, dstAddr)
|
||||
if err != nil {
|
||||
mu.Unlock()
|
||||
continue
|
||||
}
|
||||
udpConnMap[udpMsg.RemoteAddr.String()] = udpConn
|
||||
@@ -10,7 +10,7 @@ func TestUdpPacket(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buf := []byte("hello world")
|
||||
udpMsg := NewUdpPacket(buf, nil, nil)
|
||||
udpMsg := NewUDPPacket(buf, nil, nil)
|
||||
|
||||
newBuf, err := GetContent(udpMsg)
|
||||
assert.NoError(err)
|
||||
117
pkg/transport/tls.go
Normal file
117
pkg/transport/tls.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
|
||||
tlsCert, err := tls.LoadX509KeyPair(certfile, keyfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tlsCert, nil
|
||||
}
|
||||
|
||||
func newRandomTLSKeyPair() *tls.Certificate {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
&template,
|
||||
&template,
|
||||
&key.PublicKey,
|
||||
key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tlsCert
|
||||
}
|
||||
|
||||
// Only supprt one ca file to add
|
||||
func newCertPool(caPath string) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
|
||||
caCrt, err := ioutil.ReadFile(caPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool.AppendCertsFromPEM(caCrt)
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
|
||||
var base = &tls.Config{}
|
||||
|
||||
if certPath == "" || keyPath == "" {
|
||||
// server will generate tls conf by itself
|
||||
cert := newRandomTLSKeyPair()
|
||||
base.Certificates = []tls.Certificate{*cert}
|
||||
} else {
|
||||
cert, err := newCustomTLSKeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.Certificates = []tls.Certificate{*cert}
|
||||
}
|
||||
|
||||
if caPath != "" {
|
||||
pool, err := newCertPool(caPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
base.ClientCAs = pool
|
||||
}
|
||||
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func NewClientTLSConfig(certPath, keyPath, caPath, servearName string) (*tls.Config, error) {
|
||||
var base = &tls.Config{}
|
||||
|
||||
if certPath == "" || keyPath == "" {
|
||||
// client will not generate tls conf by itself
|
||||
} else {
|
||||
cert, err := newCustomTLSKeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.Certificates = []tls.Certificate{*cert}
|
||||
}
|
||||
|
||||
if caPath != "" {
|
||||
pool, err := newCertPool(caPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.RootCAs = pool
|
||||
base.ServerName = servearName
|
||||
base.InsecureSkipVerify = false
|
||||
} else {
|
||||
base.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
return base, nil
|
||||
}
|
||||
51
pkg/util/limit/reader.go
Normal file
51
pkg/util/limit/reader.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package limit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
limiter *rate.Limiter
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader, limiter *rate.Limiter) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
limiter: limiter,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
b := r.limiter.Burst()
|
||||
if b < len(p) {
|
||||
p = p[:b]
|
||||
}
|
||||
n, err = r.r.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = r.limiter.WaitN(context.Background(), n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
60
pkg/util/limit/writer.go
Normal file
60
pkg/util/limit/writer.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package limit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
limiter *rate.Limiter
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
limiter: limiter,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
var nn int
|
||||
b := w.limiter.Burst()
|
||||
for {
|
||||
end := len(p)
|
||||
if end == 0 {
|
||||
break
|
||||
}
|
||||
if b < len(p) {
|
||||
end = b
|
||||
}
|
||||
err = w.limiter.WaitN(context.Background(), end)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nn, err = w.w.Write(p[:end])
|
||||
n += nn
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p = p[end:]
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -29,16 +29,20 @@ func init() {
|
||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||
}
|
||||
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
||||
SetLogFile(logWay, logFile, maxdays)
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
|
||||
SetLogFile(logWay, logFile, maxdays, disableLogColor)
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
// SetLogFile to configure log params
|
||||
// logWay: file or console
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
|
||||
if logWay == "console" {
|
||||
Log.SetLogger("console", "")
|
||||
params := ""
|
||||
if disableLogColor {
|
||||
params = fmt.Sprintf(`{"color": false}`)
|
||||
}
|
||||
Log.SetLogger("console", params)
|
||||
} else {
|
||||
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
||||
Log.SetLogger("file", params)
|
||||
@@ -87,71 +91,3 @@ func Debug(format string, v ...interface{}) {
|
||||
func Trace(format string, v ...interface{}) {
|
||||
Log.Trace(format, v...)
|
||||
}
|
||||
|
||||
// Logger is the log interface
|
||||
type Logger interface {
|
||||
AddLogPrefix(string)
|
||||
GetPrefixStr() string
|
||||
GetAllPrefix() []string
|
||||
ClearLogPrefix()
|
||||
Error(string, ...interface{})
|
||||
Warn(string, ...interface{})
|
||||
Info(string, ...interface{})
|
||||
Debug(string, ...interface{})
|
||||
Trace(string, ...interface{})
|
||||
}
|
||||
|
||||
type PrefixLogger struct {
|
||||
prefix string
|
||||
allPrefix []string
|
||||
}
|
||||
|
||||
func NewPrefixLogger(prefix string) *PrefixLogger {
|
||||
logger := &PrefixLogger{
|
||||
allPrefix: make([]string, 0),
|
||||
}
|
||||
logger.AddLogPrefix(prefix)
|
||||
return logger
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) AddLogPrefix(prefix string) {
|
||||
if len(prefix) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
pl.prefix += "[" + prefix + "] "
|
||||
pl.allPrefix = append(pl.allPrefix, prefix)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) GetPrefixStr() string {
|
||||
return pl.prefix
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) GetAllPrefix() []string {
|
||||
return pl.allPrefix
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) ClearLogPrefix() {
|
||||
pl.prefix = ""
|
||||
pl.allPrefix = make([]string, 0)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) Error(format string, v ...interface{}) {
|
||||
Log.Error(pl.prefix+format, v...)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) Warn(format string, v ...interface{}) {
|
||||
Log.Warn(pl.prefix+format, v...)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) Info(format string, v ...interface{}) {
|
||||
Log.Info(pl.prefix+format, v...)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) Debug(format string, v ...interface{}) {
|
||||
Log.Debug(pl.prefix+format, v...)
|
||||
}
|
||||
|
||||
func (pl *PrefixLogger) Trace(format string, v ...interface{}) {
|
||||
Log.Trace(pl.prefix+format, v...)
|
||||
}
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
)
|
||||
|
||||
type Counter interface {
|
||||
Count() int64
|
||||
Inc(int64)
|
||||
Dec(int64)
|
||||
Count() int32
|
||||
Inc(int32)
|
||||
Dec(int32)
|
||||
Snapshot() Counter
|
||||
Clear()
|
||||
}
|
||||
@@ -33,28 +33,28 @@ func NewCounter() Counter {
|
||||
}
|
||||
|
||||
type StandardCounter struct {
|
||||
count int64
|
||||
count int32
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Count() int64 {
|
||||
return atomic.LoadInt64(&c.count)
|
||||
func (c *StandardCounter) Count() int32 {
|
||||
return atomic.LoadInt32(&c.count)
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Inc(count int64) {
|
||||
atomic.AddInt64(&c.count, count)
|
||||
func (c *StandardCounter) Inc(count int32) {
|
||||
atomic.AddInt32(&c.count, count)
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Dec(count int64) {
|
||||
atomic.AddInt64(&c.count, -count)
|
||||
func (c *StandardCounter) Dec(count int32) {
|
||||
atomic.AddInt32(&c.count, -count)
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Snapshot() Counter {
|
||||
tmp := &StandardCounter{
|
||||
count: atomic.LoadInt64(&c.count),
|
||||
count: atomic.LoadInt32(&c.count),
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (c *StandardCounter) Clear() {
|
||||
atomic.StoreInt64(&c.count, 0)
|
||||
atomic.StoreInt32(&c.count, 0)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user