mirror of
https://github.com/fatedier/frp.git
synced 2025-07-28 16:37:39 +00:00
Compare commits
172 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
cbf9c731a0 | ||
|
de4bfcc43c | ||
|
9737978f28 | ||
|
5bc7fe2cea | ||
|
65d8fe37c5 | ||
|
1723d7b651 | ||
|
2481dfab64 | ||
|
95a881a7d3 | ||
|
fe403ab328 | ||
|
66555dbb00 | ||
|
7f9ea48405 | ||
|
96d7e2da6f | ||
|
d879b8208b | ||
|
3585e456d4 | ||
|
1de8c3fc87 | ||
|
bbab3fe9ca | ||
|
48990da22e | ||
|
5543fc2a9a | ||
|
c41de6fd28 | ||
|
8c8fd9790e | ||
|
5a7ef3be74 | ||
|
d9b5e0bde0 | ||
|
05ca72dbf0 | ||
|
ef6f8bbf6c | ||
|
70ac7d3d11 | ||
|
385c4d3dd5 | ||
|
5e1983f7ed | ||
|
516cdbddb0 | ||
|
3954ceb93b | ||
|
2061ef11c8 | ||
|
71cbe5decc | ||
|
a2ccb6c190 | ||
|
5bdf530b7e | ||
|
5177570da4 | ||
|
0bd8f9cd9b | ||
|
649a2f2457 | ||
|
54916793f9 | ||
|
0b06c1c821 | ||
|
bbc6f1687d | ||
|
b250342e27 | ||
|
f76deb8898 | ||
|
611d063e1f | ||
|
0c7d778896 | ||
|
7c21906884 | ||
|
a4106ec4b7 | ||
|
655c52f9ce | ||
|
25cfda5768 | ||
|
b61cb14c8f | ||
|
d5ce4d4916 | ||
|
4f0ee5980d | ||
|
146956ac6e | ||
|
35278ad17f | ||
|
aea9f9fbcc | ||
|
08c17c3247 | ||
|
6934a18f95 | ||
|
5165b0821f | ||
|
0aec869513 | ||
|
826b9db5f2 | ||
|
89d1a1fb2b | ||
|
450e0b7148 | ||
|
a1ac002694 | ||
|
951d33d47c | ||
|
b33ea9274c | ||
|
3b3f3dc2b5 | ||
|
ec0b59732c | ||
|
bae1ecdc69 | ||
|
5c2ab5a749 | ||
|
1a8ac148ca | ||
|
698219b621 | ||
|
229740524e | ||
|
cbeeac06a5 | ||
|
66a69f873f | ||
|
fb13774457 | ||
|
f14ed87b29 | ||
|
07623027bc | ||
|
941ac25648 | ||
|
f645082d72 | ||
|
7793f55545 | ||
|
ca88b07ecf | ||
|
6e305db4be | ||
|
9bb08396c7 | ||
|
64136a3b3e | ||
|
b8037475ed | ||
|
082447f517 | ||
|
cc6486addb | ||
|
57417c83ae | ||
|
d74b45be5d | ||
|
0d02f291e3 | ||
|
42ee536dae | ||
|
c33b5152e7 | ||
|
b6c219aa97 | ||
|
bbc36be052 | ||
|
f5778349d5 | ||
|
71603c6d0b | ||
|
e64fcce417 | ||
|
629f2856b1 | ||
|
aeb9f2b64d | ||
|
85dd41c17b | ||
|
102408d37f | ||
|
495b577819 | ||
|
f56b49ad3b | ||
|
cb1bf71bef | ||
|
b9f062bef2 | ||
|
490019fb51 | ||
|
2e497274ba | ||
|
cf4136fe99 | ||
|
b1e9cff622 | ||
|
db2d1fce76 | ||
|
8579de9d3f | ||
|
0c35273759 | ||
|
6eb8146334 | ||
|
da78e3f52e | ||
|
e1918f6396 | ||
|
ad1e32fd2d | ||
|
3726f99b04 | ||
|
c8a7405992 | ||
|
040d198e36 | ||
|
1a6cbbb2d2 | ||
|
ea79e03bd0 | ||
|
3e349455a0 | ||
|
c7a457a045 | ||
|
0b0d5c982e | ||
|
c4f873c07a | ||
|
01b1df2b91 |
@@ -1,5 +0,0 @@
|
||||
Dockerfile
|
||||
.git
|
||||
*~
|
||||
*#
|
||||
.#*
|
@@ -2,7 +2,8 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
|
||||
install:
|
||||
- make
|
||||
|
17
Dockerfile
17
Dockerfile
@@ -1,17 +0,0 @@
|
||||
FROM golang:1.10
|
||||
|
||||
COPY . /go/src/github.com/fatedier/frp
|
||||
|
||||
RUN cd /go/src/github.com/fatedier/frp \
|
||||
&& make \
|
||||
&& mv bin/frpc /frpc \
|
||||
&& mv bin/frps /frps \
|
||||
&& mv conf/frpc.ini /frpc.ini \
|
||||
&& mv conf/frps.ini /frps.ini \
|
||||
&& make clean
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
ENTRYPOINT ["/frps"]
|
@@ -1,12 +0,0 @@
|
||||
FROM alpine:3.5
|
||||
|
||||
COPY tmp/frpc /frpc
|
||||
COPY tmp/frps /frps
|
||||
COPY conf/frpc_min.ini /frpc.ini
|
||||
COPY conf/frps_min.ini /frps.ini
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
ENTRYPOINT ["/frps"]
|
@@ -1,21 +0,0 @@
|
||||
FROM golang:1.8 as frpBuild
|
||||
|
||||
COPY . /go/src/github.com/fatedier/frp
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
RUN cd /go/src/github.com/fatedier/frp \
|
||||
&& make
|
||||
|
||||
FROM alpine:3.6
|
||||
|
||||
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frpc /
|
||||
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frpc.ini /
|
||||
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frps /
|
||||
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frps.ini /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
WORKDIR /
|
||||
|
||||
CMD ["/frps","-c","frps.ini"]
|
173
Gopkg.lock
generated
173
Gopkg.lock
generated
@@ -1,173 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/armon/go-socks5"
|
||||
packages = ["."]
|
||||
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fatedier/beego"
|
||||
packages = ["logs"]
|
||||
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/fatedier/golib"
|
||||
packages = [
|
||||
"control/shutdown",
|
||||
"crypto",
|
||||
"errors",
|
||||
"io",
|
||||
"msg/json",
|
||||
"net",
|
||||
"net/mux",
|
||||
"pool"
|
||||
]
|
||||
revision = "354693cdd7fd9fa4f207c2f91ec2534615d3e5e5"
|
||||
|
||||
[[projects]]
|
||||
branch = "frp"
|
||||
name = "github.com/fatedier/kcp-go"
|
||||
packages = ["."]
|
||||
revision = "cd167d2f15f451b0f33780ce862fca97adc0331e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/hashicorp/yamux"
|
||||
packages = ["."]
|
||||
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/julienschmidt/httprouter"
|
||||
packages = ["."]
|
||||
revision = "8c199fb6259ffc1af525cc3ad52ee60ba8359669"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/rakyll/statik"
|
||||
packages = ["fs"]
|
||||
revision = "fd36b3595eb2ec8da4b8153b107f7ea08504899d"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/rodaine/table"
|
||||
packages = ["."]
|
||||
revision = "212a2ad1c462ed4d5b5511ea2b480a573281dbbd"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/templexxx/cpufeat"
|
||||
packages = ["."]
|
||||
revision = "3794dfbfb04749f896b521032f69383f24c3687e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/templexxx/reedsolomon"
|
||||
packages = ["."]
|
||||
revision = "5e06b81a1c7628d9c8d4fb7c3c4e401e37db39b4"
|
||||
version = "0.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/templexxx/xor"
|
||||
packages = ["."]
|
||||
revision = "0af8e873c554da75f37f2049cdffda804533d44c"
|
||||
version = "0.1.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tjfoc/gmsm"
|
||||
packages = ["sm4"]
|
||||
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/vaughan0/go-ini"
|
||||
packages = ["."]
|
||||
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"blowfish",
|
||||
"cast5",
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"salsa20/salsa",
|
||||
"tea",
|
||||
"twofish",
|
||||
"xtea"
|
||||
]
|
||||
revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"bpf",
|
||||
"context",
|
||||
"internal/iana",
|
||||
"internal/socket",
|
||||
"internal/socks",
|
||||
"ipv4",
|
||||
"proxy"
|
||||
]
|
||||
revision = "d11bb6cd8e3c4e60239c9cb20ef68586d74500d0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "d934d16928772cfb22c55a39964c7ca364d02fe1ab680a90cdb5c3c8be256273"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
70
Gopkg.toml
70
Gopkg.toml
@@ -1,70 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/armon/go-socks5"
|
||||
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatedier/beego"
|
||||
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatedier/golib"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
branch = "frp"
|
||||
name = "github.com/fatedier/kcp-go"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/hashicorp/yamux"
|
||||
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rakyll/statik"
|
||||
version = "0.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rodaine/table"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/vaughan0/go-ini"
|
||||
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
32
Makefile
32
Makefile
@@ -6,11 +6,12 @@ build: frps frpc
|
||||
|
||||
# compile assets into binary file
|
||||
file:
|
||||
rm -rf ./assets/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/static
|
||||
go get -d github.com/rakyll/statik
|
||||
go install github.com/rakyll/statik
|
||||
rm -rf ./assets/statik
|
||||
rm -rf ./assets/frps/static/*
|
||||
rm -rf ./assets/frpc/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||
rm -rf ./assets/frps/statik
|
||||
rm -rf ./assets/frpc/statik
|
||||
go generate ./assets/...
|
||||
|
||||
fmt:
|
||||
@@ -18,7 +19,6 @@ fmt:
|
||||
|
||||
frps:
|
||||
go build -o bin/frps ./cmd/frps
|
||||
@cp -rf ./assets/static ./bin
|
||||
|
||||
frpc:
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
@@ -26,24 +26,18 @@ frpc:
|
||||
test: gotest
|
||||
|
||||
gotest:
|
||||
go test -v ./assets/...
|
||||
go test -v ./client/...
|
||||
go test -v ./cmd/...
|
||||
go test -v ./models/...
|
||||
go test -v ./server/...
|
||||
go test -v ./utils/...
|
||||
go test -v --cover ./assets/...
|
||||
go test -v --cover ./client/...
|
||||
go test -v --cover ./cmd/...
|
||||
go test -v --cover ./models/...
|
||||
go test -v --cover ./server/...
|
||||
go test -v --cover ./utils/...
|
||||
|
||||
ci:
|
||||
cd ./tests && ./run_test.sh && cd -
|
||||
go test -v ./tests/...
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
|
||||
cic:
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
go test -count=1 -p=1 -v ./tests/...
|
||||
|
||||
alltest: gotest ci
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
rm -f ./bin/frps
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
|
@@ -18,6 +18,8 @@ app:
|
||||
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
|
||||
|
229
README.md
229
README.md
@@ -6,13 +6,14 @@
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, udp, http and https protocol when requests can be forwarded by domains to backward web services.
|
||||
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.
|
||||
|
||||
Now it also try to support p2p connect.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [What can I do with frp?](#what-can-i-do-with-frp)
|
||||
* [Status](#status)
|
||||
* [Architecture](#architecture)
|
||||
* [Example Usage](#example-usage)
|
||||
@@ -21,13 +22,17 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
||||
* [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)
|
||||
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||
* [Expose your service in security](#expose-your-service-in-security)
|
||||
* [P2P Mode](#p2p-mode)
|
||||
* [Features](#features)
|
||||
* [Configuration File](#configuration-file)
|
||||
* [Configuration file template](#configuration-file-template)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Admin UI](#admin-ui)
|
||||
* [Authentication](#authentication)
|
||||
* [Encryption and Compression](#encryption-and-compression)
|
||||
* [TLS](#tls)
|
||||
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||
* [Port White List](#port-white-list)
|
||||
@@ -35,8 +40,13 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
||||
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||
* [Support KCP Protocol](#support-kcp-protocol)
|
||||
* [Connection Pool](#connection-pool)
|
||||
* [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)
|
||||
* [Get Real IP](#get-real-ip)
|
||||
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||
* [Proxy Protocol](#proxy-protocol)
|
||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||
* [Custom subdomain names](#custom-subdomain-names)
|
||||
* [URL routing](#url-routing)
|
||||
@@ -52,11 +62,6 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## What can I do with frp?
|
||||
|
||||
* Expose any http and https service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
|
||||
* Expose any tcp or udp service behind a NAT or firewall to the internet by a server with public IP address.
|
||||
|
||||
## 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.
|
||||
@@ -241,11 +246,34 @@ Configure frps same as above.
|
||||
|
||||
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`.
|
||||
|
||||
### Enable HTTPS for local HTTP service
|
||||
|
||||
1. Start frpc with configurations:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_htts2http]
|
||||
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
|
||||
```
|
||||
|
||||
2. Visit `https://test.yourdomain.com`.
|
||||
|
||||
### Expose your service in security
|
||||
|
||||
For some services, if expose them to the public network directly will be a security risk.
|
||||
|
||||
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
|
||||
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
|
||||
|
||||
Configure frps same as above.
|
||||
|
||||
@@ -343,6 +371,34 @@ You can find features which this document not metioned from full example configu
|
||||
|
||||
[frpc full configuration file](./conf/frpc_full.ini)
|
||||
|
||||
### Configuration file template
|
||||
|
||||
Configuration file tempalte can be rendered using os environments. Template uses Go's standard format.
|
||||
|
||||
```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 }}
|
||||
```
|
||||
|
||||
Start frpc program:
|
||||
|
||||
```
|
||||
export FRP_SERVER_ADDR="x.x.x.x"
|
||||
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`.
|
||||
|
||||
### Dashboard
|
||||
|
||||
Check frp's status and proxies's statistics information by Dashboard.
|
||||
@@ -361,13 +417,25 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
|
||||
|
||||

|
||||
|
||||
### Admin UI
|
||||
|
||||
Admin UI help you check and manage frpc's configure.
|
||||
|
||||
Configure a 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, default username and password are both `admin`.
|
||||
|
||||
### Authentication
|
||||
|
||||
Since v0.10.0, you only need to set `token` in frps.ini and frpc.ini.
|
||||
|
||||
Note that time duration between server of frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
|
||||
|
||||
Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout.
|
||||
`token` in frps.ini and frpc.ini should be same.
|
||||
|
||||
### Encryption and Compression
|
||||
|
||||
@@ -383,6 +451,14 @@ use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
#### TLS
|
||||
|
||||
frp support TLS protocol between frpc and frps since v0.25.0.
|
||||
|
||||
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
|
||||
|
||||
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
|
||||
|
||||
### Hot-Reload frpc configuration
|
||||
|
||||
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||
@@ -434,8 +510,6 @@ 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:
|
||||
@@ -483,9 +557,81 @@ This feature is fit for a large number of short connections.
|
||||
pool_count = 1
|
||||
```
|
||||
|
||||
### Load balancing
|
||||
|
||||
Load balancing is supported by `group`.
|
||||
This feature is available only for type `tcp` now.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
`group_key` is used for authentication.
|
||||
|
||||
Proxies in same group will accept connections from port 80 randomly.
|
||||
|
||||
### Health Check
|
||||
|
||||
Health check feature can help you achieve high availability with load balancing.
|
||||
|
||||
Add `health_check_type = {type}` 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:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[test1]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
# enable tcp health check
|
||||
health_check_type = tcp
|
||||
# dial timeout seconds
|
||||
health_check_timeout_s = 3
|
||||
# if continuous failed in 3 times, the proxy will be removed from frps
|
||||
health_check_max_failed = 3
|
||||
# every 10 seconds will do a health check
|
||||
health_check_interval_s = 10
|
||||
```
|
||||
|
||||
Type http configuration:
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.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
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 10
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
```
|
||||
|
||||
### Rewriting the 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.
|
||||
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.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -496,15 +642,53 @@ custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
|
||||
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
|
||||
|
||||
### Set Headers In HTTP Request
|
||||
|
||||
You can set headers for proxy which type is `http`.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Get Real IP
|
||||
|
||||
#### HTTP X-Forwarded-For
|
||||
|
||||
Features for http proxy only.
|
||||
|
||||
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
|
||||
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
|
||||
|
||||
**Note that now you can only get these two headers in first request of each user connection.**
|
||||
#### Proxy Protocol
|
||||
|
||||
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
|
||||
|
||||
Here is an example for https service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = https
|
||||
local_port = 443
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
# now v1 and v2 is supported
|
||||
proxy_protocol_version = v2
|
||||
```
|
||||
|
||||
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
|
||||
|
||||
Then you can get it from HTTP request header in your local service.
|
||||
|
||||
### Password protecting your web service
|
||||
|
||||
@@ -521,7 +705,7 @@ type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
http_user = abc
|
||||
http_passwd = abc
|
||||
http_pwd = abc
|
||||
```
|
||||
|
||||
Visit `http://test.yourdomain.com` and now you need to input username and password.
|
||||
@@ -598,7 +782,7 @@ local_port = 6000-6006,6007
|
||||
remote_port = 6000-6006,6007
|
||||
```
|
||||
|
||||
frpc will generate 6 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_5`.
|
||||
frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
|
||||
|
||||
### Plugin
|
||||
|
||||
@@ -625,9 +809,6 @@ plugin_http_passwd = abc
|
||||
## Development Plan
|
||||
|
||||
* Log http request information in frps.
|
||||
* Direct reverse proxy, like haproxy.
|
||||
* Load balance to different service in frpc.
|
||||
* kubernetes ingress support.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
246
README_zh.md
246
README_zh.md
@@ -4,13 +4,12 @@
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。
|
||||
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。
|
||||
|
||||
## 目录
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [frp 的作用](#frp-的作用)
|
||||
* [开发状态](#开发状态)
|
||||
* [架构](#架构)
|
||||
* [使用示例](#使用示例)
|
||||
@@ -19,13 +18,17 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
|
||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||
* [点对点内网穿透](#点对点内网穿透)
|
||||
* [功能说明](#功能说明)
|
||||
* [配置文件](#配置文件)
|
||||
* [配置文件模版渲染](#配置文件模版渲染)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Admin UI](#admin-ui)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [TLS](#tls)
|
||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||
* [客户端查看代理状态](#客户端查看代理状态)
|
||||
* [端口白名单](#端口白名单)
|
||||
@@ -33,8 +36,13 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [TCP 多路复用](#tcp-多路复用)
|
||||
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
|
||||
* [连接池](#连接池)
|
||||
* [负载均衡](#负载均衡)
|
||||
* [健康检查](#健康检查)
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
||||
* [获取用户真实 IP](#获取用户真实-ip)
|
||||
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||
* [Proxy Protocol](#proxy-protocol)
|
||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||
* [自定义二级域名](#自定义二级域名)
|
||||
* [URL 路由](#url-路由)
|
||||
@@ -44,21 +52,16 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [开发计划](#开发计划)
|
||||
* [为 frp 做贡献](#为-frp-做贡献)
|
||||
* [捐助](#捐助)
|
||||
* [知识星球](#知识星球)
|
||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||
* [微信支付捐赠](#微信支付捐赠)
|
||||
* [Paypal 捐赠](#paypal-捐赠)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## frp 的作用
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
|
||||
* 对于 http, https 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 仍然处于前期开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
frp 仍然处于开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
|
||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
@@ -244,6 +247,33 @@ frps 的部署步骤同上。
|
||||
|
||||
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||
|
||||
### 为本地 HTTP 服务启用 HTTPS
|
||||
|
||||
通过 `https2http` 插件可以让本地 HTTP 服务转换成 HTTPS 服务对外提供。
|
||||
|
||||
1. 启用 frpc,启用 `https2http` 插件,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_htts2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
|
||||
# HTTPS 证书相关的配置
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
```
|
||||
|
||||
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
||||
|
||||
### 安全地暴露内网服务
|
||||
|
||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||
@@ -358,10 +388,41 @@ frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量
|
||||
|
||||
[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
|
||||
@@ -376,13 +437,27 @@ dashboard_pwd = admin
|
||||
|
||||

|
||||
|
||||
### Admin UI
|
||||
|
||||
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
|
||||
|
||||
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
```
|
||||
|
||||
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI,用户名密码默认为 `admin`。
|
||||
|
||||
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
|
||||
|
||||
### 身份验证
|
||||
|
||||
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||
|
||||
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
|
||||
|
||||
这个超时时间可以在配置文件中通过 `authentication_timeout` 这个参数来修改,单位为秒,默认值为 900,即 15 分钟。如果修改为 0,则 frps 将不对身份验证报文的时间戳进行超时校验。
|
||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||
|
||||
### 加密与压缩
|
||||
|
||||
@@ -402,6 +477,14 @@ use_compression = true
|
||||
|
||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||
|
||||
#### TLS
|
||||
|
||||
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。
|
||||
|
||||
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
|
||||
|
||||
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
|
||||
|
||||
### 客户端热加载配置文件
|
||||
|
||||
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
||||
@@ -461,7 +544,7 @@ tcp_mux = false
|
||||
|
||||
### 底层通信可选 kcp 协议
|
||||
|
||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
|
||||
开启 kcp 协议支持:
|
||||
|
||||
@@ -510,6 +593,79 @@ tcp_mux = false
|
||||
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 类型的代理。
|
||||
@@ -525,11 +681,52 @@ 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。
|
||||
#### HTTP X-Forwarded-For
|
||||
|
||||
**需要注意的是,目前只在每一个用户连接的第一个 HTTP 请求中添加了这两个 header。**
|
||||
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 来获取用户真实 IP,默认启用。
|
||||
|
||||
#### Proxy Protocol
|
||||
|
||||
frp 支持通过 **Proxy Protocol** 协议来传递经过 frp 代理的请求的真实 IP,此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
|
||||
|
||||
**Proxy Protocol** 功能启用后,frpc 在和本地服务建立连接后,会先发送一段 **Proxy Protocol** 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
|
||||
|
||||
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 **Proxy Protocol** 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
|
||||
|
||||
这里以 https 类型为例:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = https
|
||||
local_port = 443
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
|
||||
proxy_protocol_version = v2
|
||||
```
|
||||
|
||||
只需要在代理配置中增加一行 `proxy_protocol_version = v2` 即可开启此功能。
|
||||
|
||||
本地的 https 服务可以通过在 nginx 的配置中启用 **Proxy Protocol** 的解析并将结果设置在 `X-Real-IP` 这个 Header 中就可以在自己的 Web 服务中通过 `X-Real-IP` 获取到用户的真实 IP。
|
||||
|
||||
### 通过密码保护你的 web 服务
|
||||
|
||||
@@ -575,9 +772,9 @@ local_port = 80
|
||||
subdomain = test
|
||||
```
|
||||
|
||||
frps 和 fprc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
|
||||
frps 和 frpc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
|
||||
|
||||
需要注意的是如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。
|
||||
**注:如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。**
|
||||
|
||||
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
|
||||
|
||||
@@ -637,7 +834,7 @@ local_port = 6000-6006,6007
|
||||
remote_port = 6000-6006,6007
|
||||
```
|
||||
|
||||
实际连接成功后会创建 6 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_5`。
|
||||
实际连接成功后会创建 8 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
|
||||
|
||||
### 插件
|
||||
|
||||
@@ -666,9 +863,6 @@ plugin_http_passwd = abc
|
||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||
|
||||
* frps 记录 http 请求日志。
|
||||
* frps 支持直接反向代理,类似 haproxy。
|
||||
* frpc 支持负载均衡到后端不同服务。
|
||||
* 集成对 k8s 等平台的支持。
|
||||
|
||||
## 为 frp 做贡献
|
||||
|
||||
@@ -689,6 +883,12 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
|
||||
frp 交流群:606194980 (QQ 群号)
|
||||
|
||||
### 知识星球
|
||||
|
||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
|
||||

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

|
||||
|
@@ -14,8 +14,10 @@
|
||||
|
||||
package assets
|
||||
|
||||
//go:generate statik -src=./static
|
||||
//go:generate go fmt statik/statik.go
|
||||
//go:generate statik -src=./frps/static -dest=./frps
|
||||
//go:generate statik -src=./frpc/static -dest=./frpc
|
||||
//go:generate go fmt ./frps/statik/statik.go
|
||||
//go:generate go fmt ./frpc/statik/statik.go
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -24,8 +26,6 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/statik"
|
||||
)
|
||||
|
||||
var (
|
||||
|
BIN
assets/frpc/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frpc/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1
assets/frpc/static/index.html
Normal file
1
assets/frpc/static/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>
|
1
assets/frpc/static/manifest.js
Normal file
1
assets/frpc/static/manifest.js
Normal file
@@ -0,0 +1 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"edb271e1d9c81f857840"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
1
assets/frpc/static/vendor.js
Normal file
1
assets/frpc/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frpc/statik/statik.go
Normal file
10
assets/frpc/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frps/static/favicon.ico
Normal file
BIN
assets/frps/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
1
assets/frps/static/index.html
Normal file
1
assets/frps/static/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?14bea8276eef86cc7c61"></script><script type="text/javascript" src="vendor.js?51925ec1a77936b64d61"></script></body> </html>
|
1
assets/frps/static/manifest.js
Normal file
1
assets/frps/static/manifest.js
Normal file
@@ -0,0 +1 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"51925ec1a77936b64d61"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
1
assets/frps/static/vendor.js
Normal file
1
assets/frps/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frps/statik/statik.go
Normal file
10
assets/frps/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?33af4addd27a494b40c1"></script><script type="text/javascript" src="vendor.js?c2d294f9c0a40fd7073a"></script><script type="text/javascript" src="index.js?a0da222d4e0f906bc057"></script></body> </html>
|
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,a){for(var u,i,f,l=0,s=[];l<t.length;l++)i=t[l],o[i]&&s.push(o[i][0]),o[i]=0;for(u in c)Object.prototype.hasOwnProperty.call(c,u)&&(e[u]=c[u]);for(r&&r(t,c,a);s.length;)s.shift()();if(a)for(l=0;l<a.length;l++)f=n(n.s=a[l]);return f};var t={},o={2:0};n.e=function(e){function r(){u.onerror=u.onload=null,clearTimeout(i);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var a=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,n.nc&&u.setAttribute("nonce",n.nc),u.src=n.p+""+e+".js?"+{0:"a0da222d4e0f906bc057",1:"c2d294f9c0a40fd7073a"}[e];var i=setTimeout(r,12e4);return u.onerror=u.onload=r,a.appendChild(u),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,10 +20,11 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -33,13 +34,23 @@ var (
|
||||
|
||||
func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
// url router
|
||||
router := httprouter.New()
|
||||
router := mux.NewRouter()
|
||||
|
||||
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
|
||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// api, see dashboard_api.go
|
||||
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))
|
||||
router.GET("/api/status", frpNet.HttprouterBasicAuth(svr.apiStatus, user, passwd))
|
||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
|
||||
// view
|
||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:%d", addr, port)
|
||||
server := &http.Server{
|
||||
|
@@ -22,75 +22,60 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
// api/reload
|
||||
type ReloadResp struct {
|
||||
GeneralResponse
|
||||
}
|
||||
// GET api/reload
|
||||
|
||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res ReloadResp
|
||||
)
|
||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http request [/api/reload]")
|
||||
defer func() {
|
||||
log.Info("Http response [/api/reload]: code [%d]", res.Code)
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
log.Info("Http response [/api/reload], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/reload]")
|
||||
|
||||
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||
if err != nil {
|
||||
res.Code = 1
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc config file error: %v", err)
|
||||
log.Warn("reload frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
||||
if err != nil {
|
||||
res.Code = 2
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc common section error: %v", err)
|
||||
log.Warn("reload frpc common section error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
conf, err := ini.LoadFile(g.GlbClientCfg.CfgFile)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||
if err != nil {
|
||||
res.Code = 1
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc config file error: %v", err)
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start)
|
||||
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
if err != nil {
|
||||
res.Code = 3
|
||||
res.Code = 500
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc proxy config error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
|
||||
if err != nil {
|
||||
res.Code = 4
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc proxy config error: %v", err)
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
log.Info("success reload conf")
|
||||
@@ -122,7 +107,7 @@ 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 *ProxyStatus) ProxyStatusResp {
|
||||
func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
Type: status.Type,
|
||||
@@ -175,8 +160,8 @@ func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp {
|
||||
return psr
|
||||
}
|
||||
|
||||
// api/status
|
||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
// GET api/status
|
||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res StatusResp
|
||||
@@ -187,14 +172,14 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request, _ httprout
|
||||
res.Https = make([]ProxyStatusResp, 0)
|
||||
res.Stcp = make([]ProxyStatusResp, 0)
|
||||
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||
|
||||
log.Info("Http request [/api/status]")
|
||||
defer func() {
|
||||
log.Info("Http response [/api/status]")
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/status]")
|
||||
|
||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
switch status.Type {
|
||||
@@ -220,3 +205,122 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request, _ httprout
|
||||
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||
return
|
||||
}
|
||||
|
||||
// GET api/config
|
||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http get request [/api/config]")
|
||||
defer func() {
|
||||
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.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)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("load frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
rows := strings.Split(content, "\n")
|
||||
newRows := make([]string, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
row = strings.TrimSpace(row)
|
||||
if strings.HasPrefix(row, "token") {
|
||||
continue
|
||||
}
|
||||
newRows = append(newRows, row)
|
||||
}
|
||||
res.Msg = strings.Join(newRows, "\n")
|
||||
}
|
||||
|
||||
// PUT api/config
|
||||
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http put request [/api/config]")
|
||||
defer func() {
|
||||
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
// get new config content
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
res.Code = 400
|
||||
res.Msg = "body can't be empty"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
// get token from origin content
|
||||
token := ""
|
||||
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("load frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
for _, row := range strings.Split(content, "\n") {
|
||||
row = strings.TrimSpace(row)
|
||||
if strings.HasPrefix(row, "token") {
|
||||
token = row
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tmpRows := make([]string, 0)
|
||||
for _, row := range strings.Split(string(body), "\n") {
|
||||
row = strings.TrimSpace(row)
|
||||
if strings.HasPrefix(row, "token") {
|
||||
continue
|
||||
}
|
||||
tmpRows = append(tmpRows, row)
|
||||
}
|
||||
|
||||
newRows := make([]string, 0)
|
||||
if token != "" {
|
||||
for _, row := range tmpRows {
|
||||
newRows = append(newRows, row)
|
||||
if strings.HasPrefix(row, "[common]") {
|
||||
newRows = append(newRows, token)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newRows = tmpRows
|
||||
}
|
||||
content = strings.Join(newRows, "\n")
|
||||
|
||||
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
||||
if err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@@ -15,39 +15,35 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"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/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
type Control struct {
|
||||
// frpc service
|
||||
svr *Service
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runId string
|
||||
|
||||
// login message to server, only used
|
||||
loginMsg *msg.Login
|
||||
// manage all proxies
|
||||
pxyCfgs map[string]config.ProxyConf
|
||||
pm *proxy.ProxyManager
|
||||
|
||||
pm *ProxyManager
|
||||
// manage all visitors
|
||||
vm *VisitorManager
|
||||
|
||||
// control connection
|
||||
conn frpNet.Conn
|
||||
@@ -61,14 +57,10 @@ type Control struct {
|
||||
// read from this channel to get the next message sent by server
|
||||
readCh chan (msg.Message)
|
||||
|
||||
// run id got from server
|
||||
runId string
|
||||
|
||||
// if we call close() in control, do not reconnect to server
|
||||
exit bool
|
||||
|
||||
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
||||
closedCh chan int
|
||||
closedCh chan struct{}
|
||||
|
||||
closedDoneCh chan struct{}
|
||||
|
||||
// last time got the Pong message
|
||||
lastPong time.Time
|
||||
@@ -82,54 +74,37 @@ type Control struct {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control {
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: g.GlbClientCfg.PoolCount,
|
||||
User: g.GlbClientCfg.User,
|
||||
Version: version.Full(),
|
||||
}
|
||||
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
|
||||
ctl := &Control{
|
||||
svr: svr,
|
||||
loginMsg: loginMsg,
|
||||
runId: runId,
|
||||
conn: conn,
|
||||
session: session,
|
||||
pxyCfgs: pxyCfgs,
|
||||
sendCh: make(chan msg.Message, 100),
|
||||
readCh: make(chan msg.Message, 100),
|
||||
closedCh: make(chan int),
|
||||
closedCh: make(chan struct{}),
|
||||
closedDoneCh: make(chan struct{}),
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
msgHandlerShutdown: shutdown.New(),
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
}
|
||||
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "")
|
||||
ctl.pm.Reload(pxyCfgs, visitorCfgs, false)
|
||||
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
|
||||
|
||||
ctl.vm = NewVisitorManager(ctl)
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
return ctl
|
||||
}
|
||||
|
||||
func (ctl *Control) Run() (err error) {
|
||||
for {
|
||||
err = ctl.login()
|
||||
if err != nil {
|
||||
ctl.Warn("login to server failed: %v", err)
|
||||
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and continues relogin to server
|
||||
if g.GlbClientCfg.LoginFailExit {
|
||||
return
|
||||
} else {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) Run() {
|
||||
go ctl.worker()
|
||||
|
||||
// start all local visitors and send NewProxy message for all configured proxies
|
||||
ctl.pm.Reset(ctl.sendCh, ctl.runId)
|
||||
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew})
|
||||
return nil
|
||||
// start all proxies
|
||||
ctl.pm.Reload(ctl.pxyCfgs)
|
||||
|
||||
// start all visitors
|
||||
go ctl.vm.Run()
|
||||
return
|
||||
}
|
||||
|
||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||
@@ -156,7 +131,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||
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) {
|
||||
@@ -171,82 +146,20 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
}
|
||||
|
||||
func (ctl *Control) Close() error {
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
ctl.exit = true
|
||||
ctl.pm.CloseProxies()
|
||||
return nil
|
||||
}
|
||||
|
||||
// login send a login message to server and wait for a loginResp message.
|
||||
func (ctl *Control) login() (err error) {
|
||||
if ctl.conn != nil {
|
||||
ctl.conn.Close()
|
||||
}
|
||||
ctl.pm.Close()
|
||||
ctl.conn.Close()
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
|
||||
conn, err := frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
session, errRet := fmux.Client(conn, fmuxCfg)
|
||||
if errRet != nil {
|
||||
return errRet
|
||||
}
|
||||
stream, errRet := session.OpenStream()
|
||||
if errRet != nil {
|
||||
session.Close()
|
||||
return errRet
|
||||
}
|
||||
conn = frpNet.WrapConn(stream)
|
||||
ctl.session = session
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(g.GlbClientCfg.Token, now)
|
||||
ctl.loginMsg.Timestamp = now
|
||||
ctl.loginMsg.RunId = ctl.runId
|
||||
|
||||
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var loginRespMsg msg.LoginResp
|
||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
ctl.Error("%s", loginRespMsg.Error)
|
||||
return err
|
||||
}
|
||||
|
||||
ctl.conn = conn
|
||||
// update runId got from server
|
||||
ctl.runId = loginRespMsg.RunId
|
||||
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
||||
ctl.ClearLogPrefix()
|
||||
ctl.AddLogPrefix(loginRespMsg.RunId)
|
||||
ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosedDoneCh returns a channel which will be closed after all resources are released
|
||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||
return ctl.closedDoneCh
|
||||
}
|
||||
|
||||
// connectServer return a new connection to frps
|
||||
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
stream, errRet := ctl.session.OpenStream()
|
||||
@@ -257,8 +170,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
}
|
||||
conn = frpNet.WrapConn(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 g.GlbClientCfg.TLSEnable {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
ctl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
@@ -286,6 +205,7 @@ func (ctl *Control) reader() {
|
||||
return
|
||||
} else {
|
||||
ctl.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -364,87 +284,35 @@ func (ctl *Control) msgHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
// controler keep watching closedCh, start a new connection if previous control connection is closed.
|
||||
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
|
||||
// If controler is notified by closedCh, reader and writer and handler will exit
|
||||
func (ctl *Control) worker() {
|
||||
go ctl.msgHandler()
|
||||
go ctl.reader()
|
||||
go ctl.writer()
|
||||
|
||||
var err error
|
||||
maxDelayTime := 20 * time.Second
|
||||
delayTime := time.Second
|
||||
select {
|
||||
case <-ctl.closedCh:
|
||||
// close related channels and wait until other goroutines done
|
||||
close(ctl.readCh)
|
||||
ctl.readerShutdown.WaitDone()
|
||||
ctl.msgHandlerShutdown.WaitDone()
|
||||
|
||||
checkInterval := 60 * time.Second
|
||||
checkProxyTicker := time.NewTicker(checkInterval)
|
||||
for {
|
||||
select {
|
||||
case <-checkProxyTicker.C:
|
||||
// check which proxy registered failed and reregister it to server
|
||||
ctl.pm.CheckAndStartProxy([]string{ProxyStatusStartErr, ProxyStatusClosed})
|
||||
case _, ok := <-ctl.closedCh:
|
||||
// we won't get any variable from this channel
|
||||
if !ok {
|
||||
// close related channels and wait until other goroutines done
|
||||
close(ctl.readCh)
|
||||
ctl.readerShutdown.WaitDone()
|
||||
ctl.msgHandlerShutdown.WaitDone()
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
|
||||
ctl.pm.CloseProxies()
|
||||
// if ctl.exit is true, just exit
|
||||
ctl.mu.RLock()
|
||||
exit := ctl.exit
|
||||
ctl.mu.RUnlock()
|
||||
if exit {
|
||||
return
|
||||
}
|
||||
|
||||
// loop util reconnecting to server success
|
||||
for {
|
||||
ctl.Info("try to reconnect to server...")
|
||||
err = ctl.login()
|
||||
if err != nil {
|
||||
ctl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
break
|
||||
}
|
||||
|
||||
// init related channels and variables
|
||||
ctl.sendCh = make(chan msg.Message, 100)
|
||||
ctl.readCh = make(chan msg.Message, 100)
|
||||
ctl.closedCh = make(chan int)
|
||||
ctl.readerShutdown = shutdown.New()
|
||||
ctl.writerShutdown = shutdown.New()
|
||||
ctl.msgHandlerShutdown = shutdown.New()
|
||||
ctl.pm.Reset(ctl.sendCh, ctl.runId)
|
||||
|
||||
// previous work goroutines should be closed and start them here
|
||||
go ctl.msgHandler()
|
||||
go ctl.writer()
|
||||
go ctl.reader()
|
||||
|
||||
// start all configured proxies
|
||||
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew, ProxyStatusClosed})
|
||||
|
||||
checkProxyTicker.Stop()
|
||||
checkProxyTicker = time.NewTicker(checkInterval)
|
||||
}
|
||||
close(ctl.closedDoneCh)
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
|
||||
err := ctl.pm.Reload(pxyCfgs, visitorCfgs, true)
|
||||
return err
|
||||
func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
ctl.pm.Reload(pxyCfgs)
|
||||
return nil
|
||||
}
|
||||
|
28
client/event/event.go
Normal file
28
client/event/event.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
)
|
||||
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
EvStartProxy EventType = iota
|
||||
EvCloseProxy
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPayloadType = errors.New("error payload type")
|
||||
)
|
||||
|
||||
type EventHandler func(evType EventType, payload interface{}) error
|
||||
|
||||
type StartProxyPayload struct {
|
||||
NewProxyMsg *msg.NewProxy
|
||||
}
|
||||
|
||||
type CloseProxyPayload struct {
|
||||
CloseProxyMsg *msg.CloseProxy
|
||||
}
|
182
client/health/health.go
Normal file
182
client/health/health.go
Normal file
@@ -0,0 +1,182 @@
|
||||
// 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 health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHealthCheckType = errors.New("error health check type")
|
||||
)
|
||||
|
||||
type HealthCheckMonitor struct {
|
||||
checkType string
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
maxFailedTimes int
|
||||
|
||||
// For tcp
|
||||
addr string
|
||||
|
||||
// For http
|
||||
url string
|
||||
|
||||
failedTimes uint64
|
||||
statusOK bool
|
||||
statusNormalFn func()
|
||||
statusFailedFn func()
|
||||
|
||||
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 {
|
||||
|
||||
if intervalS <= 0 {
|
||||
intervalS = 10
|
||||
}
|
||||
if timeoutS <= 0 {
|
||||
timeoutS = 3
|
||||
}
|
||||
if maxFailedTimes <= 0 {
|
||||
maxFailedTimes = 1
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &HealthCheckMonitor{
|
||||
checkType: checkType,
|
||||
interval: time.Duration(intervalS) * time.Second,
|
||||
timeout: time.Duration(timeoutS) * time.Second,
|
||||
maxFailedTimes: maxFailedTimes,
|
||||
addr: addr,
|
||||
url: url,
|
||||
statusOK: false,
|
||||
statusNormalFn: statusNormalFn,
|
||||
statusFailedFn: statusFailedFn,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
|
||||
monitor.l = l
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) Start() {
|
||||
go monitor.checkWorker()
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) Stop() {
|
||||
monitor.cancel()
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||
for {
|
||||
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||
err := monitor.doCheck(ctx)
|
||||
|
||||
// check if this monitor has been closed
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
cancel()
|
||||
return
|
||||
default:
|
||||
cancel()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if monitor.l != nil {
|
||||
monitor.l.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")
|
||||
}
|
||||
monitor.statusOK = true
|
||||
monitor.statusNormalFn()
|
||||
}
|
||||
} else {
|
||||
if monitor.l != nil {
|
||||
monitor.l.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")
|
||||
}
|
||||
monitor.statusOK = false
|
||||
monitor.statusFailedFn()
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(monitor.interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error {
|
||||
switch monitor.checkType {
|
||||
case "tcp":
|
||||
return monitor.doTcpCheck(ctx)
|
||||
case "http":
|
||||
return monitor.doHttpCheck(ctx)
|
||||
default:
|
||||
return ErrHealthCheckType
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
|
||||
// if tcp address is not specified, always return nil
|
||||
if monitor.addr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(ctx, "tcp", monitor.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
||||
req, err := http.NewRequest("GET", monitor.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -12,13 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -33,14 +36,16 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// Proxy defines how to deal with work connections for different proxy type.
|
||||
// Proxy defines how to handle work connections for different proxy type.
|
||||
type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(conn frpNet.Conn)
|
||||
InWorkConn(frpNet.Conn, *msg.StartWorkConn)
|
||||
|
||||
Close()
|
||||
log.Logger
|
||||
@@ -53,32 +58,32 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.StcpProxyConf:
|
||||
pxy = &StcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
pxy = &XtcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
@@ -93,7 +98,7 @@ type BaseProxy struct {
|
||||
|
||||
// TCP
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.TcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -115,14 +120,14 @@ func (pxy *TcpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -144,14 +149,14 @@ func (pxy *HttpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -173,14 +178,14 @@ func (pxy *HttpsProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// STCP
|
||||
type StcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.StcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -202,14 +207,14 @@ func (pxy *StcpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XtcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.XtcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -231,7 +236,7 @@ func (pxy *XtcpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
defer conn.Close()
|
||||
var natHoleSidMsg msg.NatHoleSid
|
||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||
@@ -272,37 +277,108 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
}
|
||||
clientConn.SetReadDeadline(time.Time{})
|
||||
clientConn.Close()
|
||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
|
||||
// Send sid to visitor udp address.
|
||||
time.Sleep(time.Second)
|
||||
if natHoleRespMsg.Error != "" {
|
||||
pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Send detect message
|
||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
pxy.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)
|
||||
pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
return
|
||||
}
|
||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
pxy.Trace("send all detect msg done")
|
||||
|
||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||
|
||||
// Listen for clientConn's address and wait for visitor connection
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
pxy.Error("dial visitor udp address error: %v", err)
|
||||
pxy.Error("listen on visitorConn's local adress error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||
defer lConn.Close()
|
||||
|
||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
||||
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 {
|
||||
pxy.Warn("get sid from visitor error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Time{})
|
||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||
pxy.Warn("incorrect sid from visitor")
|
||||
return
|
||||
}
|
||||
pool.PutBuf(sidBuf)
|
||||
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||
|
||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
|
||||
if err != nil {
|
||||
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||
if err != nil {
|
||||
pxy.Error("create yamux server from kcp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Accept()
|
||||
if err != nil {
|
||||
pxy.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))
|
||||
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//uConn := ipv4.NewConn(tConn)
|
||||
//uConn.SetTTL(3)
|
||||
|
||||
tConn.Write(content)
|
||||
tConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
@@ -340,7 +416,7 @@ func (pxy *UdpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
@@ -407,7 +483,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
@@ -427,10 +503,43 @@ 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 h.SourceAddress.To16() == nil {
|
||||
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)
|
||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||
workConn.Debug("handle by plugin finished")
|
||||
return
|
||||
} else {
|
||||
@@ -443,6 +552,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||
|
||||
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())
|
||||
|
||||
if len(extraInfo) > 0 {
|
||||
localConn.Write(extraInfo)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, remote)
|
||||
workConn.Debug("join connections closed")
|
||||
}
|
139
client/proxy/proxy_manager.go
Normal file
139
client/proxy/proxy_manager.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/golib/errors"
|
||||
)
|
||||
|
||||
type ProxyManager struct {
|
||||
sendCh chan (msg.Message)
|
||||
proxies map[string]*ProxyWrapper
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
logPrefix string
|
||||
log.Logger
|
||||
}
|
||||
|
||||
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 (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
|
||||
pm.mu.RLock()
|
||||
pxy, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("proxy [%s] not found", name)
|
||||
}
|
||||
|
||||
err := pxy.SetRunningStatus(remoteAddr, serverRespErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Close() {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
pxy.Stop()
|
||||
}
|
||||
pm.proxies = make(map[string]*ProxyWrapper)
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
pm.mu.RLock()
|
||||
pw, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if ok {
|
||||
pw.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) HandleEvent(evType event.EventType, payload interface{}) error {
|
||||
var m msg.Message
|
||||
switch e := payload.(type) {
|
||||
case *event.StartProxyPayload:
|
||||
m = e.NewProxyMsg
|
||||
case *event.CloseProxyPayload:
|
||||
m = e.CloseProxyMsg
|
||||
default:
|
||||
return event.ErrPayloadType
|
||||
}
|
||||
|
||||
err := errors.PanicToError(func() {
|
||||
pm.sendCh <- m
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
|
||||
ps := make([]*ProxyStatus, 0)
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
ps = append(ps, pxy.GetStatus())
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
delPxyNames := make([]string, 0)
|
||||
for name, pxy := range pm.proxies {
|
||||
del := false
|
||||
cfg, ok := pxyCfgs[name]
|
||||
if !ok {
|
||||
del = true
|
||||
} else {
|
||||
if !pxy.Cfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
}
|
||||
|
||||
if del {
|
||||
delPxyNames = append(delPxyNames, name)
|
||||
delete(pm.proxies, name)
|
||||
|
||||
pxy.Stop()
|
||||
}
|
||||
}
|
||||
if len(delPxyNames) > 0 {
|
||||
pm.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)
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
|
||||
pxy.Start()
|
||||
}
|
||||
}
|
||||
if len(addPxyNames) > 0 {
|
||||
pm.Info("proxy added: %v", addPxyNames)
|
||||
}
|
||||
}
|
244
client/proxy/proxy_wrapper.go
Normal file
244
client/proxy/proxy_wrapper.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/golib/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ProxyStatusNew = "new"
|
||||
ProxyStatusWaitStart = "wait start"
|
||||
ProxyStatusStartErr = "start error"
|
||||
ProxyStatusRunning = "running"
|
||||
ProxyStatusCheckFailed = "check failed"
|
||||
ProxyStatusClosed = "closed"
|
||||
)
|
||||
|
||||
var (
|
||||
statusCheckInterval time.Duration = 3 * time.Second
|
||||
waitResponseTimeout = 20 * time.Second
|
||||
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"`
|
||||
|
||||
// Got from server.
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
type ProxyWrapper struct {
|
||||
ProxyStatus
|
||||
|
||||
// underlying proxy
|
||||
pxy Proxy
|
||||
|
||||
// if ProxyConf has healcheck config
|
||||
// monitor will watch if it is alive
|
||||
monitor *health.HealthCheckMonitor
|
||||
|
||||
// event handler
|
||||
handler event.EventHandler
|
||||
|
||||
health uint32
|
||||
lastSendStartMsg time.Time
|
||||
lastStartErr time.Time
|
||||
closeCh chan struct{}
|
||||
healthNotifyCh chan struct{}
|
||||
mu sync.RWMutex
|
||||
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
|
||||
baseInfo := cfg.GetBaseInfo()
|
||||
pw := &ProxyWrapper{
|
||||
ProxyStatus: ProxyStatus{
|
||||
Name: baseInfo.ProxyName,
|
||||
Type: baseInfo.ProxyType,
|
||||
Status: ProxyStatusNew,
|
||||
Cfg: cfg,
|
||||
},
|
||||
closeCh: make(chan struct{}),
|
||||
healthNotifyCh: make(chan struct{}),
|
||||
handler: eventHandler,
|
||||
Logger: log.NewPrefixLogger(logPrefix),
|
||||
}
|
||||
pw.AddLogPrefix(pw.Name)
|
||||
|
||||
if baseInfo.HealthCheckType != "" {
|
||||
pw.health = 1 // means failed
|
||||
pw.monitor = health.NewHealthCheckMonitor(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")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.Cfg)
|
||||
return pw
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
if pw.Status != ProxyStatusWaitStart {
|
||||
return fmt.Errorf("status not wait start, ignore start message")
|
||||
}
|
||||
|
||||
pw.RemoteAddr = remoteAddr
|
||||
if respErr != "" {
|
||||
pw.Status = ProxyStatusStartErr
|
||||
pw.Err = respErr
|
||||
pw.lastStartErr = time.Now()
|
||||
return fmt.Errorf(pw.Err)
|
||||
}
|
||||
|
||||
if err := pw.pxy.Run(); err != nil {
|
||||
pw.Status = ProxyStatusStartErr
|
||||
pw.Err = err.Error()
|
||||
pw.lastStartErr = time.Now()
|
||||
return err
|
||||
}
|
||||
|
||||
pw.Status = ProxyStatusRunning
|
||||
pw.Err = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) Start() {
|
||||
go pw.checkWorker()
|
||||
if pw.monitor != nil {
|
||||
go pw.monitor.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) Stop() {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
close(pw.closeCh)
|
||||
close(pw.healthNotifyCh)
|
||||
pw.pxy.Close()
|
||||
if pw.monitor != nil {
|
||||
pw.monitor.Stop()
|
||||
}
|
||||
pw.Status = ProxyStatusClosed
|
||||
|
||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||
CloseProxyMsg: &msg.CloseProxy{
|
||||
ProxyName: pw.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) checkWorker() {
|
||||
if pw.monitor != nil {
|
||||
// let monitor do check request first
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
for {
|
||||
// check proxy status
|
||||
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))) {
|
||||
|
||||
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
|
||||
pw.Status = ProxyStatusWaitStart
|
||||
|
||||
var newProxyMsg msg.NewProxy
|
||||
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
||||
pw.lastSendStartMsg = now
|
||||
pw.handler(event.EvStartProxy, &event.StartProxyPayload{
|
||||
NewProxyMsg: &newProxyMsg,
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
pw.mu.Unlock()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-pw.closeCh:
|
||||
return
|
||||
case <-time.After(statusCheckInterval):
|
||||
case <-pw.healthNotifyCh:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) statusNormalCallback() {
|
||||
atomic.StoreUint32(&pw.health, 0)
|
||||
errors.PanicToError(func() {
|
||||
select {
|
||||
case pw.healthNotifyCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
pw.Info("health check success")
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) statusFailedCallback() {
|
||||
atomic.StoreUint32(&pw.health, 1)
|
||||
errors.PanicToError(func() {
|
||||
select {
|
||||
case pw.healthNotifyCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
pw.Info("health check failed")
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
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, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
|
||||
pw.mu.RLock()
|
||||
defer pw.mu.RUnlock()
|
||||
ps := &ProxyStatus{
|
||||
Name: pw.Name,
|
||||
Type: pw.Type,
|
||||
Status: pw.Status,
|
||||
Err: pw.Err,
|
||||
Cfg: pw.Cfg,
|
||||
RemoteAddr: pw.RemoteAddr,
|
||||
}
|
||||
return ps
|
||||
}
|
@@ -1,365 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"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/golib/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ProxyStatusNew = "new"
|
||||
ProxyStatusStartErr = "start error"
|
||||
ProxyStatusWaitStart = "wait start"
|
||||
ProxyStatusRunning = "running"
|
||||
ProxyStatusClosed = "closed"
|
||||
)
|
||||
|
||||
type ProxyManager struct {
|
||||
ctl *Control
|
||||
|
||||
proxies map[string]*ProxyWrapper
|
||||
|
||||
visitorCfgs map[string]config.ProxyConf
|
||||
visitors map[string]Visitor
|
||||
|
||||
sendCh chan (msg.Message)
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type ProxyWrapper struct {
|
||||
Name string
|
||||
Type string
|
||||
Status string
|
||||
Err string
|
||||
Cfg config.ProxyConf
|
||||
|
||||
RemoteAddr string
|
||||
|
||||
pxy Proxy
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type ProxyStatus struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Err string `json:"err"`
|
||||
Cfg config.ProxyConf `json:"cfg"`
|
||||
|
||||
// Got from server.
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
func NewProxyWrapper(cfg config.ProxyConf) *ProxyWrapper {
|
||||
return &ProxyWrapper{
|
||||
Name: cfg.GetBaseInfo().ProxyName,
|
||||
Type: cfg.GetBaseInfo().ProxyType,
|
||||
Status: ProxyStatusNew,
|
||||
Cfg: cfg,
|
||||
pxy: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) GetStatusStr() string {
|
||||
pw.mu.RLock()
|
||||
defer pw.mu.RUnlock()
|
||||
return pw.Status
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
|
||||
pw.mu.RLock()
|
||||
defer pw.mu.RUnlock()
|
||||
ps := &ProxyStatus{
|
||||
Name: pw.Name,
|
||||
Type: pw.Type,
|
||||
Status: pw.Status,
|
||||
Err: pw.Err,
|
||||
Cfg: pw.Cfg,
|
||||
RemoteAddr: pw.RemoteAddr,
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) WaitStart() {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
pw.Status = ProxyStatusWaitStart
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error {
|
||||
if pw.pxy != nil {
|
||||
pw.pxy.Close()
|
||||
pw.pxy = nil
|
||||
}
|
||||
|
||||
if serverRespErr != "" {
|
||||
pw.mu.Lock()
|
||||
pw.Status = ProxyStatusStartErr
|
||||
pw.RemoteAddr = remoteAddr
|
||||
pw.Err = serverRespErr
|
||||
pw.mu.Unlock()
|
||||
return fmt.Errorf(serverRespErr)
|
||||
}
|
||||
|
||||
pxy := NewProxy(pw.Cfg)
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
pw.RemoteAddr = remoteAddr
|
||||
if err := pxy.Run(); err != nil {
|
||||
pw.Status = ProxyStatusStartErr
|
||||
pw.Err = err.Error()
|
||||
return err
|
||||
}
|
||||
pw.Status = ProxyStatusRunning
|
||||
pw.Err = ""
|
||||
pw.pxy = pxy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
|
||||
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)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) Close() {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
if pw.pxy != nil {
|
||||
pw.pxy.Close()
|
||||
pw.pxy = nil
|
||||
}
|
||||
pw.Status = ProxyStatusClosed
|
||||
}
|
||||
|
||||
func NewProxyManager(ctl *Control, msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
|
||||
return &ProxyManager{
|
||||
ctl: ctl,
|
||||
proxies: make(map[string]*ProxyWrapper),
|
||||
visitorCfgs: make(map[string]config.ProxyConf),
|
||||
visitors: make(map[string]Visitor),
|
||||
sendCh: msgSendCh,
|
||||
closed: false,
|
||||
Logger: log.NewPrefixLogger(logPrefix),
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Reset(msgSendCh chan (msg.Message), logPrefix string) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
pm.closed = false
|
||||
pm.sendCh = msgSendCh
|
||||
pm.ClearLogPrefix()
|
||||
pm.AddLogPrefix(logPrefix)
|
||||
}
|
||||
|
||||
// Must hold the lock before calling this function.
|
||||
func (pm *ProxyManager) sendMsg(m msg.Message) error {
|
||||
err := errors.PanicToError(func() {
|
||||
pm.sendCh <- m
|
||||
})
|
||||
if err != nil {
|
||||
pm.closed = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
if pm.closed {
|
||||
return fmt.Errorf("ProxyManager is closed now")
|
||||
}
|
||||
|
||||
pxy, ok := pm.proxies[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("no proxy found")
|
||||
}
|
||||
|
||||
if err := pxy.Start(remoteAddr, serverRespErr); err != nil {
|
||||
errRet := err
|
||||
err = pm.sendMsg(&msg.CloseProxy{
|
||||
ProxyName: name,
|
||||
})
|
||||
if err != nil {
|
||||
errRet = fmt.Errorf("send CloseProxy message error")
|
||||
}
|
||||
return errRet
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) CloseProxies() {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
pxy.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// pxyStatus: check and start proxies in which status
|
||||
func (pm *ProxyManager) CheckAndStartProxy(pxyStatus []string) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
if pm.closed {
|
||||
pm.Warn("CheckAndStartProxy error: ProxyManager is closed now")
|
||||
return
|
||||
}
|
||||
|
||||
for _, pxy := range pm.proxies {
|
||||
status := pxy.GetStatusStr()
|
||||
for _, s := range pxyStatus {
|
||||
if status == s {
|
||||
var newProxyMsg msg.NewProxy
|
||||
pxy.Cfg.MarshalToMsg(&newProxyMsg)
|
||||
err := pm.sendMsg(&newProxyMsg)
|
||||
if err != nil {
|
||||
pm.Warn("[%s] proxy send NewProxy message error")
|
||||
return
|
||||
}
|
||||
pxy.WaitStart()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, cfg := range pm.visitorCfgs {
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
if _, exist := pm.visitors[name]; !exist {
|
||||
pm.Info("try to start visitor [%s]", name)
|
||||
visitor := NewVisitor(pm.ctl, cfg)
|
||||
err := visitor.Run()
|
||||
if err != nil {
|
||||
visitor.Warn("start error: %v", err)
|
||||
continue
|
||||
}
|
||||
pm.visitors[name] = visitor
|
||||
visitor.Info("start visitor success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf, startNow bool) error {
|
||||
pm.mu.Lock()
|
||||
defer func() {
|
||||
pm.mu.Unlock()
|
||||
if startNow {
|
||||
go pm.CheckAndStartProxy([]string{ProxyStatusNew})
|
||||
}
|
||||
}()
|
||||
if pm.closed {
|
||||
err := fmt.Errorf("Reload error: ProxyManager is closed now")
|
||||
pm.Warn(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
delPxyNames := make([]string, 0)
|
||||
for name, pxy := range pm.proxies {
|
||||
del := false
|
||||
cfg, ok := pxyCfgs[name]
|
||||
if !ok {
|
||||
del = true
|
||||
} else {
|
||||
if !pxy.Cfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
}
|
||||
|
||||
if del {
|
||||
delPxyNames = append(delPxyNames, name)
|
||||
delete(pm.proxies, name)
|
||||
|
||||
pxy.Close()
|
||||
err := pm.sendMsg(&msg.CloseProxy{
|
||||
ProxyName: name,
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Reload error: ProxyManager is closed now")
|
||||
pm.Warn(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
pm.Info("proxy removed: %v", delPxyNames)
|
||||
|
||||
addPxyNames := make([]string, 0)
|
||||
for name, cfg := range pxyCfgs {
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewProxyWrapper(cfg)
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
}
|
||||
}
|
||||
pm.Info("proxy added: %v", addPxyNames)
|
||||
|
||||
delVisitorName := make([]string, 0)
|
||||
for name, oldVisitorCfg := range pm.visitorCfgs {
|
||||
del := false
|
||||
cfg, ok := visitorCfgs[name]
|
||||
if !ok {
|
||||
del = true
|
||||
} else {
|
||||
if !oldVisitorCfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
}
|
||||
|
||||
if del {
|
||||
delVisitorName = append(delVisitorName, name)
|
||||
delete(pm.visitorCfgs, name)
|
||||
if visitor, ok := pm.visitors[name]; ok {
|
||||
visitor.Close()
|
||||
}
|
||||
delete(pm.visitors, name)
|
||||
}
|
||||
}
|
||||
pm.Info("visitor removed: %v", delVisitorName)
|
||||
|
||||
addVisitorName := make([]string, 0)
|
||||
for name, visitorCfg := range visitorCfgs {
|
||||
if _, ok := pm.visitorCfgs[name]; !ok {
|
||||
pm.visitorCfgs[name] = visitorCfg
|
||||
addVisitorName = append(addVisitorName, name)
|
||||
}
|
||||
}
|
||||
pm.Info("visitor added: %v", addVisitorName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
|
||||
pm.mu.RLock()
|
||||
pw, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if ok {
|
||||
pw.InWorkConn(workConn)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
|
||||
ps := make([]*ProxyStatus, 0)
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
ps = append(ps, pxy.GetStatus())
|
||||
}
|
||||
return ps
|
||||
}
|
@@ -15,35 +15,94 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
// manager control connection with server
|
||||
ctl *Control
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runId string
|
||||
|
||||
// manager control connection with server
|
||||
ctl *Control
|
||||
ctlMu sync.RWMutex
|
||||
|
||||
pxyCfgs map[string]config.ProxyConf
|
||||
visitorCfgs map[string]config.VisitorConf
|
||||
cfgMu sync.RWMutex
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
closedCh chan int
|
||||
}
|
||||
|
||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) {
|
||||
svr = &Service{
|
||||
closedCh: make(chan int),
|
||||
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
|
||||
}
|
||||
|
||||
svr = &Service{
|
||||
pxyCfgs: pxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
exit: 0,
|
||||
closedCh: make(chan int),
|
||||
}
|
||||
ctl := NewControl(svr, pxyCfgs, visitorCfgs)
|
||||
svr.ctl = ctl
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) GetController() *Control {
|
||||
svr.ctlMu.RLock()
|
||||
defer svr.ctlMu.RUnlock()
|
||||
return svr.ctl
|
||||
}
|
||||
|
||||
func (svr *Service) Run() error {
|
||||
err := svr.ctl.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
// first login
|
||||
for {
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
log.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 {
|
||||
return err
|
||||
} else {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
go svr.keepControllerWorking()
|
||||
|
||||
if g.GlbClientCfg.AdminPort != 0 {
|
||||
err = svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
@@ -54,6 +113,126 @@ func (svr *Service) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
svr.ctl.Close()
|
||||
func (svr *Service) keepControllerWorking() {
|
||||
maxDelayTime := 20 * time.Second
|
||||
delayTime := time.Second
|
||||
|
||||
for {
|
||||
<-svr.ctl.ClosedDoneCh()
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
log.Info("try to reconnect to server...")
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
log.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
|
||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var tlsConfig *tls.Config
|
||||
if g.GlbClientCfg.TLSEnable {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
session, err = fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stream, errRet := session.OpenStream()
|
||||
if errRet != nil {
|
||||
session.Close()
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
conn = frpNet.WrapConn(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,
|
||||
}
|
||||
|
||||
if err = msg.WriteMsg(conn, loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var loginRespMsg msg.LoginResp
|
||||
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
log.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)
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
||||
svr.cfgMu.Lock()
|
||||
svr.pxyCfgs = pxyCfgs
|
||||
svr.visitorCfgs = visitorCfgs
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
svr.ctl.Close()
|
||||
close(svr.closedCh)
|
||||
}
|
||||
|
@@ -18,14 +18,11 @@ import (
|
||||
"bytes"
|
||||
"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"
|
||||
@@ -35,6 +32,7 @@ import (
|
||||
|
||||
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.
|
||||
@@ -44,20 +42,20 @@ type Visitor interface {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
|
||||
func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||
baseVisitor := BaseVisitor{
|
||||
ctl: ctl,
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||
Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName),
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.StcpProxyConf:
|
||||
switch cfg := cfg.(type) {
|
||||
case *config.StcpVisitorConf:
|
||||
visitor = &StcpVisitor{
|
||||
BaseVisitor: baseVisitor,
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
case *config.XtcpVisitorConf:
|
||||
visitor = &XtcpVisitor{
|
||||
BaseVisitor: baseVisitor,
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
@@ -73,9 +71,9 @@ type BaseVisitor struct {
|
||||
}
|
||||
|
||||
type StcpVisitor struct {
|
||||
BaseVisitor
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.StcpProxyConf
|
||||
cfg *config.StcpVisitorConf
|
||||
}
|
||||
|
||||
func (sv *StcpVisitor) Run() (err error) {
|
||||
@@ -160,9 +158,9 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
}
|
||||
|
||||
type XtcpVisitor struct {
|
||||
BaseVisitor
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.XtcpProxyConf
|
||||
cfg *config.XtcpVisitorConf
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) Run() (err error) {
|
||||
@@ -183,7 +181,7 @@ func (sv *XtcpVisitor) worker() {
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
sv.Warn("stcp local listener closed")
|
||||
sv.Warn("xtcp local listener closed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -202,7 +200,16 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||
if err != nil {
|
||||
sv.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)
|
||||
return
|
||||
}
|
||||
defer visitorConn.Close()
|
||||
|
||||
now := time.Now().Unix()
|
||||
@@ -235,40 +242,36 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
pool.PutBuf(buf)
|
||||
|
||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
if natHoleRespMsg.Error != "" {
|
||||
sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
sv.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)
|
||||
sv.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 {
|
||||
sv.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)
|
||||
return
|
||||
@@ -278,11 +281,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
sv.Warn("incorrect sid from client")
|
||||
return
|
||||
}
|
||||
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
||||
pool.PutBuf(sidBuf)
|
||||
|
||||
sv.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)
|
||||
return
|
||||
@@ -300,25 +305,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, remote)
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Client(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
sv.Error("create yamux session error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Open()
|
||||
if err != nil {
|
||||
sv.Error("open yamux stream error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, muxConn)
|
||||
sv.Debug("join connections closed")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
123
client/visitor_manager.go
Normal file
123
client/visitor_manager.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
)
|
||||
|
||||
type VisitorManager struct {
|
||||
ctl *Control
|
||||
|
||||
cfgs map[string]config.VisitorConf
|
||||
visitors map[string]Visitor
|
||||
|
||||
checkInterval time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewVisitorManager(ctl *Control) *VisitorManager {
|
||||
return &VisitorManager{
|
||||
ctl: ctl,
|
||||
cfgs: make(map[string]config.VisitorConf),
|
||||
visitors: make(map[string]Visitor),
|
||||
checkInterval: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Run() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
vm.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Hold lock before calling this function.
|
||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||
name := cfg.GetBaseInfo().ProxyName
|
||||
visitor := NewVisitor(vm.ctl, cfg)
|
||||
err = visitor.Run()
|
||||
if err != nil {
|
||||
visitor.Warn("start error: %v", err)
|
||||
} else {
|
||||
vm.visitors[name] = visitor
|
||||
visitor.Info("start visitor success")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
|
||||
delNames := make([]string, 0)
|
||||
for name, oldCfg := range vm.cfgs {
|
||||
del := false
|
||||
cfg, ok := cfgs[name]
|
||||
if !ok {
|
||||
del = true
|
||||
} else {
|
||||
if !oldCfg.Compare(cfg) {
|
||||
del = true
|
||||
}
|
||||
}
|
||||
|
||||
if del {
|
||||
delNames = append(delNames, name)
|
||||
delete(vm.cfgs, name)
|
||||
if visitor, ok := vm.visitors[name]; ok {
|
||||
visitor.Close()
|
||||
}
|
||||
delete(vm.visitors, name)
|
||||
}
|
||||
}
|
||||
if len(delNames) > 0 {
|
||||
log.Info("visitor removed: %v", delNames)
|
||||
}
|
||||
|
||||
addNames := make([]string, 0)
|
||||
for name, cfg := range cfgs {
|
||||
if _, ok := vm.cfgs[name]; !ok {
|
||||
vm.cfgs[name] = cfg
|
||||
addNames = append(addNames, name)
|
||||
vm.startVisitor(cfg)
|
||||
}
|
||||
}
|
||||
if len(addNames) > 0 {
|
||||
log.Info("visitor added: %v", addNames)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *VisitorManager) Close() {
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
for _, v := range vm.visitors {
|
||||
v.Close()
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
@@ -28,7 +28,7 @@ import (
|
||||
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")
|
||||
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")
|
||||
|
@@ -28,7 +28,7 @@ import (
|
||||
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")
|
||||
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")
|
||||
|
@@ -16,7 +16,6 @@ package sub
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -25,8 +24,8 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,7 +36,13 @@ var reloadCmd = &cobra.Command{
|
||||
Use: "reload",
|
||||
Short: "Hot-Reload frpc configuration",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeIni, cfgFile)
|
||||
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -72,21 +77,16 @@ func reload() error {
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.GeneralResponse{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
} else if res.Code != 0 {
|
||||
return fmt.Errorf(res.Msg)
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -17,7 +17,6 @@ package sub
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -27,7 +26,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
@@ -69,11 +67,16 @@ var (
|
||||
sk string
|
||||
serverName string
|
||||
bindAddr string
|
||||
bindPort int
|
||||
|
||||
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{})
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -107,12 +110,12 @@ func handleSignal(svr *client.Service) {
|
||||
<-ch
|
||||
svr.Close()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
close(kcpDoneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfg(fileType int, filePath string) (err error) {
|
||||
func parseClientCommonCfg(fileType int, content string) (err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseClientCommonCfgFromIni(filePath)
|
||||
err = parseClientCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseClientCommonCfgFromCmd()
|
||||
}
|
||||
@@ -120,8 +123,6 @@ func parseClientCommonCfg(fileType int, filePath string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
g.GlbClientCfg.CfgFile = cfgFile
|
||||
|
||||
err = g.GlbClientCfg.ClientCommonConf.Check()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -129,13 +130,7 @@ func parseClientCommonCfg(fileType int, filePath string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromIni(filePath string) (err error) {
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
func parseClientCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -165,21 +160,28 @@ func parseClientCommonCfgFromCmd() (err error) {
|
||||
g.GlbClientCfg.LogLevel = logLevel
|
||||
g.GlbClientCfg.LogFile = logFile
|
||||
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
||||
if logFile == "console" {
|
||||
g.GlbClientCfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) (err error) {
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, cfgFilePath)
|
||||
var content string
|
||||
content, err = config.GetRenderedConfFromFile(cfgFilePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
g.GlbClientCfg.CfgFile = cfgFilePath
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conf, err := ini.LoadFile(cfgFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -188,7 +190,7 @@ func runClient(cfgFilePath string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (err error) {
|
||||
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
|
||||
@@ -203,7 +205,11 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
||||
},
|
||||
}
|
||||
}
|
||||
svr := client.NewService(pxyCfgs, visitorCfgs)
|
||||
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
@@ -211,5 +217,8 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
<-kcpDoneCh
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,7 +39,13 @@ var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Overview of all proxies status",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeIni, cfgFile)
|
||||
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
@@ -27,7 +27,7 @@ import (
|
||||
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")
|
||||
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")
|
||||
@@ -40,6 +40,7 @@ func init() {
|
||||
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
@@ -56,47 +57,57 @@ var stcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.StcpProxyConf{}
|
||||
proxyConfs := make(map[string]config.ProxyConf)
|
||||
visitorConfs := make(map[string]config.VisitorConf)
|
||||
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.ServerName = serverName
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if role == "server" {
|
||||
cfg := &config.StcpProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
cfg := &config.StcpVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.ServerName = serverName
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.BindPort = bindPort
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
visitorConfs[cfg.ProxyName] = cfg
|
||||
} else {
|
||||
fmt.Println("invalid role")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(proxyConfs, visitorConfs)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if cfg.Role == "server" {
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
visitorConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(nil, visitorConfs)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ import (
|
||||
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")
|
||||
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")
|
||||
|
@@ -27,7 +27,7 @@ import (
|
||||
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")
|
||||
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")
|
||||
@@ -40,6 +40,7 @@ func init() {
|
||||
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
@@ -56,47 +57,57 @@ var xtcpCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.XtcpProxyConf{}
|
||||
proxyConfs := make(map[string]config.ProxyConf)
|
||||
visitorConfs := make(map[string]config.VisitorConf)
|
||||
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.XtcpProxy
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.ServerName = serverName
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if role == "server" {
|
||||
cfg := &config.XtcpProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.XtcpProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
proxyConfs[cfg.ProxyName] = cfg
|
||||
} else if role == "visitor" {
|
||||
cfg := &config.XtcpVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.XtcpProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
cfg.Sk = sk
|
||||
cfg.ServerName = serverName
|
||||
cfg.BindAddr = bindAddr
|
||||
cfg.BindPort = bindPort
|
||||
err = cfg.Check()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
visitorConfs[cfg.ProxyName] = cfg
|
||||
} else {
|
||||
fmt.Println("invalid role")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = startService(proxyConfs, visitorConfs)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if cfg.Role == "server" {
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
visitorConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(nil, visitorConfs)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@@ -16,7 +16,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/server"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
)
|
||||
|
||||
@@ -44,17 +44,16 @@ var (
|
||||
proxyBindAddr string
|
||||
vhostHttpPort int
|
||||
vhostHttpsPort int
|
||||
vhostHttpTimeout int64
|
||||
dashboardAddr string
|
||||
dashboardPort int
|
||||
dashboardUser string
|
||||
dashboardPwd string
|
||||
assetsDir string
|
||||
logFile string
|
||||
logWay string
|
||||
logLevel string
|
||||
logMaxDays int64
|
||||
token string
|
||||
authTimeout int64
|
||||
subDomainHost string
|
||||
tcpMux bool
|
||||
allowPorts string
|
||||
@@ -63,7 +62,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||
@@ -73,17 +72,17 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
|
||||
rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
|
||||
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||
rootCmd.PersistentFlags().StringVarP(&logWay, "log_way", "", "console", "log way")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
|
||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "", "", "auth token")
|
||||
rootCmd.PersistentFlags().Int64VarP(&authTimeout, "auth_timeout", "", 900, "auth timeout")
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -98,7 +97,13 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
var err error
|
||||
if cfgFile != "" {
|
||||
err = parseServerCommonCfg(CfgFileTypeIni, cfgFile)
|
||||
var content string
|
||||
content, err = config.GetRenderedConfFromFile(cfgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.GlbServerCfg.CfgFile = cfgFile
|
||||
err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||
} else {
|
||||
err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||
}
|
||||
@@ -121,9 +126,9 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerCommonCfg(fileType int, filePath string) (err error) {
|
||||
func parseServerCommonCfg(fileType int, content string) (err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseServerCommonCfgFromIni(filePath)
|
||||
err = parseServerCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseServerCommonCfgFromCmd()
|
||||
}
|
||||
@@ -131,8 +136,6 @@ func parseServerCommonCfg(fileType int, filePath string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
g.GlbServerCfg.CfgFile = filePath
|
||||
|
||||
err = g.GlbServerCfg.ServerCommonConf.Check()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -142,13 +145,7 @@ func parseServerCommonCfg(fileType int, filePath string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromIni(filePath string) (err error) {
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
func parseServerCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -165,18 +162,35 @@ func parseServerCommonCfgFromCmd() (err error) {
|
||||
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.LogWay = logWay
|
||||
g.GlbServerCfg.LogLevel = logLevel
|
||||
g.GlbServerCfg.LogMaxDays = logMaxDays
|
||||
g.GlbServerCfg.Token = token
|
||||
g.GlbServerCfg.AuthTimeout = authTimeout
|
||||
g.GlbServerCfg.SubDomainHost = subDomainHost
|
||||
if len(allowPorts) > 0 {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||
|
||||
if logFile == "console" {
|
||||
g.GlbServerCfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbServerCfg.LogWay = "file"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,7 @@ token = 12345678
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_passwd = admin
|
||||
admin_pwd = admin
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 5
|
||||
@@ -41,11 +41,14 @@ user = your_name
|
||||
login_fail_exit = true
|
||||
|
||||
# communication protocol used to connect to server
|
||||
# now it supports tcp and kcp, default is tcp
|
||||
# now it supports tcp and kcp and websocket, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# if tls_enable is true, frpc will connect frps by tls
|
||||
tls_enable = true
|
||||
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
dns_server = 8.8.8.8
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start divided by ','
|
||||
# default is empty, means all proxies
|
||||
@@ -56,10 +59,10 @@ dns_server = 8.8.8.8
|
||||
# heartbeat_interval = 30
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# ssh is the proxy name same as server's configuration
|
||||
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh
|
||||
# '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]
|
||||
# tcp | udp | http | https, default is tcp
|
||||
# tcp | udp | http | https | stcp | xtcp, default is tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
@@ -69,6 +72,19 @@ use_encryption = false
|
||||
use_compression = false
|
||||
# remote port listen by frps
|
||||
remote_port = 6001
|
||||
# frps will load balancing connections for proxies in same group
|
||||
group = test_group
|
||||
# group should have same group key
|
||||
group_key = 123456
|
||||
# enable health check for the backend service, it support 'tcp' and 'http' now
|
||||
# frpc will connect local service's port to detect it's healthy status
|
||||
health_check_type = tcp
|
||||
# health check connection timeout
|
||||
health_check_timeout_s = 3
|
||||
# if continuous failed in 3 times, the proxy will be removed from frps
|
||||
health_check_max_failed = 3
|
||||
# every 10 seconds will do a health check
|
||||
health_check_interval_s = 10
|
||||
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
@@ -120,6 +136,15 @@ custom_domains = web02.yourdomain.com
|
||||
# locations is only available for http type
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
# params with prefix "header_" will be used to update http request headers
|
||||
header_X-From-Where = frp
|
||||
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
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 10
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
|
||||
[web02]
|
||||
type = https
|
||||
@@ -129,6 +154,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
|
||||
@@ -136,7 +164,7 @@ remote_port = 6003
|
||||
# if plugin is defined, local_ip and local_port is useless
|
||||
# plugin will handle connections got from frps
|
||||
plugin = unix_domain_socket
|
||||
# params set with prefix "plugin_" that plugin needed
|
||||
# params with prefix "plugin_" that plugin needed
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
|
||||
[plugin_http_proxy]
|
||||
@@ -162,6 +190,15 @@ 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
|
||||
|
||||
[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
|
||||
|
@@ -20,6 +20,9 @@ kcp_bind_port = 7000
|
||||
vhost_http_port = 80
|
||||
vhost_https_port = 443
|
||||
|
||||
# response header timeout(seconds) for vhost http server, default is 60s
|
||||
# vhost_http_timeout = 60
|
||||
|
||||
# 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
|
||||
@@ -56,13 +59,12 @@ max_pool_count = 5
|
||||
# max ports can be used for each client, default value is 0 means no limit
|
||||
max_ports_per_client = 0
|
||||
|
||||
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
|
||||
# if authentication_timeout is zero, the time is not verified, default is 900s
|
||||
authentication_timeout = 900
|
||||
|
||||
# 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
|
||||
|
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
|
BIN
doc/pic/zsxq.jpg
Normal file
BIN
doc/pic/zsxq.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
34
go.mod
Normal file
34
go.mod
Normal file
@@ -0,0 +1,34 @@
|
||||
module github.com/fatedier/frp
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible
|
||||
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-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/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
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/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/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
|
||||
)
|
37
go.sum
Normal file
37
go.sum
Normal file
@@ -0,0 +1,37 @@
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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 v2.0.4-0.20190317085623-2063a803e6fe+incompatible h1:pNNeBKz1jtMDupiwvtEGFTujA3J86xoEXGSkwVeYFsw=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible/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-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
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/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/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||
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/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
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/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=
|
@@ -44,6 +44,7 @@ type ClientCommonConf struct {
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
Start map[string]struct{} `json:"start"`
|
||||
Protocol string `json:"protocol"`
|
||||
TLSEnable bool `json:"tls_enable"`
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
}
|
||||
@@ -69,6 +70,7 @@ func GetDefaultClientConf() *ClientCommonConf {
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
HeartBeatInterval: 30,
|
||||
HeartBeatTimeout: 90,
|
||||
}
|
||||
@@ -186,13 +188,20 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "protocol"); ok {
|
||||
// Now it only support tcp and kcp.
|
||||
if tmpStr != "kcp" {
|
||||
tmpStr = "tcp"
|
||||
// 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", "heartbeat_timeout"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||
|
@@ -91,7 +91,9 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
|
||||
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
err = cfg.CheckForCli()
|
||||
if err = cfg.CheckForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,8 +102,15 @@ type BaseProxyConf struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
|
||||
// only used for client
|
||||
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||
LocalSvrConf
|
||||
HealthCheckConf
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||
@@ -112,7 +121,16 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
||||
if cfg.ProxyName != cmp.ProxyName ||
|
||||
cfg.ProxyType != cmp.ProxyType ||
|
||||
cfg.UseEncryption != cmp.UseEncryption ||
|
||||
cfg.UseCompression != cmp.UseCompression {
|
||||
cfg.UseCompression != cmp.UseCompression ||
|
||||
cfg.Group != cmp.Group ||
|
||||
cfg.GroupKey != cmp.GroupKey ||
|
||||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
|
||||
return false
|
||||
}
|
||||
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||
return false
|
||||
}
|
||||
if !cfg.HealthCheckConf.compare(&cmp.HealthCheckConf) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -123,6 +141,8 @@ func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.ProxyType = pMsg.ProxyType
|
||||
cfg.UseEncryption = pMsg.UseEncryption
|
||||
cfg.UseCompression = pMsg.UseCompression
|
||||
cfg.Group = pMsg.Group
|
||||
cfg.GroupKey = pMsg.GroupKey
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
|
||||
@@ -142,6 +162,29 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
if ok && tmpStr == "true" {
|
||||
cfg.UseCompression = true
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -150,6 +193,24 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.ProxyType = cfg.ProxyType
|
||||
pMsg.UseEncryption = cfg.UseEncryption
|
||||
pMsg.UseCompression = cfg.UseCompression
|
||||
pMsg.Group = cfg.Group
|
||||
pMsg.GroupKey = cfg.GroupKey
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if err = cfg.HealthCheckConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind info
|
||||
@@ -262,7 +323,7 @@ func (cfg *DomainConf) checkForSvr() (err error) {
|
||||
|
||||
if cfg.SubDomain != "" {
|
||||
if subDomainHost == "" {
|
||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
|
||||
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, "*") {
|
||||
return fmt.Errorf("'.' and '*' is not supported in subdomain")
|
||||
@@ -324,12 +385,83 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *LocalSvrConf) checkForCli() (err error) {
|
||||
if cfg.Plugin == "" {
|
||||
if cfg.LocalIp == "" {
|
||||
err = fmt.Errorf("local ip or plugin is required")
|
||||
return
|
||||
}
|
||||
if cfg.LocalPort <= 0 {
|
||||
err = fmt.Errorf("error local_port")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Health check info
|
||||
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
|
||||
HealthCheckAddr string `json:"-"`
|
||||
}
|
||||
|
||||
func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
|
||||
if cfg.HealthCheckType != cmp.HealthCheckType ||
|
||||
cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS ||
|
||||
cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed ||
|
||||
cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS ||
|
||||
cfg.HealthCheckUrl != cmp.HealthCheckUrl {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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"]
|
||||
|
||||
if tmpStr, ok := section["health_check_timeout_s"]; ok {
|
||||
if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] health_check_timeout_s error", name)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok := section["health_check_max_failed"]; ok {
|
||||
if cfg.HealthCheckMaxFailed, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] health_check_max_failed error", name)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok := section["health_check_interval_s"]; ok {
|
||||
if cfg.HealthCheckIntervalS, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] health_check_interval_s error", name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HealthCheckConf) checkForCli() error {
|
||||
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
|
||||
return fmt.Errorf("unsupport health check type")
|
||||
}
|
||||
if cfg.HealthCheckType != "" {
|
||||
if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" {
|
||||
return fmt.Errorf("health_check_url is required for health check type 'http'")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
|
||||
LocalSvrConf
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
@@ -339,8 +471,7 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
}
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
|
||||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
|
||||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -358,9 +489,6 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -369,7 +497,12 @@ func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) CheckForCli() error { return nil }
|
||||
func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
||||
|
||||
@@ -377,8 +510,6 @@ func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
||||
type UdpProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
|
||||
LocalSvrConf
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
@@ -388,8 +519,7 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
}
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
|
||||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
|
||||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -407,9 +537,6 @@ func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section in
|
||||
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -418,7 +545,12 @@ func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) CheckForCli() error { return nil }
|
||||
func (cfg *UdpProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
|
||||
|
||||
@@ -427,12 +559,11 @@ type HttpProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
|
||||
LocalSvrConf
|
||||
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
HttpUser string `json:"http_user"`
|
||||
HttpPwd string `json:"http_pwd"`
|
||||
Locations []string `json:"locations"`
|
||||
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 {
|
||||
@@ -443,13 +574,23 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
|
||||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
|
||||
cfg.HttpUser != cmpConf.HttpUser ||
|
||||
cfg.HttpPwd != cmpConf.HttpPwd {
|
||||
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 {
|
||||
return false
|
||||
} else {
|
||||
if v != v2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -461,6 +602,7 @@ func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
|
||||
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) {
|
||||
@@ -470,9 +612,6 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
@@ -487,6 +626,13 @@ 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.Headers = make(map[string]string)
|
||||
|
||||
for k, v := range section {
|
||||
if strings.HasPrefix(k, "header_") {
|
||||
cfg.Headers[strings.TrimPrefix(k, "header_")] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -498,9 +644,13 @@ func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
|
||||
pMsg.HttpUser = cfg.HttpUser
|
||||
pMsg.HttpPwd = cfg.HttpPwd
|
||||
pMsg.Headers = cfg.Headers
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.DomainConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -509,7 +659,7 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
||||
|
||||
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
||||
if vhostHttpPort == 0 {
|
||||
err = fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
}
|
||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
@@ -522,8 +672,6 @@ func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
||||
type HttpsProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
|
||||
LocalSvrConf
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
|
||||
@@ -533,8 +681,7 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
|
||||
}
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
|
||||
!cfg.DomainConf.compare(&cmpConf.DomainConf) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -552,9 +699,6 @@ func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section
|
||||
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -564,6 +708,9 @@ func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.DomainConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -587,14 +734,6 @@ type StcpProxyConf struct {
|
||||
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
|
||||
// used in role server
|
||||
LocalSvrConf
|
||||
|
||||
// used in role visitor
|
||||
ServerName string `json:"server_name"`
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
@@ -604,12 +743,8 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
}
|
||||
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||
cfg.Role != cmpConf.Role ||
|
||||
cfg.Sk != cmpConf.Sk ||
|
||||
cfg.ServerName != cmpConf.ServerName ||
|
||||
cfg.BindAddr != cmpConf.BindAddr ||
|
||||
cfg.BindPort != cmpConf.BindPort {
|
||||
cfg.Sk != cmpConf.Sk {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -626,36 +761,15 @@ func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
return
|
||||
}
|
||||
|
||||
tmpStr := section["role"]
|
||||
if tmpStr == "" {
|
||||
tmpStr = "server"
|
||||
}
|
||||
if tmpStr == "server" || tmpStr == "visitor" {
|
||||
cfg.Role = tmpStr
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
|
||||
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 tmpStr == "visitor" {
|
||||
prefix := section["prefix"]
|
||||
cfg.ServerName = prefix + section["server_name"]
|
||||
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||
cfg.BindAddr = "127.0.0.1"
|
||||
}
|
||||
|
||||
if tmpStr, ok := section["bind_port"]; ok {
|
||||
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||
}
|
||||
} else {
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -666,15 +780,12 @@ func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
}
|
||||
|
||||
func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
||||
if cfg.Role != "server" && cfg.Role != "visitor" {
|
||||
err = fmt.Errorf("role should be 'server' or 'visitor'")
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
if cfg.Role == "visitor" {
|
||||
if cfg.BindAddr == "" {
|
||||
err = fmt.Errorf("bind_addr shouldn't be empty")
|
||||
return
|
||||
}
|
||||
if cfg.Role != "server" {
|
||||
err = fmt.Errorf("role should be 'server'")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -689,14 +800,6 @@ type XtcpProxyConf struct {
|
||||
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
|
||||
// used in role server
|
||||
LocalSvrConf
|
||||
|
||||
// used in role visitor
|
||||
ServerName string `json:"server_name"`
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
@@ -708,10 +811,7 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||
cfg.Role != cmpConf.Role ||
|
||||
cfg.Sk != cmpConf.Sk ||
|
||||
cfg.ServerName != cmpConf.ServerName ||
|
||||
cfg.BindAddr != cmpConf.BindAddr ||
|
||||
cfg.BindPort != cmpConf.BindPort {
|
||||
cfg.Sk != cmpConf.Sk {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -728,36 +828,15 @@ func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
return
|
||||
}
|
||||
|
||||
tmpStr := section["role"]
|
||||
if tmpStr == "" {
|
||||
tmpStr = "server"
|
||||
}
|
||||
if tmpStr == "server" || tmpStr == "visitor" {
|
||||
cfg.Role = tmpStr
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
|
||||
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 tmpStr == "visitor" {
|
||||
prefix := section["prefix"]
|
||||
cfg.ServerName = prefix + section["server_name"]
|
||||
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||
cfg.BindAddr = "127.0.0.1"
|
||||
}
|
||||
|
||||
if tmpStr, ok := section["bind_port"]; ok {
|
||||
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||
}
|
||||
} else {
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -768,15 +847,12 @@ func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
}
|
||||
|
||||
func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
||||
if cfg.Role != "server" && cfg.Role != "visitor" {
|
||||
err = fmt.Errorf("role should be 'server' or 'visitor'")
|
||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
if cfg.Role == "visitor" {
|
||||
if cfg.BindAddr == "" {
|
||||
err = fmt.Errorf("bind_addr shouldn't be empty")
|
||||
return
|
||||
}
|
||||
if cfg.Role != "server" {
|
||||
err = fmt.Errorf("role should be 'server'")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -819,8 +895,14 @@ func ParseRangeSection(name string, section ini.Section) (sections map[string]in
|
||||
|
||||
// if len(startProxy) is 0, start all
|
||||
// otherwise just start proxies in startProxy map
|
||||
func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) (
|
||||
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
|
||||
func LoadAllConfFromIni(prefix string, content string, startProxy map[string]struct{}) (
|
||||
proxyConfs map[string]ProxyConf, visitorConfs map[string]VisitorConf, err error) {
|
||||
|
||||
conf, errRet := ini.Load(strings.NewReader(content))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
prefix += "."
|
||||
@@ -831,7 +913,7 @@ func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]st
|
||||
startAll = false
|
||||
}
|
||||
proxyConfs = make(map[string]ProxyConf)
|
||||
visitorConfs = make(map[string]ProxyConf)
|
||||
visitorConfs = make(map[string]VisitorConf)
|
||||
for name, section := range conf {
|
||||
if name == "common" {
|
||||
continue
|
||||
@@ -856,16 +938,27 @@ func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]st
|
||||
}
|
||||
|
||||
for subName, subSection := range subSections {
|
||||
cfg, err := NewProxyConfFromIni(prefix, subName, subSection)
|
||||
if err != nil {
|
||||
return proxyConfs, visitorConfs, err
|
||||
if subSection["role"] == "" {
|
||||
subSection["role"] = "server"
|
||||
}
|
||||
|
||||
role := subSection["role"]
|
||||
if role == "visitor" {
|
||||
if role == "server" {
|
||||
cfg, errRet := NewProxyConfFromIni(prefix, subName, subSection)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
proxyConfs[prefix+subName] = cfg
|
||||
} else if role == "visitor" {
|
||||
cfg, errRet := NewVisitorConfFromIni(prefix, subName, subSection)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
visitorConfs[prefix+subName] = cfg
|
||||
} else {
|
||||
proxyConfs[prefix+subName] = cfg
|
||||
err = fmt.Errorf("role should be 'server' or 'visitor'")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -51,8 +51,11 @@ type ServerCommonConf struct {
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
|
||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||
VhostHttpsPort int `json:"vhost_http_port"`
|
||||
DashboardAddr string `json:"dashboard_addr"`
|
||||
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"`
|
||||
@@ -64,9 +67,9 @@ type ServerCommonConf struct {
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
AuthTimeout int64 `json:"auth_timeout"`
|
||||
SubDomainHost string `json:"subdomain_host"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
Custom404Page string `json:"custom_404_page"`
|
||||
|
||||
AllowPorts map[int]struct{}
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
@@ -84,6 +87,7 @@ func GetDefaultServerConf() *ServerCommonConf {
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHttpPort: 0,
|
||||
VhostHttpsPort: 0,
|
||||
VhostHttpTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
@@ -94,7 +98,6 @@ func GetDefaultServerConf() *ServerCommonConf {
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
Token: "",
|
||||
AuthTimeout: 900,
|
||||
SubDomainHost: "",
|
||||
TcpMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
@@ -102,6 +105,7 @@ func GetDefaultServerConf() *ServerCommonConf {
|
||||
MaxPortsPerClient: 0,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +185,16 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
||||
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 {
|
||||
@@ -271,16 +285,6 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "authentication_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
|
||||
return
|
||||
} else {
|
||||
cfg.AuthTimeout = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
|
||||
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||
}
|
||||
@@ -291,6 +295,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
||||
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 {
|
||||
|
64
models/config/value.go
Normal file
64
models/config/value.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
glbEnvs map[string]string
|
||||
)
|
||||
|
||||
func init() {
|
||||
glbEnvs = make(map[string]string)
|
||||
envs := os.Environ()
|
||||
for _, env := range envs {
|
||||
kv := strings.Split(env, "=")
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
glbEnvs[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
type Values struct {
|
||||
Envs map[string]string // environment vars
|
||||
}
|
||||
|
||||
func GetValues() *Values {
|
||||
return &Values{
|
||||
Envs: glbEnvs,
|
||||
}
|
||||
}
|
||||
|
||||
func RenderContent(in string) (out string, err error) {
|
||||
tmpl, errRet := template.New("frp").Parse(in)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
buffer := bytes.NewBufferString("")
|
||||
v := GetValues()
|
||||
err = tmpl.Execute(buffer, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = buffer.String()
|
||||
return
|
||||
}
|
||||
|
||||
func GetRenderedConfFromFile(path string) (out string, err error) {
|
||||
var b []byte
|
||||
b, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
out, err = RenderContent(content)
|
||||
return
|
||||
}
|
213
models/config/visitor.go
Normal file
213
models/config/visitor.go
Normal file
@@ -0,0 +1,213 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var (
|
||||
visitorConfTypeMap map[string]reflect.Type
|
||||
)
|
||||
|
||||
func init() {
|
||||
visitorConfTypeMap = make(map[string]reflect.Type)
|
||||
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
|
||||
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
|
||||
}
|
||||
|
||||
type VisitorConf interface {
|
||||
GetBaseInfo() *BaseVisitorConf
|
||||
Compare(cmp VisitorConf) bool
|
||||
UnmarshalFromIni(prefix string, name string, section ini.Section) error
|
||||
Check() error
|
||||
}
|
||||
|
||||
func NewVisitorConfByType(cfgType string) VisitorConf {
|
||||
v, ok := visitorConfTypeMap[cfgType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cfg := reflect.New(v).Interface().(VisitorConf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) {
|
||||
cfgType := section["type"]
|
||||
if cfgType == "" {
|
||||
err = fmt.Errorf("visitor [%s] type shouldn't be empty", name)
|
||||
return
|
||||
}
|
||||
cfg = NewVisitorConfByType(cfgType)
|
||||
if cfg == nil {
|
||||
err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType)
|
||||
return
|
||||
}
|
||||
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.Check(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseVisitorConf struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Role string `json:"role"`
|
||||
Sk string `json:"sk"`
|
||||
ServerName string `json:"server_name"`
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) compare(cmp *BaseVisitorConf) bool {
|
||||
if cfg.ProxyName != cmp.ProxyName ||
|
||||
cfg.ProxyType != cmp.ProxyType ||
|
||||
cfg.UseEncryption != cmp.UseEncryption ||
|
||||
cfg.UseCompression != cmp.UseCompression ||
|
||||
cfg.Role != cmp.Role ||
|
||||
cfg.Sk != cmp.Sk ||
|
||||
cfg.ServerName != cmp.ServerName ||
|
||||
cfg.BindAddr != cmp.BindAddr ||
|
||||
cfg.BindPort != cmp.BindPort {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) check() (err error) {
|
||||
if cfg.Role != "visitor" {
|
||||
err = fmt.Errorf("invalid role")
|
||||
return
|
||||
}
|
||||
if cfg.BindAddr == "" {
|
||||
err = fmt.Errorf("bind_addr shouldn't be empty")
|
||||
return
|
||||
}
|
||||
if cfg.BindPort <= 0 {
|
||||
err = fmt.Errorf("bind_port is required")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
cfg.ProxyName = prefix + name
|
||||
cfg.ProxyType = section["type"]
|
||||
|
||||
if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" {
|
||||
cfg.UseEncryption = true
|
||||
}
|
||||
if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" {
|
||||
cfg.UseCompression = true
|
||||
}
|
||||
|
||||
cfg.Role = section["role"]
|
||||
if cfg.Role != "visitor" {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||
}
|
||||
cfg.Sk = section["sk"]
|
||||
cfg.ServerName = prefix + section["server_name"]
|
||||
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||
cfg.BindAddr = "127.0.0.1"
|
||||
}
|
||||
|
||||
if tmpStr, ok = section["bind_port"]; ok {
|
||||
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StcpVisitorConf struct {
|
||||
BaseVisitorConf
|
||||
}
|
||||
|
||||
func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||
cmpConf, ok := cmp.(*StcpVisitorConf)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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 *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
|
||||
}
|
||||
return
|
||||
}
|
@@ -17,44 +17,46 @@ 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{},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -86,17 +88,20 @@ type NewProxy struct {
|
||||
ProxyType string `json:"proxy_type"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
|
||||
// tcp and udp only
|
||||
RemotePort int `json:"remote_port"`
|
||||
|
||||
// http and https only
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
SubDomain string `json:"subdomain"`
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
HttpUser string `json:"http_user"`
|
||||
HttpPwd string `json:"http_pwd"`
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
SubDomain string `json:"subdomain"`
|
||||
Locations []string `json:"locations"`
|
||||
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"`
|
||||
@@ -121,6 +126,10 @@ 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"`
|
||||
}
|
||||
|
||||
type NewVisitorConn struct {
|
||||
@@ -163,6 +172,10 @@ type NatHoleResp struct {
|
||||
Sid string `json:"sid"`
|
||||
VisitorAddr string `json:"visitor_addr"`
|
||||
ClientAddr string `json:"client_addr"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type NatHoleClientDetectOK struct {
|
||||
}
|
||||
|
||||
type NatHoleSid struct {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package nathole
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -18,6 +18,11 @@ import (
|
||||
// Timeout seconds.
|
||||
var NatHoleTimeout int64 = 10
|
||||
|
||||
type SidRequest struct {
|
||||
Sid string
|
||||
NotifyCh chan struct{}
|
||||
}
|
||||
|
||||
type NatHoleController struct {
|
||||
listener *net.UDPConn
|
||||
|
||||
@@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
||||
clientCfg := &NatHoleClientCfg{
|
||||
Name: name,
|
||||
Sk: sk,
|
||||
SidCh: make(chan string),
|
||||
SidCh: make(chan *SidRequest),
|
||||
}
|
||||
nc.mu.Lock()
|
||||
nc.clientCfgs[name] = clientCfg
|
||||
@@ -106,10 +111,21 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
||||
}
|
||||
nc.mu.Lock()
|
||||
clientCfg, ok := nc.clientCfgs[m.ProxyName]
|
||||
if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
||||
if !ok {
|
||||
nc.mu.Unlock()
|
||||
errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)
|
||||
log.Debug(errInfo)
|
||||
nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
||||
return
|
||||
}
|
||||
if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
||||
nc.mu.Unlock()
|
||||
errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName)
|
||||
log.Debug(errInfo)
|
||||
nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
||||
return
|
||||
}
|
||||
|
||||
nc.sessions[sid] = session
|
||||
nc.mu.Unlock()
|
||||
log.Trace("handle visitor message, sid [%s]", sid)
|
||||
@@ -121,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
|
||||
@@ -130,7 +149,7 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
||||
// Wait client connections.
|
||||
select {
|
||||
case <-session.NotifyCh:
|
||||
resp := nc.GenNatHoleResponse(raddr, session)
|
||||
resp := nc.GenNatHoleResponse(session, "")
|
||||
log.Trace("send nat hole response to visitor")
|
||||
nc.listener.WriteToUDP(resp, raddr)
|
||||
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||
@@ -147,18 +166,28 @@ 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(raddr, session)
|
||||
resp := nc.GenNatHoleResponse(session, "")
|
||||
log.Trace("send nat hole response to client")
|
||||
nc.listener.WriteToUDP(resp, raddr)
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
|
||||
func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte {
|
||||
var (
|
||||
sid string
|
||||
visitorAddr string
|
||||
clientAddr string
|
||||
)
|
||||
if session != nil {
|
||||
sid = session.Sid
|
||||
visitorAddr = session.VisitorAddr.String()
|
||||
clientAddr = session.ClientAddr.String()
|
||||
}
|
||||
m := &msg.NatHoleResp{
|
||||
Sid: session.Sid,
|
||||
VisitorAddr: session.VisitorAddr.String(),
|
||||
ClientAddr: session.ClientAddr.String(),
|
||||
Sid: sid,
|
||||
VisitorAddr: visitorAddr,
|
||||
ClientAddr: clientAddr,
|
||||
Error: errInfo,
|
||||
}
|
||||
b := bytes.NewBuffer(nil)
|
||||
err := msg.WriteMsg(b, m)
|
||||
@@ -179,5 +208,5 @@ type NatHoleSession struct {
|
||||
type NatHoleClientCfg struct {
|
||||
Name string
|
||||
Sk string
|
||||
SidCh chan string
|
||||
SidCh chan *SidRequest
|
||||
}
|
@@ -64,7 +64,7 @@ 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 frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
|
||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||
|
114
models/plugin/https2http.go
Normal file
114
models/plugin/https2http.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// 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/http"
|
||||
"net/http/httputil"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
const PluginHTTPS2HTTP = "https2http"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||
}
|
||||
|
||||
type HTTPS2HTTPPlugin struct {
|
||||
crtPath string
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr 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"]
|
||||
|
||||
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,
|
||||
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
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
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 frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||
return PluginHTTPS2HTTP
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||
return nil
|
||||
}
|
@@ -46,7 +46,7 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
@@ -53,7 +53,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 frpNet.Conn, extraBufToLocal []byte) {
|
||||
defer conn.Close()
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.Server.ServeConn(wrapConn)
|
||||
|
@@ -18,9 +18,9 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const PluginStaticFile = "static_file"
|
||||
@@ -61,9 +61,10 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
} else {
|
||||
prefix = "/"
|
||||
}
|
||||
router := httprouter.New()
|
||||
router.Handler("GET", prefix+"*filepath", frpNet.MakeHttpGzipHandler(
|
||||
frpNet.NewHttpBasicAuthWraper(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))), httpUser, httpPasswd)))
|
||||
|
||||
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")
|
||||
sp.s = &http.Server{
|
||||
Handler: router,
|
||||
}
|
||||
@@ -71,7 +72,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 frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.l.PutConn(wrapConn)
|
||||
}
|
||||
|
@@ -53,11 +53,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 frpNet.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)
|
||||
}
|
||||
|
@@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||
|
@@ -15,7 +15,7 @@ rm -rf ./packages
|
||||
mkdir ./packages
|
||||
|
||||
os_all='linux windows darwin freebsd'
|
||||
arch_all='386 amd64 arm mips64 mips64le mips mipsle'
|
||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
|
||||
|
||||
for os in $os_all; do
|
||||
for arch in $arch_all; do
|
||||
@@ -44,7 +44,7 @@ for os in $os_all; do
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
||||
cp ./LICENSE ${frp_path}
|
||||
cp ./conf/* ${frp_path}
|
||||
cp -rf ./conf/* ${frp_path}
|
||||
|
||||
# packages
|
||||
cd ./packages
|
||||
|
@@ -26,6 +26,9 @@ import (
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
frpErr "github.com/fatedier/frp/models/errors"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
"github.com/fatedier/frp/server/proxy"
|
||||
"github.com/fatedier/frp/server/stats"
|
||||
"github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
|
||||
@@ -34,9 +37,56 @@ import (
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type ControlManager struct {
|
||||
// controls indexed by run id
|
||||
ctlsByRunId map[string]*Control
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewControlManager() *ControlManager {
|
||||
return &ControlManager{
|
||||
ctlsByRunId: make(map[string]*Control),
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
|
||||
oldCtl, ok := cm.ctlsByRunId[runId]
|
||||
if ok {
|
||||
oldCtl.Replaced(ctl)
|
||||
}
|
||||
cm.ctlsByRunId[runId] = ctl
|
||||
return
|
||||
}
|
||||
|
||||
// we should make sure if it's the same control to prevent delete a new one
|
||||
func (cm *ControlManager) Del(runId string, ctl *Control) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
if c, ok := cm.ctlsByRunId[runId]; ok && c == ctl {
|
||||
delete(cm.ctlsByRunId, runId)
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
ctl, ok = cm.ctlsByRunId[runId]
|
||||
return
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
// frps service
|
||||
svr *Service
|
||||
// all resource managers and controllers
|
||||
rc *controller.ResourceController
|
||||
|
||||
// proxy manager
|
||||
pxyManager *proxy.ProxyManager
|
||||
|
||||
// stats collector to store stats info of clients and proxies
|
||||
statsCollector stats.Collector
|
||||
|
||||
// login message
|
||||
loginMsg *msg.Login
|
||||
@@ -54,7 +104,7 @@ type Control struct {
|
||||
workConnCh chan net.Conn
|
||||
|
||||
// proxies in one client
|
||||
proxies map[string]Proxy
|
||||
proxies map[string]proxy.Proxy
|
||||
|
||||
// pool count
|
||||
poolCount int
|
||||
@@ -81,15 +131,19 @@ type Control struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
||||
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
|
||||
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
||||
|
||||
return &Control{
|
||||
svr: svr,
|
||||
rc: rc,
|
||||
pxyManager: pxyManager,
|
||||
statsCollector: statsCollector,
|
||||
conn: ctlConn,
|
||||
loginMsg: loginMsg,
|
||||
sendCh: make(chan msg.Message, 10),
|
||||
readCh: make(chan msg.Message, 10),
|
||||
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
||||
proxies: make(map[string]Proxy),
|
||||
proxies: make(map[string]proxy.Proxy),
|
||||
poolCount: loginMsg.PoolCount,
|
||||
portsUsedNum: 0,
|
||||
lastPing: time.Now(),
|
||||
@@ -247,6 +301,7 @@ func (ctl *Control) reader() {
|
||||
return
|
||||
} else {
|
||||
ctl.conn.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -284,14 +339,22 @@ func (ctl *Control) stoper() {
|
||||
|
||||
for _, pxy := range ctl.proxies {
|
||||
pxy.Close()
|
||||
ctl.svr.DelProxy(pxy.GetName())
|
||||
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
ctl.pxyManager.Del(pxy.GetName())
|
||||
ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{
|
||||
Name: pxy.GetName(),
|
||||
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
||||
})
|
||||
}
|
||||
|
||||
ctl.allShutdown.Done()
|
||||
ctl.conn.Info("client exit success")
|
||||
|
||||
StatsCloseClient()
|
||||
ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{})
|
||||
}
|
||||
|
||||
// block until Control closed
|
||||
func (ctl *Control) WaitClosed() {
|
||||
ctl.allShutdown.WaitDone()
|
||||
}
|
||||
|
||||
func (ctl *Control) manager() {
|
||||
@@ -333,7 +396,10 @@ func (ctl *Control) manager() {
|
||||
} else {
|
||||
resp.RemoteAddr = remoteAddr
|
||||
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
|
||||
StatsNewProxy(m.ProxyName, m.ProxyType)
|
||||
ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{
|
||||
Name: m.ProxyName,
|
||||
ProxyType: m.ProxyType,
|
||||
})
|
||||
}
|
||||
ctl.sendCh <- resp
|
||||
case *msg.CloseProxy:
|
||||
@@ -358,7 +424,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||
|
||||
// NewProxy will return a interface Proxy.
|
||||
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||
pxy, err := NewProxy(ctl, pxyConf)
|
||||
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
|
||||
if err != nil {
|
||||
return remoteAddr, err
|
||||
}
|
||||
@@ -393,7 +459,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||
}
|
||||
}()
|
||||
|
||||
err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
|
||||
err = ctl.pxyManager.Add(pxyMsg.ProxyName, pxy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -406,7 +472,6 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||
|
||||
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
ctl.mu.Lock()
|
||||
|
||||
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
||||
if !ok {
|
||||
ctl.mu.Unlock()
|
||||
@@ -417,10 +482,13 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||
}
|
||||
pxy.Close()
|
||||
ctl.svr.DelProxy(pxy.GetName())
|
||||
ctl.pxyManager.Del(pxy.GetName())
|
||||
delete(ctl.proxies, closeMsg.ProxyName)
|
||||
ctl.mu.Unlock()
|
||||
|
||||
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{
|
||||
Name: pxy.GetName(),
|
||||
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
46
server/controller/resource.go
Normal file
46
server/controller/resource.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/models/nathole"
|
||||
"github.com/fatedier/frp/server/group"
|
||||
"github.com/fatedier/frp/server/ports"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
)
|
||||
|
||||
// All resource managers and controllers
|
||||
type ResourceController struct {
|
||||
// Manage all visitor listeners
|
||||
VisitorManager *VisitorManager
|
||||
|
||||
// Tcp Group Controller
|
||||
TcpGroupCtl *group.TcpGroupCtl
|
||||
|
||||
// Manage all tcp ports
|
||||
TcpPortManager *ports.PortManager
|
||||
|
||||
// Manage all udp ports
|
||||
UdpPortManager *ports.PortManager
|
||||
|
||||
// For http proxies, forwarding http requests
|
||||
HttpReverseProxy *vhost.HttpReverseProxy
|
||||
|
||||
// For https proxies, route requests to different clients by hostname and other infomation
|
||||
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||
|
||||
// Controller for nat hole connections
|
||||
NatHoleController *nathole.NatHoleController
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 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,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -25,75 +25,6 @@ import (
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
)
|
||||
|
||||
type ControlManager struct {
|
||||
// controls indexed by run id
|
||||
ctlsByRunId map[string]*Control
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewControlManager() *ControlManager {
|
||||
return &ControlManager{
|
||||
ctlsByRunId: make(map[string]*Control),
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
|
||||
oldCtl, ok := cm.ctlsByRunId[runId]
|
||||
if ok {
|
||||
oldCtl.Replaced(ctl)
|
||||
}
|
||||
cm.ctlsByRunId[runId] = ctl
|
||||
return
|
||||
}
|
||||
|
||||
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
ctl, ok = cm.ctlsByRunId[runId]
|
||||
return
|
||||
}
|
||||
|
||||
type ProxyManager struct {
|
||||
// proxies indexed by proxy name
|
||||
pxys map[string]Proxy
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewProxyManager() *ProxyManager {
|
||||
return &ProxyManager{
|
||||
pxys: make(map[string]Proxy),
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
if _, ok := pm.pxys[name]; ok {
|
||||
return fmt.Errorf("proxy name [%s] is already in use", name)
|
||||
}
|
||||
|
||||
pm.pxys[name] = pxy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Del(name string) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
delete(pm.pxys, name)
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
pxy, ok = pm.pxys[name]
|
||||
return
|
||||
}
|
||||
|
||||
// Manager for visitor listeners.
|
||||
type VisitorManager struct {
|
||||
visitorListeners map[string]*frpNet.CustomListener
|
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -32,32 +32,26 @@ var (
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func RunDashboardServer(addr string, port int) (err error) {
|
||||
func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
||||
// url router
|
||||
router := httprouter.New()
|
||||
router := mux.NewRouter()
|
||||
|
||||
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
|
||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// api, see dashboard_api.go
|
||||
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd))
|
||||
router.GET("/api/proxy/tcp/:name", frpNet.HttprouterBasicAuth(apiProxyTcpByName, user, passwd))
|
||||
router.GET("/api/proxy/udp/:name", frpNet.HttprouterBasicAuth(apiProxyUdpByName, user, passwd))
|
||||
router.GET("/api/proxy/http/:name", frpNet.HttprouterBasicAuth(apiProxyHttpByName, user, passwd))
|
||||
router.GET("/api/proxy/https/:name", frpNet.HttprouterBasicAuth(apiProxyHttpsByName, user, passwd))
|
||||
router.GET("/api/proxy/tcp", frpNet.HttprouterBasicAuth(apiProxyTcp, user, passwd))
|
||||
router.GET("/api/proxy/udp", frpNet.HttprouterBasicAuth(apiProxyUdp, user, passwd))
|
||||
router.GET("/api/proxy/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd))
|
||||
router.GET("/api/proxy/https", frpNet.HttprouterBasicAuth(apiProxyHttps, user, passwd))
|
||||
router.GET("/api/proxy/traffic/:name", frpNet.HttprouterBasicAuth(apiProxyTraffic, user, passwd))
|
||||
router.HandleFunc("/api/serverinfo", svr.ApiServerInfo).Methods("GET")
|
||||
router.HandleFunc("/api/proxy/{type}", svr.ApiProxyByType).Methods("GET")
|
||||
router.HandleFunc("/api/proxy/{type}/{name}", svr.ApiProxyByTypeAndName).Methods("GET")
|
||||
router.HandleFunc("/api/traffic/{name}", svr.ApiProxyTraffic).Methods("GET")
|
||||
|
||||
// view
|
||||
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
|
||||
router.Handler("GET", "/static/*filepath", frpNet.MakeHttpGzipHandler(
|
||||
frpNet.NewHttpBasicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)), user, passwd)))
|
||||
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.HandlerFunc("GET", "/", frpNet.HttpBasicAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
}, user, passwd))
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:%d", addr, port)
|
||||
server := &http.Server{
|
||||
|
@@ -24,25 +24,21 @@ import (
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
// api/serverinfo
|
||||
type ServerInfoResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUdpPort int `json:"bind_udp_port"`
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
VhostHttpsPort int `json:"vhost_https_port"`
|
||||
KcpBindPort int `json:"kcp_bind_port"`
|
||||
AuthTimeout int64 `json:"auth_timeout"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
@@ -55,26 +51,27 @@ type ServerInfoResp struct {
|
||||
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
|
||||
}
|
||||
|
||||
func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res ServerInfoResp
|
||||
)
|
||||
// api/serverinfo
|
||||
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Info("Http response [/api/serverinfo]: code [%d]", res.Code)
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/serverinfo]")
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
serverStats := StatsGetServer()
|
||||
res = ServerInfoResp{
|
||||
serverStats := svr.statsCollector.GetServer()
|
||||
svrResp := ServerInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: cfg.BindPort,
|
||||
BindUdpPort: cfg.BindUdpPort,
|
||||
VhostHttpPort: cfg.VhostHttpPort,
|
||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
||||
KcpBindPort: cfg.KcpBindPort,
|
||||
AuthTimeout: cfg.AuthTimeout,
|
||||
SubdomainHost: cfg.SubDomainHost,
|
||||
MaxPoolCount: cfg.MaxPoolCount,
|
||||
MaxPortsPerClient: cfg.MaxPortsPerClient,
|
||||
@@ -87,102 +84,117 @@ func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
|
||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&svrResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
type BaseOutConf struct {
|
||||
config.BaseProxyConf
|
||||
}
|
||||
|
||||
type TcpOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remote_port"`
|
||||
}
|
||||
|
||||
type UdpOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remote_port"`
|
||||
}
|
||||
|
||||
type HttpOutConf struct {
|
||||
BaseOutConf
|
||||
config.DomainConf
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
}
|
||||
|
||||
type HttpsOutConf struct {
|
||||
BaseOutConf
|
||||
config.DomainConf
|
||||
}
|
||||
|
||||
type StcpOutConf struct {
|
||||
BaseOutConf
|
||||
}
|
||||
|
||||
type XtcpOutConf struct {
|
||||
BaseOutConf
|
||||
}
|
||||
|
||||
func getConfByType(proxyType string) interface{} {
|
||||
switch proxyType {
|
||||
case consts.TcpProxy:
|
||||
return &TcpOutConf{}
|
||||
case consts.UdpProxy:
|
||||
return &UdpOutConf{}
|
||||
case consts.HttpProxy:
|
||||
return &HttpOutConf{}
|
||||
case consts.HttpsProxy:
|
||||
return &HttpsOutConf{}
|
||||
case consts.StcpProxy:
|
||||
return &StcpOutConf{}
|
||||
case consts.XtcpProxy:
|
||||
return &XtcpOutConf{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get proxy info.
|
||||
type ProxyStatsInfo struct {
|
||||
Name string `json:"name"`
|
||||
Conf config.ProxyConf `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Conf interface{} `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type GetProxyInfoResp struct {
|
||||
GeneralResponse
|
||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||
}
|
||||
|
||||
// api/proxy/tcp
|
||||
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
// api/proxy/:type
|
||||
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/tcp]: code [%d]", res.Code)
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/tcp]")
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.TcpProxy)
|
||||
proxyInfoResp := GetProxyInfoResp{}
|
||||
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&proxyInfoResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// api/proxy/udp
|
||||
func apiProxyUdp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/udp]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/udp]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.UdpProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/http
|
||||
func apiProxyHttp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/http]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/http]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.HttpProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/https
|
||||
func apiProxyHttps(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/https]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/https]")
|
||||
|
||||
res.Proxies = getProxyStatsByType(consts.HttpsProxy)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
proxyStats := StatsGetProxiesByType(proxyType)
|
||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
proxyStats := svr.statsCollector.GetProxiesByType(proxyType)
|
||||
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||
for _, ps := range proxyStats {
|
||||
proxyInfo := &ProxyStatsInfo{}
|
||||
if pxy, ok := ServerService.pxyManager.GetByName(ps.Name); ok {
|
||||
proxyInfo.Conf = pxy.GetConf()
|
||||
if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
|
||||
content, err := json.Marshal(pxy.GetConf())
|
||||
if err != nil {
|
||||
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
continue
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
continue
|
||||
}
|
||||
proxyInfo.Status = consts.Online
|
||||
} else {
|
||||
proxyInfo.Status = consts.Offline
|
||||
@@ -200,103 +212,64 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
|
||||
// Get proxy info by name.
|
||||
type GetProxyStatsResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Name string `json:"name"`
|
||||
Conf config.ProxyConf `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Conf interface{} `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// api/proxy/tcp/:name
|
||||
func apiProxyTcpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyStatsResp
|
||||
)
|
||||
name := params.ByName("name")
|
||||
// api/proxy/:type/:name
|
||||
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/tcp/:name]: code [%d]", res.Code)
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/tcp/:name]")
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
|
||||
res = getProxyStatsByTypeAndName(consts.TcpProxy, name)
|
||||
proxyStatsResp := GetProxyStatsResp{}
|
||||
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||
if res.Code != 200 {
|
||||
return
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&proxyStatsResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// api/proxy/udp/:name
|
||||
func apiProxyUdpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyStatsResp
|
||||
)
|
||||
name := params.ByName("name")
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/udp/:name]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/udp/:name]")
|
||||
|
||||
res = getProxyStatsByTypeAndName(consts.UdpProxy, name)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/http/:name
|
||||
func apiProxyHttpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyStatsResp
|
||||
)
|
||||
name := params.ByName("name")
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/http/:name]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/http/:name]")
|
||||
|
||||
res = getProxyStatsByTypeAndName(consts.HttpProxy, name)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// api/proxy/https/:name
|
||||
func apiProxyHttpsByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyStatsResp
|
||||
)
|
||||
name := params.ByName("name")
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/https/:name]: code [%d]", res.Code)
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/https/:name]")
|
||||
|
||||
res = getProxyStatsByTypeAndName(consts.HttpsProxy, name)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) {
|
||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||
proxyInfo.Name = proxyName
|
||||
ps := StatsGetProxiesByTypeAndName(proxyType, proxyName)
|
||||
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||
if ps == nil {
|
||||
proxyInfo.Code = 1
|
||||
proxyInfo.Msg = "no proxy info found"
|
||||
code = 404
|
||||
msg = "no proxy info found"
|
||||
} else {
|
||||
if pxy, ok := ServerService.pxyManager.GetByName(proxyName); ok {
|
||||
proxyInfo.Conf = pxy.GetConf()
|
||||
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
||||
content, err := json.Marshal(pxy.GetConf())
|
||||
if err != nil {
|
||||
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Status = consts.Online
|
||||
} else {
|
||||
proxyInfo.Status = consts.Offline
|
||||
@@ -306,42 +279,46 @@ func getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo G
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
code = 200
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// api/proxy/traffic/:name
|
||||
// api/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Name string `json:"name"`
|
||||
TrafficIn []int64 `json:"traffic_in"`
|
||||
TrafficOut []int64 `json:"traffic_out"`
|
||||
}
|
||||
|
||||
func apiProxyTraffic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyTrafficResp
|
||||
)
|
||||
name := params.ByName("name")
|
||||
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [/api/proxy/traffic/:name]: code [%d]", res.Code)
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Info("Http request: [/api/proxy/traffic/:name]")
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
|
||||
trafficResp := GetProxyTrafficResp{}
|
||||
trafficResp.Name = name
|
||||
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
||||
|
||||
res.Name = name
|
||||
proxyTrafficInfo := StatsGetProxyTraffic(name)
|
||||
if proxyTrafficInfo == nil {
|
||||
res.Code = 1
|
||||
res.Code = 404
|
||||
res.Msg = "no proxy info found"
|
||||
return
|
||||
} else {
|
||||
res.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
res.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&trafficResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
26
server/group/group.go
Normal file
26
server/group/group.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 group
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrGroupAuthFailed = errors.New("group auth failed")
|
||||
ErrGroupParamsInvalid = errors.New("group params invalid")
|
||||
ErrListenerClosed = errors.New("group listener closed")
|
||||
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
||||
)
|
204
server/group/tcp.go
Normal file
204
server/group/tcp.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// 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 group
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/server/ports"
|
||||
|
||||
gerr "github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type TcpGroupListener struct {
|
||||
groupName string
|
||||
group *TcpGroup
|
||||
|
||||
addr net.Addr
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
||||
return &TcpGroupListener{
|
||||
groupName: name,
|
||||
group: group,
|
||||
addr: addr,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case <-ln.closeCh:
|
||||
return nil, ErrListenerClosed
|
||||
case c, ok = <-ln.group.Accept():
|
||||
if !ok {
|
||||
return nil, ErrListenerClosed
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ln *TcpGroupListener) Addr() net.Addr {
|
||||
return ln.addr
|
||||
}
|
||||
|
||||
func (ln *TcpGroupListener) Close() (err error) {
|
||||
close(ln.closeCh)
|
||||
ln.group.CloseListener(ln)
|
||||
return
|
||||
}
|
||||
|
||||
type TcpGroup struct {
|
||||
group string
|
||||
groupKey string
|
||||
addr string
|
||||
port int
|
||||
realPort int
|
||||
|
||||
acceptCh chan net.Conn
|
||||
index uint64
|
||||
tcpLn net.Listener
|
||||
lns []*TcpGroupListener
|
||||
ctl *TcpGroupCtl
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
||||
return &TcpGroup{
|
||||
lns: make([]*TcpGroupListener, 0),
|
||||
ctl: ctl,
|
||||
acceptCh: make(chan net.Conn),
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
||||
tg.mu.Lock()
|
||||
defer tg.mu.Unlock()
|
||||
if len(tg.lns) == 0 {
|
||||
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
ln = newTcpGroupListener(group, tg, tcpLn.Addr())
|
||||
|
||||
tg.group = group
|
||||
tg.groupKey = groupKey
|
||||
tg.addr = addr
|
||||
tg.port = port
|
||||
tg.realPort = realPort
|
||||
tg.tcpLn = tcpLn
|
||||
tg.lns = append(tg.lns, ln)
|
||||
if tg.acceptCh == nil {
|
||||
tg.acceptCh = make(chan net.Conn)
|
||||
}
|
||||
go tg.worker()
|
||||
} else {
|
||||
if tg.group != group || tg.addr != addr {
|
||||
err = ErrGroupParamsInvalid
|
||||
return
|
||||
}
|
||||
if tg.port != port {
|
||||
err = ErrGroupDifferentPort
|
||||
return
|
||||
}
|
||||
if tg.groupKey != groupKey {
|
||||
err = ErrGroupAuthFailed
|
||||
return
|
||||
}
|
||||
ln = newTcpGroupListener(group, tg, tg.lns[0].Addr())
|
||||
realPort = tg.realPort
|
||||
tg.lns = append(tg.lns, ln)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tg *TcpGroup) worker() {
|
||||
for {
|
||||
c, err := tg.tcpLn.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = gerr.PanicToError(func() {
|
||||
tg.acceptCh <- c
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *TcpGroup) Accept() <-chan net.Conn {
|
||||
return tg.acceptCh
|
||||
}
|
||||
|
||||
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
||||
tg.mu.Lock()
|
||||
defer tg.mu.Unlock()
|
||||
for i, tmpLn := range tg.lns {
|
||||
if tmpLn == ln {
|
||||
tg.lns = append(tg.lns[:i], tg.lns[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(tg.lns) == 0 {
|
||||
close(tg.acceptCh)
|
||||
tg.tcpLn.Close()
|
||||
tg.ctl.portManager.Release(tg.realPort)
|
||||
tg.ctl.RemoveGroup(tg.group)
|
||||
}
|
||||
}
|
||||
|
||||
type TcpGroupCtl struct {
|
||||
groups map[string]*TcpGroup
|
||||
|
||||
portManager *ports.PortManager
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
||||
return &TcpGroupCtl{
|
||||
groups: make(map[string]*TcpGroup),
|
||||
portManager: portManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string,
|
||||
addr string, port int) (l net.Listener, realPort int, err error) {
|
||||
|
||||
tgc.mu.Lock()
|
||||
defer tgc.mu.Unlock()
|
||||
if tcpGroup, ok := tgc.groups[group]; ok {
|
||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
||||
} else {
|
||||
tcpGroup = NewTcpGroup(tgc)
|
||||
tgc.groups[group] = tcpGroup
|
||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
||||
}
|
||||
}
|
||||
|
||||
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
||||
tgc.mu.Lock()
|
||||
defer tgc.mu.Unlock()
|
||||
delete(tgc.groups, group)
|
||||
}
|
316
server/metric.go
316
server/metric.go
@@ -1,316 +0,0 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/metric"
|
||||
)
|
||||
|
||||
const (
|
||||
ReserveDays = 7
|
||||
)
|
||||
|
||||
var globalStats *ServerStatistics
|
||||
|
||||
type ServerStatistics struct {
|
||||
TotalTrafficIn metric.DateCounter
|
||||
TotalTrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
|
||||
// counter for clients
|
||||
ClientCounts metric.Counter
|
||||
|
||||
// counter for proxy types
|
||||
ProxyTypeCounts map[string]metric.Counter
|
||||
|
||||
// statistics for different proxies
|
||||
// key is proxy name
|
||||
ProxyStatistics map[string]*ProxyStatistics
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type ProxyStatistics struct {
|
||||
Name string
|
||||
ProxyType string
|
||||
TrafficIn metric.DateCounter
|
||||
TrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
LastStartTime time.Time
|
||||
LastCloseTime time.Time
|
||||
}
|
||||
|
||||
func init() {
|
||||
globalStats = &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),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(12 * time.Hour)
|
||||
log.Debug("start to clear useless proxy statistics data...")
|
||||
StatsClearUselessInfo()
|
||||
log.Debug("finish to clear useless proxy statistics data")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StatsClearUselessInfo() {
|
||||
// To check if there are proxies that closed than 7 days and drop them.
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
for name, data := range globalStats.ProxyStatistics {
|
||||
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||
delete(globalStats.ProxyStatistics, name)
|
||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsNewClient() {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.ClientCounts.Inc(1)
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseClient() {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.ClientCounts.Dec(1)
|
||||
}
|
||||
}
|
||||
|
||||
func StatsNewProxy(name string, proxyType string) {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
counter, ok := globalStats.ProxyTypeCounts[proxyType]
|
||||
if !ok {
|
||||
counter = metric.NewCounter()
|
||||
}
|
||||
counter.Inc(1)
|
||||
globalStats.ProxyTypeCounts[proxyType] = counter
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if !(ok && proxyStats.ProxyType == proxyType) {
|
||||
proxyStats = &ProxyStatistics{
|
||||
Name: name,
|
||||
ProxyType: proxyType,
|
||||
CurConns: metric.NewCounter(),
|
||||
TrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
}
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
proxyStats.LastStartTime = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseProxy(proxyName string, proxyType string) {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
|
||||
counter.Dec(1)
|
||||
}
|
||||
if proxyStats, ok := globalStats.ProxyStatistics[proxyName]; ok {
|
||||
proxyStats.LastCloseTime = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsOpenConnection(name string) {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.CurConns.Inc(1)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.CurConns.Inc(1)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseConnection(name string) {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.CurConns.Dec(1)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.CurConns.Dec(1)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsAddTrafficIn(name string, trafficIn int64) {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.TotalTrafficIn.Inc(trafficIn)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.TrafficIn.Inc(trafficIn)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StatsAddTrafficOut(name string, trafficOut int64) {
|
||||
if g.GlbServerCfg.DashboardPort != 0 {
|
||||
globalStats.TotalTrafficOut.Inc(trafficOut)
|
||||
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
proxyStats.TrafficOut.Inc(trafficOut)
|
||||
globalStats.ProxyStatistics[name] = proxyStats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Functions for getting server stats.
|
||||
type ServerStats struct {
|
||||
TotalTrafficIn int64
|
||||
TotalTrafficOut int64
|
||||
CurConns int64
|
||||
ClientCounts int64
|
||||
ProxyTypeCounts map[string]int64
|
||||
}
|
||||
|
||||
func StatsGetServer() *ServerStats {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
s := &ServerStats{
|
||||
TotalTrafficIn: globalStats.TotalTrafficIn.TodayCount(),
|
||||
TotalTrafficOut: globalStats.TotalTrafficOut.TodayCount(),
|
||||
CurConns: globalStats.CurConns.Count(),
|
||||
ClientCounts: globalStats.ClientCounts.Count(),
|
||||
ProxyTypeCounts: make(map[string]int64),
|
||||
}
|
||||
for k, v := range globalStats.ProxyTypeCounts {
|
||||
s.ProxyTypeCounts[k] = v.Count()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type ProxyStats struct {
|
||||
Name string
|
||||
Type string
|
||||
TodayTrafficIn int64
|
||||
TodayTrafficOut int64
|
||||
LastStartTime string
|
||||
LastCloseTime string
|
||||
CurConns int64
|
||||
}
|
||||
|
||||
func StatsGetProxiesByType(proxyType string) []*ProxyStats {
|
||||
res := make([]*ProxyStats, 0)
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range globalStats.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
ps := &ProxyStats{
|
||||
Name: name,
|
||||
Type: proxyStats.ProxyType,
|
||||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||
CurConns: 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 StatsGetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range globalStats.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: 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
|
||||
}
|
||||
|
||||
type ProxyTrafficInfo struct {
|
||||
Name string
|
||||
TrafficIn []int64
|
||||
TrafficOut []int64
|
||||
}
|
||||
|
||||
func StatsGetProxyTraffic(name string) (res *ProxyTrafficInfo) {
|
||||
globalStats.mu.Lock()
|
||||
defer globalStats.mu.Unlock()
|
||||
|
||||
proxyStats, ok := globalStats.ProxyStatistics[name]
|
||||
if ok {
|
||||
res = &ProxyTrafficInfo{
|
||||
Name: name,
|
||||
}
|
||||
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
|
||||
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
|
||||
}
|
||||
return
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package ports
|
||||
|
||||
import (
|
||||
"errors"
|
667
server/proxy.go
667
server/proxy.go
@@ -1,667 +0,0 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"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/proto/udp"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
Run() (remoteAddr string, err error)
|
||||
GetControl() *Control
|
||||
GetName() string
|
||||
GetConf() config.ProxyConf
|
||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
||||
GetUsedPortsNum() int
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
name string
|
||||
ctl *Control
|
||||
listeners []frpNet.Listener
|
||||
usedPortsNum int
|
||||
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetName() string {
|
||||
return pxy.name
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetControl() *Control {
|
||||
return pxy.ctl
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetUsedPortsNum() int {
|
||||
return pxy.usedPortsNum
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) Close() {
|
||||
pxy.Info("proxy closing")
|
||||
for _, l := range pxy.listeners {
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
||||
ctl := pxy.GetControl()
|
||||
// try all connections from the pool
|
||||
for i := 0; i < ctl.poolCount+1; i++ {
|
||||
if workConn, err = ctl.GetWorkConn(); err != nil {
|
||||
pxy.Warn("failed to get work connection: %v", err)
|
||||
return
|
||||
}
|
||||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||
workConn.AddLogPrefix(pxy.GetName())
|
||||
|
||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||
ProxyName: pxy.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||
workConn.Close()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
pxy.Error("try to get work connection failed in the end")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// startListenHandler start a goroutine handler for each listener.
|
||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
||||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn)) {
|
||||
for _, listener := range pxy.listeners {
|
||||
go func(l frpNet.Listener) {
|
||||
for {
|
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
pxy.Info("listener is closed")
|
||||
return
|
||||
}
|
||||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||
go handler(p, c)
|
||||
}
|
||||
}(listener)
|
||||
}
|
||||
}
|
||||
|
||||
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
||||
basePxy := BaseProxy{
|
||||
name: pxyConf.GetBaseInfo().ProxyName,
|
||||
ctl: ctl,
|
||||
listeners: make([]frpNet.Listener, 0),
|
||||
Logger: log.NewPrefixLogger(ctl.runId),
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
basePxy.usedPortsNum = 1
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
basePxy.usedPortsNum = 1
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.StcpProxyConf:
|
||||
pxy = &StcpProxy{
|
||||
BaseProxy: basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
pxy = &XtcpProxy{
|
||||
BaseProxy: basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
default:
|
||||
return pxy, fmt.Errorf("proxy type not support")
|
||||
}
|
||||
pxy.AddLogPrefix(pxy.GetName())
|
||||
return
|
||||
}
|
||||
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.TcpProxyConf
|
||||
|
||||
realPort int
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||
pxy.realPort, err = pxy.ctl.svr.tcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
listener.AddLogPrefix(pxy.name)
|
||||
pxy.listeners = append(pxy.listeners, listener)
|
||||
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.HttpProxyConf
|
||||
|
||||
closeFuncs []func()
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||
routeConfig := vhost.VhostRouteConfig{
|
||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||
Username: pxy.cfg.HttpUser,
|
||||
Password: pxy.cfg.HttpPwd,
|
||||
CreateConnFn: pxy.GetRealConn,
|
||||
}
|
||||
|
||||
locations := pxy.cfg.Locations
|
||||
if len(locations) == 0 {
|
||||
locations = []string{""}
|
||||
}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
routeConfig.Domain = domain
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpDomain := routeConfig.Domain
|
||||
tmpLocation := routeConfig.Location
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
}
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpDomain := routeConfig.Domain
|
||||
tmpLocation := routeConfig.Location
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
}
|
||||
}
|
||||
remoteAddr = strings.Join(addrs, ",")
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
|
||||
tmpConn, errRet := pxy.GetWorkConnFromPool()
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
var rwc io.ReadWriteCloser = tmpConn
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||
StatsOpenConnection(pxy.GetName())
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
||||
name := pxy.GetName()
|
||||
StatsCloseConnection(name)
|
||||
StatsAddTrafficIn(name, totalWrite)
|
||||
StatsAddTrafficOut(name, totalRead)
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
for _, closeFn := range pxy.closeFuncs {
|
||||
closeFn()
|
||||
}
|
||||
}
|
||||
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.HttpsProxyConf
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||
routeConfig := &vhost.VhostRouteConfig{}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
routeConfig.Domain = domain
|
||||
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
|
||||
}
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
remoteAddr = strings.Join(addrs, ",")
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
}
|
||||
|
||||
type StcpProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.StcpProxyConf
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
|
||||
listener, errRet := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
listener.AddLogPrefix(pxy.name)
|
||||
pxy.listeners = append(pxy.listeners, listener)
|
||||
pxy.Info("stcp proxy custom listen success")
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
pxy.ctl.svr.visitorManager.CloseListener(pxy.GetName())
|
||||
}
|
||||
|
||||
type XtcpProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.XtcpProxyConf
|
||||
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
||||
if pxy.ctl.svr.natHoleController == nil {
|
||||
pxy.Error("udp port for xtcp is not specified.")
|
||||
err = fmt.Errorf("xtcp is not supported in frps")
|
||||
return
|
||||
}
|
||||
sidCh := pxy.ctl.svr.natHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
break
|
||||
case sid := <-sidCh:
|
||||
workConn, errRet := pxy.GetWorkConnFromPool()
|
||||
if errRet != nil {
|
||||
continue
|
||||
}
|
||||
m := &msg.NatHoleSid{
|
||||
Sid: sid,
|
||||
}
|
||||
errRet = msg.WriteMsg(workConn, m)
|
||||
if errRet != nil {
|
||||
pxy.Warn("write nat hole sid package error, %v", errRet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
pxy.ctl.svr.natHoleController.CloseClient(pxy.GetName())
|
||||
errors.PanicToError(func() {
|
||||
close(pxy.closeCh)
|
||||
})
|
||||
}
|
||||
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
realPort int
|
||||
|
||||
// udpConn is the listener of udp packages
|
||||
udpConn *net.UDPConn
|
||||
|
||||
// there are always only one workConn at the same time
|
||||
// get another one if it closed
|
||||
workConn net.Conn
|
||||
|
||||
// sendCh is used for sending packages to workConn
|
||||
sendCh chan *msg.UdpPacket
|
||||
|
||||
// readCh is used for reading packages from workConn
|
||||
readCh chan *msg.UdpPacket
|
||||
|
||||
// checkCloseCh is used for watching if workConn is closed
|
||||
checkCloseCh chan int
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
||||
pxy.realPort, err = pxy.ctl.svr.udpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pxy.ctl.svr.udpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
udpConn, errRet := net.ListenUDP("udp", addr)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
pxy.Warn("listen udp port error: %v", err)
|
||||
return
|
||||
}
|
||||
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||
|
||||
pxy.udpConn = udpConn
|
||||
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.checkCloseCh = make(chan int)
|
||||
|
||||
// read message from workConn, if it returns any error, notify proxy to start a new workConn
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
pxy.Trace("loop waiting message from udp workConn")
|
||||
// client will send heartbeat in workConn for keeping alive
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||
conn.Close()
|
||||
// notify proxy to start a new work connection
|
||||
// ignore error here, it means the proxy is closed
|
||||
errors.PanicToError(func() {
|
||||
pxy.checkCloseCh <- 1
|
||||
})
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
pxy.Trace("udp work conn get ping message")
|
||||
continue
|
||||
case *msg.UdpPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
pxy.Trace("get udp message from workConn: %s", m.Content)
|
||||
pxy.readCh <- m
|
||||
StatsAddTrafficOut(pxy.GetName(), int64(len(m.Content)))
|
||||
}); errRet != nil {
|
||||
conn.Close()
|
||||
pxy.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send message to workConn
|
||||
workConnSenderFn := func(conn net.Conn, ctx context.Context) {
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-pxy.sendCh:
|
||||
if !ok {
|
||||
pxy.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
|
||||
conn.Close()
|
||||
return
|
||||
} else {
|
||||
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
|
||||
StatsAddTrafficIn(pxy.GetName(), int64(len(udpMsg.Content)))
|
||||
continue
|
||||
}
|
||||
case <-ctx.Done():
|
||||
pxy.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
for {
|
||||
workConn, err := pxy.GetWorkConnFromPool()
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
// check if proxy is closed
|
||||
select {
|
||||
case _, ok := <-pxy.checkCloseCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
// close the old workConn and replac it with a new one
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
pxy.workConn = workConn
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn, ctx)
|
||||
_, ok := <-pxy.checkCloseCh
|
||||
cancel()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
|
||||
// Client will transfor udp message to local udp service and waiting for response for a while.
|
||||
// Response will be wrapped to be forwarded by work connection to server.
|
||||
// Close readCh and sendCh at the end.
|
||||
go func() {
|
||||
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
|
||||
pxy.Close()
|
||||
}()
|
||||
return remoteAddr, nil
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
if !pxy.isClosed {
|
||||
pxy.isClosed = true
|
||||
|
||||
pxy.BaseProxy.Close()
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
pxy.udpConn.Close()
|
||||
|
||||
// all channels only closed here
|
||||
close(pxy.checkCloseCh)
|
||||
close(pxy.readCh)
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
pxy.ctl.svr.udpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
|
||||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||
// It can be used for tcp, http, https type.
|
||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
|
||||
defer userConn.Close()
|
||||
|
||||
// try all connections from the pool
|
||||
workConn, err := pxy.GetWorkConnFromPool()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer workConn.Close()
|
||||
|
||||
var local io.ReadWriteCloser = workConn
|
||||
cfg := pxy.GetConf().GetBaseInfo()
|
||||
if cfg.UseEncryption {
|
||||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if cfg.UseCompression {
|
||||
local = frpIo.WithCompression(local)
|
||||
}
|
||||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
||||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||
|
||||
StatsOpenConnection(pxy.GetName())
|
||||
inCount, outCount := frpIo.Join(local, userConn)
|
||||
StatsCloseConnection(pxy.GetName())
|
||||
StatsAddTrafficIn(pxy.GetName(), inCount)
|
||||
StatsAddTrafficOut(pxy.GetName(), outCount)
|
||||
pxy.Debug("join connections closed")
|
||||
}
|
149
server/proxy/http.go
Normal file
149
server/proxy/http.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/server/stats"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
)
|
||||
|
||||
type HttpProxy struct {
|
||||
*BaseProxy
|
||||
cfg *config.HttpProxyConf
|
||||
|
||||
closeFuncs []func()
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||
routeConfig := vhost.VhostRouteConfig{
|
||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||
Headers: pxy.cfg.Headers,
|
||||
Username: pxy.cfg.HttpUser,
|
||||
Password: pxy.cfg.HttpPwd,
|
||||
CreateConnFn: pxy.GetRealConn,
|
||||
}
|
||||
|
||||
locations := pxy.cfg.Locations
|
||||
if len(locations) == 0 {
|
||||
locations = []string{""}
|
||||
}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
if domain == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
routeConfig.Domain = domain
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpDomain := routeConfig.Domain
|
||||
tmpLocation := routeConfig.Location
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
}
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpDomain := routeConfig.Domain
|
||||
tmpLocation := routeConfig.Location
|
||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||
})
|
||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||
}
|
||||
}
|
||||
remoteAddr = strings.Join(addrs, ",")
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
|
||||
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
||||
if errRet != nil {
|
||||
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
||||
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
||||
}
|
||||
|
||||
tmpConn, errRet := pxy.GetWorkConnFromPool(rAddr, nil)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
var rwc io.ReadWriteCloser = tmpConn
|
||||
if pxy.cfg.UseEncryption {
|
||||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.UseCompression {
|
||||
rwc = frpIo.WithCompression(rwc)
|
||||
}
|
||||
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||
pxy.statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
||||
name := pxy.GetName()
|
||||
pxy.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseConnectionPayload{ProxyName: name})
|
||||
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
||||
ProxyName: name,
|
||||
TrafficBytes: totalWrite,
|
||||
})
|
||||
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
||||
ProxyName: name,
|
||||
TrafficBytes: totalRead,
|
||||
})
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
for _, closeFn := range pxy.closeFuncs {
|
||||
closeFn()
|
||||
}
|
||||
}
|
76
server/proxy/https.go
Normal file
76
server/proxy/https.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
)
|
||||
|
||||
type HttpsProxy struct {
|
||||
*BaseProxy
|
||||
cfg *config.HttpsProxyConf
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||
routeConfig := &vhost.VhostRouteConfig{}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
if domain == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
routeConfig.Domain = domain
|
||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
l.AddLogPrefix(pxy.name)
|
||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||
pxy.listeners = append(pxy.listeners, l)
|
||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
|
||||
}
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
remoteAddr = strings.Join(addrs, ",")
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
}
|
273
server/proxy/proxy.go
Normal file
273
server/proxy/proxy.go
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
"github.com/fatedier/frp/server/stats"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
)
|
||||
|
||||
type GetWorkConnFn func() (frpNet.Conn, error)
|
||||
|
||||
type Proxy interface {
|
||||
Run() (remoteAddr string, err error)
|
||||
GetName() string
|
||||
GetConf() config.ProxyConf
|
||||
GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
|
||||
GetUsedPortsNum() int
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
name string
|
||||
rc *controller.ResourceController
|
||||
statsCollector stats.Collector
|
||||
listeners []frpNet.Listener
|
||||
usedPortsNum int
|
||||
poolCount int
|
||||
getWorkConnFn GetWorkConnFn
|
||||
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetName() string {
|
||||
return pxy.name
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetUsedPortsNum() int {
|
||||
return pxy.usedPortsNum
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) Close() {
|
||||
pxy.Info("proxy closing")
|
||||
for _, l := range pxy.listeners {
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
||||
// try all connections from the pool
|
||||
for i := 0; i < pxy.poolCount+1; i++ {
|
||||
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
||||
pxy.Warn("failed to get work connection: %v", err)
|
||||
return
|
||||
}
|
||||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||
workConn.AddLogPrefix(pxy.GetName())
|
||||
|
||||
var (
|
||||
srcAddr string
|
||||
dstAddr string
|
||||
srcPortStr string
|
||||
dstPortStr string
|
||||
srcPort int
|
||||
dstPort int
|
||||
)
|
||||
|
||||
if src != nil {
|
||||
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||
srcPort, _ = strconv.Atoi(srcPortStr)
|
||||
}
|
||||
if dst != nil {
|
||||
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||
dstPort, _ = strconv.Atoi(dstPortStr)
|
||||
}
|
||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||
ProxyName: pxy.GetName(),
|
||||
SrcAddr: srcAddr,
|
||||
SrcPort: uint16(srcPort),
|
||||
DstAddr: dstAddr,
|
||||
DstPort: uint16(dstPort),
|
||||
})
|
||||
if err != nil {
|
||||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||
workConn.Close()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
pxy.Error("try to get work connection failed in the end")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// startListenHandler start a goroutine handler for each listener.
|
||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
||||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) {
|
||||
for _, listener := range pxy.listeners {
|
||||
go func(l frpNet.Listener) {
|
||||
for {
|
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
pxy.Info("listener is closed")
|
||||
return
|
||||
}
|
||||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||
go handler(p, c, pxy.statsCollector)
|
||||
}
|
||||
}(listener)
|
||||
}
|
||||
}
|
||||
|
||||
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
|
||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
||||
|
||||
basePxy := BaseProxy{
|
||||
name: pxyConf.GetBaseInfo().ProxyName,
|
||||
rc: rc,
|
||||
statsCollector: statsCollector,
|
||||
listeners: make([]frpNet.Listener, 0),
|
||||
poolCount: poolCount,
|
||||
getWorkConnFn: getWorkConnFn,
|
||||
Logger: log.NewPrefixLogger(runId),
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
basePxy.usedPortsNum = 1
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
basePxy.usedPortsNum = 1
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.StcpProxyConf:
|
||||
pxy = &StcpProxy{
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
pxy = &XtcpProxy{
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
default:
|
||||
return pxy, fmt.Errorf("proxy type not support")
|
||||
}
|
||||
pxy.AddLogPrefix(pxy.GetName())
|
||||
return
|
||||
}
|
||||
|
||||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||
// It can be used for tcp, http, https type.
|
||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
|
||||
defer userConn.Close()
|
||||
|
||||
// try all connections from the pool
|
||||
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer workConn.Close()
|
||||
|
||||
var local io.ReadWriteCloser = workConn
|
||||
cfg := pxy.GetConf().GetBaseInfo()
|
||||
if cfg.UseEncryption {
|
||||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
|
||||
if err != nil {
|
||||
pxy.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if cfg.UseCompression {
|
||||
local = frpIo.WithCompression(local)
|
||||
}
|
||||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
||||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||
|
||||
statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
|
||||
inCount, outCount := frpIo.Join(local, userConn)
|
||||
statsCollector.Mark(stats.TypeCloseConnection, &stats.CloseConnectionPayload{ProxyName: pxy.GetName()})
|
||||
statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
||||
ProxyName: pxy.GetName(),
|
||||
TrafficBytes: inCount,
|
||||
})
|
||||
statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
||||
ProxyName: pxy.GetName(),
|
||||
TrafficBytes: outCount,
|
||||
})
|
||||
pxy.Debug("join connections closed")
|
||||
}
|
||||
|
||||
type ProxyManager struct {
|
||||
// proxies indexed by proxy name
|
||||
pxys map[string]Proxy
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewProxyManager() *ProxyManager {
|
||||
return &ProxyManager{
|
||||
pxys: make(map[string]Proxy),
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
if _, ok := pm.pxys[name]; ok {
|
||||
return fmt.Errorf("proxy name [%s] is already in use", name)
|
||||
}
|
||||
|
||||
pm.pxys[name] = pxy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Del(name string) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
delete(pm.pxys, name)
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
pxy, ok = pm.pxys[name]
|
||||
return
|
||||
}
|
47
server/proxy/stcp.go
Normal file
47
server/proxy/stcp.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
type StcpProxy struct {
|
||||
*BaseProxy
|
||||
cfg *config.StcpProxyConf
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
|
||||
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
listener.AddLogPrefix(pxy.name)
|
||||
pxy.listeners = append(pxy.listeners, listener)
|
||||
pxy.Info("stcp proxy custom listen success")
|
||||
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
pxy.rc.VisitorManager.CloseListener(pxy.GetName())
|
||||
}
|
84
server/proxy/tcp.go
Normal file
84
server/proxy/tcp.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
type TcpProxy struct {
|
||||
*BaseProxy
|
||||
cfg *config.TcpProxyConf
|
||||
|
||||
realPort int
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||
if pxy.cfg.Group != "" {
|
||||
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
l.Close()
|
||||
}
|
||||
}()
|
||||
pxy.realPort = realPort
|
||||
listener := frpNet.WrapLogListener(l)
|
||||
listener.AddLogPrefix(pxy.name)
|
||||
pxy.listeners = append(pxy.listeners, listener)
|
||||
pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
|
||||
} else {
|
||||
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
listener.AddLogPrefix(pxy.name)
|
||||
pxy.listeners = append(pxy.listeners, listener)
|
||||
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||
}
|
||||
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
if pxy.cfg.Group == "" {
|
||||
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}
|
225
server/proxy/udp.go
Normal file
225
server/proxy/udp.go
Normal file
@@ -0,0 +1,225 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/server/stats"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type UdpProxy struct {
|
||||
*BaseProxy
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
realPort int
|
||||
|
||||
// udpConn is the listener of udp packages
|
||||
udpConn *net.UDPConn
|
||||
|
||||
// there are always only one workConn at the same time
|
||||
// get another one if it closed
|
||||
workConn net.Conn
|
||||
|
||||
// sendCh is used for sending packages to workConn
|
||||
sendCh chan *msg.UdpPacket
|
||||
|
||||
// readCh is used for reading packages from workConn
|
||||
readCh chan *msg.UdpPacket
|
||||
|
||||
// checkCloseCh is used for watching if workConn is closed
|
||||
checkCloseCh chan int
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
||||
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pxy.rc.UdpPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
udpConn, errRet := net.ListenUDP("udp", addr)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
pxy.Warn("listen udp port error: %v", err)
|
||||
return
|
||||
}
|
||||
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||
|
||||
pxy.udpConn = udpConn
|
||||
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.checkCloseCh = make(chan int)
|
||||
|
||||
// read message from workConn, if it returns any error, notify proxy to start a new workConn
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
pxy.Trace("loop waiting message from udp workConn")
|
||||
// client will send heartbeat in workConn for keeping alive
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||
conn.Close()
|
||||
// notify proxy to start a new work connection
|
||||
// ignore error here, it means the proxy is closed
|
||||
errors.PanicToError(func() {
|
||||
pxy.checkCloseCh <- 1
|
||||
})
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
pxy.Trace("udp work conn get ping message")
|
||||
continue
|
||||
case *msg.UdpPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
pxy.Trace("get udp message from workConn: %s", m.Content)
|
||||
pxy.readCh <- m
|
||||
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
||||
ProxyName: pxy.GetName(),
|
||||
TrafficBytes: int64(len(m.Content)),
|
||||
})
|
||||
}); errRet != nil {
|
||||
conn.Close()
|
||||
pxy.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send message to workConn
|
||||
workConnSenderFn := func(conn net.Conn, ctx context.Context) {
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-pxy.sendCh:
|
||||
if !ok {
|
||||
pxy.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
|
||||
conn.Close()
|
||||
return
|
||||
} else {
|
||||
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
|
||||
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
||||
ProxyName: pxy.GetName(),
|
||||
TrafficBytes: int64(len(udpMsg.Content)),
|
||||
})
|
||||
continue
|
||||
}
|
||||
case <-ctx.Done():
|
||||
pxy.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
for {
|
||||
workConn, err := pxy.GetWorkConnFromPool(nil, nil)
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
// check if proxy is closed
|
||||
select {
|
||||
case _, ok := <-pxy.checkCloseCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
// close the old workConn and replac it with a new one
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
pxy.workConn = workConn
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn, ctx)
|
||||
_, ok := <-pxy.checkCloseCh
|
||||
cancel()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
|
||||
// Client will transfor udp message to local udp service and waiting for response for a while.
|
||||
// Response will be wrapped to be forwarded by work connection to server.
|
||||
// Close readCh and sendCh at the end.
|
||||
go func() {
|
||||
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
|
||||
pxy.Close()
|
||||
}()
|
||||
return remoteAddr, nil
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
if !pxy.isClosed {
|
||||
pxy.isClosed = true
|
||||
|
||||
pxy.BaseProxy.Close()
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
pxy.udpConn.Close()
|
||||
|
||||
// all channels only closed here
|
||||
close(pxy.checkCloseCh)
|
||||
close(pxy.readCh)
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
pxy.rc.UdpPortManager.Release(pxy.realPort)
|
||||
}
|
95
server/proxy/xtcp.go
Normal file
95
server/proxy/xtcp.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
type XtcpProxy struct {
|
||||
*BaseProxy
|
||||
cfg *config.XtcpProxyConf
|
||||
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
||||
if pxy.rc.NatHoleController == nil {
|
||||
pxy.Error("udp port for xtcp is not specified.")
|
||||
err = fmt.Errorf("xtcp is not supported in frps")
|
||||
return
|
||||
}
|
||||
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
break
|
||||
case sidRequest := <-sidCh:
|
||||
sr := sidRequest
|
||||
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||
if errRet != nil {
|
||||
continue
|
||||
}
|
||||
m := &msg.NatHoleSid{
|
||||
Sid: sr.Sid,
|
||||
}
|
||||
errRet = msg.WriteMsg(workConn, m)
|
||||
if errRet != nil {
|
||||
pxy.Warn("write nat hole sid package error, %v", errRet)
|
||||
workConn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
go func() {
|
||||
raw, errRet := msg.ReadMsg(workConn)
|
||||
if errRet != nil {
|
||||
pxy.Warn("read nat hole client ok package error: %v", errRet)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
||||
pxy.Warn("read nat hole client ok package format error")
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case sr.NotifyCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) Close() {
|
||||
pxy.BaseProxy.Close()
|
||||
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
|
||||
errors.PanicToError(func() {
|
||||
close(pxy.closeCh)
|
||||
})
|
||||
}
|
@@ -15,8 +15,15 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -24,6 +31,12 @@ import (
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/nathole"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
"github.com/fatedier/frp/server/group"
|
||||
"github.com/fatedier/frp/server/ports"
|
||||
"github.com/fatedier/frp/server/proxy"
|
||||
"github.com/fatedier/frp/server/stats"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
@@ -40,58 +53,64 @@ const (
|
||||
|
||||
var ServerService *Service
|
||||
|
||||
// Server service.
|
||||
// Server service
|
||||
type Service struct {
|
||||
// Dispatch connections to different handlers listen on same port.
|
||||
// Dispatch connections to different handlers listen on same port
|
||||
muxer *mux.Mux
|
||||
|
||||
// Accept connections from client.
|
||||
// Accept connections from client
|
||||
listener frpNet.Listener
|
||||
|
||||
// Accept connections using kcp.
|
||||
// Accept connections using kcp
|
||||
kcpListener frpNet.Listener
|
||||
|
||||
// For https proxies, route requests to different clients by hostname and other infomation.
|
||||
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||
// Accept connections using websocket
|
||||
websocketListener frpNet.Listener
|
||||
|
||||
httpReverseProxy *vhost.HttpReverseProxy
|
||||
// Accept frp tls connections
|
||||
tlsListener frpNet.Listener
|
||||
|
||||
// Manage all controllers.
|
||||
// Manage all controllers
|
||||
ctlManager *ControlManager
|
||||
|
||||
// Manage all proxies.
|
||||
pxyManager *ProxyManager
|
||||
// Manage all proxies
|
||||
pxyManager *proxy.ProxyManager
|
||||
|
||||
// Manage all visitor listeners.
|
||||
visitorManager *VisitorManager
|
||||
// All resource managers and controllers
|
||||
rc *controller.ResourceController
|
||||
|
||||
// Manage all tcp ports.
|
||||
tcpPortManager *PortManager
|
||||
// stats collector to store server and proxies stats info
|
||||
statsCollector stats.Collector
|
||||
|
||||
// Manage all udp ports.
|
||||
udpPortManager *PortManager
|
||||
|
||||
// Controller for nat hole connections.
|
||||
natHoleController *NatHoleController
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func NewService() (svr *Service, err error) {
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
svr = &Service{
|
||||
ctlManager: NewControlManager(),
|
||||
pxyManager: NewProxyManager(),
|
||||
visitorManager: NewVisitorManager(),
|
||||
tcpPortManager: NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
udpPortManager: NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
ctlManager: NewControlManager(),
|
||||
pxyManager: proxy.NewProxyManager(),
|
||||
rc: &controller.ResourceController{
|
||||
VisitorManager: controller.NewVisitorManager(),
|
||||
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
},
|
||||
tlsConfig: generateTLSConfig(),
|
||||
}
|
||||
|
||||
// Init assets.
|
||||
// Init group controller
|
||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||
|
||||
// Init assets
|
||||
err = assets.Load(cfg.AssetsDir)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Load assets error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Init 404 not found page
|
||||
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||
|
||||
var (
|
||||
httpMuxOn bool
|
||||
httpsMuxOn bool
|
||||
@@ -103,9 +122,6 @@ func NewService() (svr *Service, err error) {
|
||||
if cfg.BindPort == cfg.VhostHttpsPort {
|
||||
httpsMuxOn = true
|
||||
}
|
||||
if httpMuxOn || httpsMuxOn {
|
||||
svr.muxer = mux.NewMux()
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for accepting connections from client.
|
||||
@@ -114,10 +130,11 @@ func NewService() (svr *Service, err error) {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
}
|
||||
if svr.muxer != nil {
|
||||
go svr.muxer.Serve(ln)
|
||||
ln = svr.muxer.DefaultListener()
|
||||
}
|
||||
|
||||
svr.muxer = mux.NewMux(ln)
|
||||
go svr.muxer.Serve()
|
||||
ln = svr.muxer.DefaultListener()
|
||||
|
||||
svr.listener = frpNet.WrapLogListener(ln)
|
||||
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
|
||||
|
||||
@@ -131,10 +148,19 @@ func NewService() (svr *Service, err error) {
|
||||
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort)
|
||||
}
|
||||
|
||||
// Listen for accepting connections from client using websocket protocol.
|
||||
websocketPrefix := []byte("GET " + frpNet.FrpWebsocketPath)
|
||||
websocketLn := svr.muxer.Listen(0, uint32(len(websocketPrefix)), func(data []byte) bool {
|
||||
return bytes.Equal(data, websocketPrefix)
|
||||
})
|
||||
svr.websocketListener = frpNet.NewWebsocketListener(websocketLn)
|
||||
|
||||
// Create http vhost muxer.
|
||||
if cfg.VhostHttpPort > 0 {
|
||||
rp := vhost.NewHttpReverseProxy()
|
||||
svr.httpReverseProxy = rp
|
||||
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
|
||||
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
|
||||
})
|
||||
svr.rc.HttpReverseProxy = rp
|
||||
|
||||
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||
server := &http.Server{
|
||||
@@ -143,7 +169,7 @@ func NewService() (svr *Service, err error) {
|
||||
}
|
||||
var l net.Listener
|
||||
if httpMuxOn {
|
||||
l = svr.muxer.ListenHttp(0)
|
||||
l = svr.muxer.ListenHttp(1)
|
||||
} else {
|
||||
l, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
@@ -159,7 +185,7 @@ func NewService() (svr *Service, err error) {
|
||||
if cfg.VhostHttpsPort > 0 {
|
||||
var l net.Listener
|
||||
if httpsMuxOn {
|
||||
l = svr.muxer.ListenHttps(0)
|
||||
l = svr.muxer.ListenHttps(1)
|
||||
} else {
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort))
|
||||
if err != nil {
|
||||
@@ -168,7 +194,7 @@ func NewService() (svr *Service, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(frpNet.WrapLogListener(l), 30*time.Second)
|
||||
svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(frpNet.WrapLogListener(l), 30*time.Second)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
||||
return
|
||||
@@ -176,40 +202,53 @@ func NewService() (svr *Service, err error) {
|
||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||
}
|
||||
|
||||
// frp tls listener
|
||||
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||
})
|
||||
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
|
||||
|
||||
// Create nat hole controller.
|
||||
if cfg.BindUdpPort > 0 {
|
||||
var nc *NatHoleController
|
||||
var nc *nathole.NatHoleController
|
||||
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUdpPort)
|
||||
nc, err = NewNatHoleController(addr)
|
||||
nc, err = nathole.NewNatHoleController(addr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create nat hole controller error, %v", err)
|
||||
return
|
||||
}
|
||||
svr.natHoleController = nc
|
||||
svr.rc.NatHoleController = nc
|
||||
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUdpPort)
|
||||
}
|
||||
|
||||
var statsEnable bool
|
||||
// Create dashboard web server.
|
||||
if cfg.DashboardPort > 0 {
|
||||
err = RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
||||
statsEnable = true
|
||||
}
|
||||
|
||||
svr.statsCollector = stats.NewInternalCollector(statsEnable)
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) Run() {
|
||||
if svr.natHoleController != nil {
|
||||
go svr.natHoleController.Run()
|
||||
if svr.rc.NatHoleController != nil {
|
||||
go svr.rc.NatHoleController.Run()
|
||||
}
|
||||
if g.GlbServerCfg.KcpBindPort > 0 {
|
||||
go svr.HandleListener(svr.kcpListener)
|
||||
}
|
||||
svr.HandleListener(svr.listener)
|
||||
|
||||
go svr.HandleListener(svr.websocketListener)
|
||||
go svr.HandleListener(svr.tlsListener)
|
||||
|
||||
svr.HandleListener(svr.listener)
|
||||
}
|
||||
|
||||
func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
@@ -220,6 +259,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
log.Warn("Listener for incoming connections from client closed")
|
||||
return
|
||||
}
|
||||
c = frpNet.CheckAndEnableTLSServerConn(c, svr.tlsConfig)
|
||||
|
||||
// Start a new goroutine for dealing connections.
|
||||
go func(frpConn frpNet.Conn) {
|
||||
@@ -270,6 +310,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
|
||||
if g.GlbServerCfg.TcpMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
session, err := fmux.Server(frpConn, fmuxCfg)
|
||||
if err != nil {
|
||||
@@ -306,11 +347,6 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
}
|
||||
|
||||
// Check auth.
|
||||
nowTime := time.Now().Unix()
|
||||
if g.GlbServerCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > g.GlbServerCfg.AuthTimeout {
|
||||
err = fmt.Errorf("authorization timeout")
|
||||
return
|
||||
}
|
||||
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
err = fmt.Errorf("authorization failed")
|
||||
return
|
||||
@@ -325,7 +361,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
}
|
||||
}
|
||||
|
||||
ctl := NewControl(svr, ctlConn, loginMsg)
|
||||
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg)
|
||||
|
||||
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
|
||||
oldCtl.allShutdown.WaitDone()
|
||||
@@ -335,7 +371,13 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
ctl.Start()
|
||||
|
||||
// for statistics
|
||||
StatsNewClient()
|
||||
svr.statsCollector.Mark(stats.TypeNewClient, &stats.NewClientPayload{})
|
||||
|
||||
go func() {
|
||||
// block until control closed
|
||||
ctl.WaitClosed()
|
||||
svr.ctlManager.Del(loginMsg.RunId, ctl)
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -351,14 +393,27 @@ func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkCo
|
||||
}
|
||||
|
||||
func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
|
||||
return svr.visitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||
newMsg.UseEncryption, newMsg.UseCompression)
|
||||
}
|
||||
|
||||
func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
|
||||
return svr.pxyManager.Add(name, pxy)
|
||||
}
|
||||
// Setup a bare-bones TLS config for the server
|
||||
func generateTLSConfig() *tls.Config {
|
||||
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})
|
||||
|
||||
func (svr *Service) DelProxy(name string) {
|
||||
svr.pxyManager.Del(name)
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
}
|
||||
|
277
server/stats/internal.go
Normal file
277
server/stats/internal.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// 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 stats
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/metric"
|
||||
)
|
||||
|
||||
type internalCollector struct {
|
||||
enable bool
|
||||
info *ServerStatistics
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewInternalCollector(enable bool) Collector {
|
||||
return &internalCollector{
|
||||
enable: enable,
|
||||
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 (collector *internalCollector) Run() error {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(12 * time.Hour)
|
||||
log.Debug("start to clear useless proxy statistics data...")
|
||||
collector.ClearUselessInfo()
|
||||
log.Debug("finish to clear useless proxy statistics data")
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (collector *internalCollector) ClearUselessInfo() {
|
||||
// To check if there are proxies that closed than 7 days and drop them.
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
for name, data := range collector.info.ProxyStatistics {
|
||||
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||
delete(collector.info.ProxyStatistics, name)
|
||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) Mark(statsType StatsType, payload interface{}) {
|
||||
if !collector.enable {
|
||||
return
|
||||
}
|
||||
|
||||
switch v := payload.(type) {
|
||||
case *NewClientPayload:
|
||||
collector.newClient(v)
|
||||
case *CloseClientPayload:
|
||||
collector.closeClient(v)
|
||||
case *NewProxyPayload:
|
||||
collector.newProxy(v)
|
||||
case *CloseProxyPayload:
|
||||
collector.closeProxy(v)
|
||||
case *OpenConnectionPayload:
|
||||
collector.openConnection(v)
|
||||
case *CloseConnectionPayload:
|
||||
collector.closeConnection(v)
|
||||
case *AddTrafficInPayload:
|
||||
collector.addTrafficIn(v)
|
||||
case *AddTrafficOutPayload:
|
||||
collector.addTrafficOut(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) newClient(payload *NewClientPayload) {
|
||||
collector.info.ClientCounts.Inc(1)
|
||||
}
|
||||
|
||||
func (collector *internalCollector) closeClient(payload *CloseClientPayload) {
|
||||
collector.info.ClientCounts.Dec(1)
|
||||
}
|
||||
|
||||
func (collector *internalCollector) newProxy(payload *NewProxyPayload) {
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
counter, ok := collector.info.ProxyTypeCounts[payload.ProxyType]
|
||||
if !ok {
|
||||
counter = metric.NewCounter()
|
||||
}
|
||||
counter.Inc(1)
|
||||
collector.info.ProxyTypeCounts[payload.ProxyType] = counter
|
||||
|
||||
proxyStats, ok := collector.info.ProxyStatistics[payload.Name]
|
||||
if !(ok && proxyStats.ProxyType == payload.ProxyType) {
|
||||
proxyStats = &ProxyStatistics{
|
||||
Name: payload.Name,
|
||||
ProxyType: payload.ProxyType,
|
||||
CurConns: metric.NewCounter(),
|
||||
TrafficIn: metric.NewDateCounter(ReserveDays),
|
||||
TrafficOut: metric.NewDateCounter(ReserveDays),
|
||||
}
|
||||
collector.info.ProxyStatistics[payload.Name] = proxyStats
|
||||
}
|
||||
proxyStats.LastStartTime = time.Now()
|
||||
}
|
||||
|
||||
func (collector *internalCollector) closeProxy(payload *CloseProxyPayload) {
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
if counter, ok := collector.info.ProxyTypeCounts[payload.ProxyType]; ok {
|
||||
counter.Dec(1)
|
||||
}
|
||||
if proxyStats, ok := collector.info.ProxyStatistics[payload.Name]; ok {
|
||||
proxyStats.LastCloseTime = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) openConnection(payload *OpenConnectionPayload) {
|
||||
collector.info.CurConns.Inc(1)
|
||||
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName]
|
||||
if ok {
|
||||
proxyStats.CurConns.Inc(1)
|
||||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) closeConnection(payload *CloseConnectionPayload) {
|
||||
collector.info.CurConns.Dec(1)
|
||||
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName]
|
||||
if ok {
|
||||
proxyStats.CurConns.Dec(1)
|
||||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) addTrafficIn(payload *AddTrafficInPayload) {
|
||||
collector.info.TotalTrafficIn.Inc(payload.TrafficBytes)
|
||||
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
|
||||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName]
|
||||
if ok {
|
||||
proxyStats.TrafficIn.Inc(payload.TrafficBytes)
|
||||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) addTrafficOut(payload *AddTrafficOutPayload) {
|
||||
collector.info.TotalTrafficOut.Inc(payload.TrafficBytes)
|
||||
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
|
||||
proxyStats, ok := collector.info.ProxyStatistics[payload.ProxyName]
|
||||
if ok {
|
||||
proxyStats.TrafficOut.Inc(payload.TrafficBytes)
|
||||
collector.info.ProxyStatistics[payload.ProxyName] = proxyStats
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *internalCollector) GetServer() *ServerStats {
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
s := &ServerStats{
|
||||
TotalTrafficIn: collector.info.TotalTrafficIn.TodayCount(),
|
||||
TotalTrafficOut: collector.info.TotalTrafficOut.TodayCount(),
|
||||
CurConns: collector.info.CurConns.Count(),
|
||||
ClientCounts: collector.info.ClientCounts.Count(),
|
||||
ProxyTypeCounts: make(map[string]int64),
|
||||
}
|
||||
for k, v := range collector.info.ProxyTypeCounts {
|
||||
s.ProxyTypeCounts[k] = v.Count()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (collector *internalCollector) GetProxiesByType(proxyType string) []*ProxyStats {
|
||||
res := make([]*ProxyStats, 0)
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range collector.info.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
ps := &ProxyStats{
|
||||
Name: name,
|
||||
Type: proxyStats.ProxyType,
|
||||
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||
CurConns: 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 (collector *internalCollector) GetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) {
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
|
||||
for name, proxyStats := range collector.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: 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 (collector *internalCollector) GetProxyTraffic(name string) (res *ProxyTrafficInfo) {
|
||||
collector.mu.Lock()
|
||||
defer collector.mu.Unlock()
|
||||
|
||||
proxyStats, ok := collector.info.ProxyStatistics[name]
|
||||
if ok {
|
||||
res = &ProxyTrafficInfo{
|
||||
Name: name,
|
||||
}
|
||||
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
|
||||
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
|
||||
}
|
||||
return
|
||||
}
|
129
server/stats/stats.go
Normal file
129
server/stats/stats.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/utils/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
|
||||
CurConns int64
|
||||
ClientCounts int64
|
||||
ProxyTypeCounts map[string]int64
|
||||
}
|
||||
|
||||
type ProxyStats struct {
|
||||
Name string
|
||||
Type string
|
||||
TodayTrafficIn int64
|
||||
TodayTrafficOut int64
|
||||
LastStartTime string
|
||||
LastCloseTime string
|
||||
CurConns int64
|
||||
}
|
||||
|
||||
type ProxyTrafficInfo struct {
|
||||
Name string
|
||||
TrafficIn []int64
|
||||
TrafficOut []int64
|
||||
}
|
||||
|
||||
type ProxyStatistics struct {
|
||||
Name string
|
||||
ProxyType string
|
||||
TrafficIn metric.DateCounter
|
||||
TrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
LastStartTime time.Time
|
||||
LastCloseTime time.Time
|
||||
}
|
||||
|
||||
type ServerStatistics struct {
|
||||
TotalTrafficIn metric.DateCounter
|
||||
TotalTrafficOut metric.DateCounter
|
||||
CurConns metric.Counter
|
||||
|
||||
// counter for clients
|
||||
ClientCounts metric.Counter
|
||||
|
||||
// counter for proxy types
|
||||
ProxyTypeCounts map[string]metric.Counter
|
||||
|
||||
// statistics for different proxies
|
||||
// key is proxy name
|
||||
ProxyStatistics map[string]*ProxyStatistics
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 10700
|
||||
log_file = ./frpc.log
|
||||
log_file = console
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
token = 123456
|
||||
@@ -23,6 +23,22 @@ remote_port = 10901
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
|
||||
[tcp_group1]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
remote_port = 10802
|
||||
group = test1
|
||||
group_key = 123
|
||||
|
||||
[tcp_group2]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10702
|
||||
remote_port = 10802
|
||||
group = test1
|
||||
group_key = 123
|
||||
|
||||
[udp_normal]
|
||||
type = udp
|
||||
local_ip = 127.0.0.1
|
||||
@@ -103,6 +119,20 @@ use_compression = true
|
||||
http_user = test
|
||||
http_user = test
|
||||
|
||||
[web06]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10704
|
||||
custom_domains = test6.frp.com
|
||||
host_header_rewrite = test6.frp.com
|
||||
header_X-From-Where = frp
|
||||
|
||||
[wildcard_http]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10704
|
||||
custom_domains = *.frp1.com
|
||||
|
||||
[subhost01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
@@ -1,7 +1,7 @@
|
||||
[common]
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 10700
|
||||
log_file = ./frpc_visitor.log
|
||||
log_file = console
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
token = 123456
|
@@ -2,7 +2,7 @@
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 10700
|
||||
vhost_http_port = 10804
|
||||
log_file = ./frps.log
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
allow_ports = 10000-20000,20002,30000-50000
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user