mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 15:45:39 +00:00
Compare commits
202 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
f1bea49314 | ||
|
2ffae3489b | ||
|
96b94d9164 | ||
|
76b04f52d1 | ||
|
97db0d187a | ||
|
d9aadab4cb | ||
|
a0fe2fc2c2 | ||
|
1464836f05 | ||
|
b2a2037032 | ||
|
071cbf4b15 | ||
|
20fcb58437 | ||
|
a27e3dda88 | ||
|
1dd7317c06 | ||
|
58a54bd0fb | ||
|
caec4982cc | ||
|
dd8f788ca4 | ||
|
8a6d6c534a | ||
|
39089cf262 | ||
|
55800dc29f | ||
|
04560c1896 | ||
|
178efd67f1 | ||
|
6ef5fb6391 | ||
|
1ae43e4d41 | ||
|
5db605ca02 | ||
|
e087301425 | ||
|
f45283dbdb | ||
|
b0959b3caa | ||
|
c5c89a2519 | ||
|
bebd1db22a | ||
|
cd37d22f3b | ||
|
30af32728a | ||
|
60ecd1d58c | ||
|
a60be8f562 | ||
|
c008b14d0f | ||
|
853892f3cd | ||
|
e43f9f5850 | ||
|
d5f30ccd6b | ||
|
b87df569e7 | ||
|
976cf3e9f8 | ||
|
371c401f5b | ||
|
69919e8ef9 | ||
|
9abbe33790 | ||
|
4a5c00286e | ||
|
dfb892c8f6 | ||
|
461c4c18fd | ||
|
00b9ba95ae | ||
|
c47aad348d | ||
|
4cb4da3afc | ||
|
c1f57da00d | ||
|
fe187eb8ec | ||
|
0f6f674a64 | ||
|
814afbe1f6 | ||
|
3fde9176c9 | ||
|
af7cca1a93 | ||
|
7dd28a14aa | ||
|
1325c59a4c | ||
|
82dc1e924f | ||
|
3166bdf3f0 | ||
|
8af70c8822 | ||
|
87763e8251 | ||
|
e9241aeb94 | ||
|
2eaf134042 | ||
|
1739e012d6 | ||
|
9e8980429f | ||
|
1d0865ca49 | ||
|
5c9909aeef | ||
|
456ce09061 | ||
|
ffc13b704a | ||
|
5d239127bb | ||
|
9b990adf96 | ||
|
44e8108910 | ||
|
1c35e9a0c6 | ||
|
8e719ff0ff | ||
|
637ddbce1f | ||
|
ce8fde793c | ||
|
eede31c064 | ||
|
41c41789b6 | ||
|
68dfc89bce |
@@ -1,5 +0,0 @@
|
||||
Dockerfile
|
||||
.git
|
||||
*~
|
||||
*#
|
||||
.#*
|
@@ -2,8 +2,8 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
|
||||
install:
|
||||
- make
|
||||
|
17
Dockerfile
17
Dockerfile
@@ -1,17 +0,0 @@
|
||||
FROM golang:1.8
|
||||
|
||||
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"]
|
43
Makefile
43
Makefile
@@ -1,5 +1,4 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
|
||||
all: fmt build
|
||||
|
||||
@@ -7,24 +6,19 @@ 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:
|
||||
go fmt ./assets/...
|
||||
go fmt ./client/...
|
||||
go fmt ./cmd/...
|
||||
go fmt ./models/...
|
||||
go fmt ./server/...
|
||||
go fmt ./utils/...
|
||||
go fmt ./...
|
||||
|
||||
frps:
|
||||
go build -o bin/frps ./cmd/frps
|
||||
@cp -rf ./assets/static ./bin
|
||||
|
||||
frpc:
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
@@ -32,27 +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 -
|
||||
|
||||
ciclean:
|
||||
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 -
|
||||
|
||||
save:
|
||||
godep save ./...
|
||||
|
@@ -1,5 +1,4 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
LDFLAGS := -s -w
|
||||
|
||||
all: build
|
||||
@@ -9,12 +8,18 @@ build: app
|
||||
app:
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc
|
||||
@@ -23,10 +28,10 @@ app:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
|
||||
|
||||
temp:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
|
236
README.md
236
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)
|
||||
@@ -20,27 +21,32 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||
* [Forward DNS query request](#forward-dns-query-request)
|
||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||
* [Expose your service in security](#expose-your-service-in-security)
|
||||
* [P2P Mode](#p2p-mode)
|
||||
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
||||
* [Features](#features)
|
||||
* [Configuration File](#configuration-file)
|
||||
* [Configuration file template](#configuration-file-template)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Authentication](#authentication)
|
||||
* [Encryption and Compression](#encryption-and-compression)
|
||||
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||
* [Privilege Mode](#privilege-mode)
|
||||
* [Port White List](#port-white-list)
|
||||
* [Port White List](#port-white-list)
|
||||
* [Port Reuse](#port-reuse)
|
||||
* [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)
|
||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||
* [Custom subdomain names](#custom-subdomain-names)
|
||||
* [URL routing](#url-routing)
|
||||
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
||||
* [Range ports mapping](#range-ports-mapping)
|
||||
* [Plugin](#plugin)
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
@@ -51,11 +57,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.
|
||||
@@ -214,6 +215,32 @@ Configure frps same as above.
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### Expose a simple http file server
|
||||
|
||||
A simple way to visit files in the LAN.
|
||||
|
||||
Configure frps same as above.
|
||||
|
||||
1. Start frpc with configurations:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_static_file]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = static_file
|
||||
plugin_local_path = /tmp/file
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||
|
||||
### Expose your service in security
|
||||
|
||||
For some services, if expose them to the public network directly will be a security risk.
|
||||
@@ -306,26 +333,6 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
|
||||
|
||||
`ssh -oPort=6000 test@127.0.0.1`
|
||||
|
||||
### Connect website through frpc's network
|
||||
|
||||
Configure frps same as above.
|
||||
|
||||
1. Start frpc with configurations:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy # or socks5
|
||||
```
|
||||
|
||||
2. Set http proxy or socks5 proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
|
||||
|
||||
## Features
|
||||
|
||||
### Configuration File
|
||||
@@ -336,6 +343,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.
|
||||
@@ -356,11 +391,7 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
|
||||
|
||||
### Authentication
|
||||
|
||||
Since v0.10.0, you only need to set `privilege_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
|
||||
|
||||
@@ -395,21 +426,23 @@ Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to le
|
||||
|
||||
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
|
||||
|
||||
### Privilege Mode
|
||||
### Port White List
|
||||
|
||||
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in client.
|
||||
|
||||
#### Port White List
|
||||
|
||||
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports:
|
||||
`allow_ports` in frps.ini is used for preventing abuse of ports:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`.
|
||||
`allow_ports` consists of a specific port or a range of ports divided by `,`.
|
||||
|
||||
### Port Reuse
|
||||
|
||||
Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly.
|
||||
|
||||
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
|
||||
|
||||
### TCP Stream Multiplexing
|
||||
|
||||
@@ -474,9 +507,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
|
||||
@@ -487,7 +592,24 @@ 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.
|
||||
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -495,8 +617,6 @@ Features for http proxy only.
|
||||
|
||||
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.**
|
||||
|
||||
### Password protecting your web service
|
||||
|
||||
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
|
||||
@@ -576,11 +696,26 @@ server_port = 7000
|
||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
```
|
||||
|
||||
### Range ports mapping
|
||||
|
||||
Proxy name has prefix `range:` will support mapping range ports.
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[range:test_tcp]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6000-6006,6007
|
||||
remote_port = 6000-6006,6007
|
||||
```
|
||||
|
||||
frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
|
||||
|
||||
### Plugin
|
||||
|
||||
frpc only forward request to local tcp or udp port by default.
|
||||
|
||||
Plugin is used for providing rich features. There are built-in plugins such as **unix_domain_socket**, **http_proxy**, **socks5** and you can see [example usage](#example-usage).
|
||||
Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
||||
|
||||
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
||||
|
||||
@@ -598,17 +733,12 @@ plugin_http_passwd = abc
|
||||
|
||||
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||
|
||||
|
||||
## Development Plan
|
||||
|
||||
* Log http request information in frps.
|
||||
* Direct reverse proxy, like haproxy.
|
||||
* Load balance to different service in frpc.
|
||||
* Frpc can directly be a webserver for static files.
|
||||
* P2p communicate by making udp hole to penetrate NAT.
|
||||
* kubernetes ingress support.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Interested in getting involved? We would like to help you!
|
||||
|
259
README_zh.md
259
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-的作用)
|
||||
* [开发状态](#开发状态)
|
||||
* [架构](#架构)
|
||||
* [使用示例](#使用示例)
|
||||
@@ -18,27 +17,32 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||
* [点对点内网穿透](#点对点内网穿透)
|
||||
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
||||
* [功能说明](#功能说明)
|
||||
* [配置文件](#配置文件)
|
||||
* [配置文件模版渲染](#配置文件模版渲染)
|
||||
* [Dashboard](#dashboard)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||
* [客户端查看代理状态](#客户端查看代理状态)
|
||||
* [特权模式](#特权模式)
|
||||
* [端口白名单](#端口白名单)
|
||||
* [端口白名单](#端口白名单)
|
||||
* [端口复用](#端口复用)
|
||||
* [TCP 多路复用](#tcp-多路复用)
|
||||
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
|
||||
* [连接池](#连接池)
|
||||
* [负载均衡](#负载均衡)
|
||||
* [健康检查](#健康检查)
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
||||
* [获取用户真实 IP](#获取用户真实-ip)
|
||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||
* [自定义二级域名](#自定义二级域名)
|
||||
* [URL 路由](#url-路由)
|
||||
* [通过代理连接 frps](#通过代理连接-frps)
|
||||
* [范围端口映射](#范围端口映射)
|
||||
* [插件](#插件)
|
||||
* [开发计划](#开发计划)
|
||||
* [为 frp 做贡献](#为-frp-做贡献)
|
||||
@@ -49,15 +53,9 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## frp 的作用
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
|
||||
* 对于 http, https 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 仍然处于前期开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
frp 仍然处于开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
|
||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
@@ -192,11 +190,11 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
||||
|
||||
### 转发 Unix域套接字
|
||||
|
||||
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
|
||||
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 启动 frpc,启用 unix_domain_socket 插件,配置如下:
|
||||
1. 启动 frpc,启用 `unix_domain_socket` 插件,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
@@ -215,6 +213,34 @@ frps 的部署步骤同上。
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### 对外提供简单的文件访问服务
|
||||
|
||||
通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 启动 frpc,启用 `static_file` 插件,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_static_file]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = static_file
|
||||
# 要对外暴露的文件目录
|
||||
plugin_local_path = /tmp/file
|
||||
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||
|
||||
### 安全地暴露内网服务
|
||||
|
||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||
@@ -319,28 +345,6 @@ frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量
|
||||
|
||||
`ssh -oPort=6000 test@127.0.0.1`
|
||||
|
||||
### 通过 frpc 所在机器访问外网
|
||||
|
||||
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 启动 frpc,启用 http_proxy 或 socks5 插件(plugin 换为 socks5 即可), 配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
```
|
||||
|
||||
2. 浏览器设置 http 或 socks5 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 配置文件
|
||||
@@ -351,10 +355,41 @@ frps 的部署步骤同上。
|
||||
|
||||
[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
|
||||
@@ -371,11 +406,7 @@ dashboard_pwd = admin
|
||||
|
||||
### 身份验证
|
||||
|
||||
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。
|
||||
|
||||
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
|
||||
|
||||
这个超时时间可以在配置文件中通过 `authentication_timeout` 这个参数来修改,单位为秒,默认值为 900,即 15 分钟。如果修改为 0,则 frps 将不对身份验证报文的时间戳进行超时校验。
|
||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||
|
||||
### 加密与压缩
|
||||
|
||||
@@ -420,21 +451,25 @@ admin_port = 7400
|
||||
|
||||
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。
|
||||
|
||||
### 特权模式
|
||||
### 端口白名单
|
||||
|
||||
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式。
|
||||
|
||||
#### 端口白名单
|
||||
|
||||
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
|
||||
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
|
||||
`allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
|
||||
|
||||
### 端口复用
|
||||
|
||||
目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口,frps 会对连接的协议进行分析,之后进行不同的处理。
|
||||
|
||||
例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。
|
||||
|
||||
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
|
||||
|
||||
### TCP 多路复用
|
||||
|
||||
@@ -499,6 +534,78 @@ 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 类型的代理。
|
||||
@@ -514,12 +621,26 @@ 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 请求中添加了这两个 header。**
|
||||
|
||||
### 通过密码保护你的 web 服务
|
||||
|
||||
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
|
||||
@@ -564,9 +685,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` 可以同时配置。
|
||||
|
||||
@@ -609,11 +730,30 @@ server_port = 7000
|
||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
```
|
||||
|
||||
### 范围端口映射
|
||||
|
||||
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
|
||||
|
||||
这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy,每一个 proxy 以数字为后缀命名。
|
||||
|
||||
例如要映射本地 6000-6005, 6007 这6个端口,主要配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[range:test_tcp]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6000-6006,6007
|
||||
remote_port = 6000-6006,6007
|
||||
```
|
||||
|
||||
实际连接成功后会创建 8 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
|
||||
|
||||
### 插件
|
||||
|
||||
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||
|
||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**、**socks5**。具体使用方式请查看[使用示例](#使用示例)。
|
||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
|
||||
|
||||
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
||||
|
||||
@@ -636,11 +776,6 @@ plugin_http_passwd = abc
|
||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||
|
||||
* frps 记录 http 请求日志。
|
||||
* frps 支持直接反向代理,类似 haproxy。
|
||||
* frpc 支持负载均衡到后端不同服务。
|
||||
* frpc 支持直接作为 webserver 访问指定静态页面。
|
||||
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
||||
* 集成对 k8s 等平台的支持。
|
||||
|
||||
## 为 frp 做贡献
|
||||
|
||||
@@ -661,6 +796,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?facf06d98c7e1aea259d"></script><script type="text/javascript" src="vendor.js?a05a344be2b42183469b"></script><script type="text/javascript" src="index.js?a914c2dc7a5bb16ad443"></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,a,c){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 a)Object.prototype.hasOwnProperty.call(a,u)&&(e[u]=a[u]);for(r&&r(t,a,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={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 a=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=a;var c=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:"a914c2dc7a5bb16ad443",1:"a05a344be2b42183469b"}[e];var i=setTimeout(r,12e4);return u.onerror=u.onload=r,c.appendChild(u),a},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/models/config"
|
||||
"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 := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd
|
||||
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{
|
||||
|
@@ -17,69 +17,65 @@ package client
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"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]")
|
||||
|
||||
conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile)
|
||||
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
|
||||
}
|
||||
|
||||
newCommonCfg, err := config.LoadClientCommonConf(conf)
|
||||
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
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||
if err != nil {
|
||||
res.Code = 3
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc proxy config error: %v", err)
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
err = svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
|
||||
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
if err != nil {
|
||||
res.Code = 4
|
||||
res.Code = 500
|
||||
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")
|
||||
@@ -111,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,
|
||||
@@ -125,18 +121,18 @@ func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp {
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, cfg.RemotePort)
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = config.ClientCommonCfg.ServerAddr + status.RemoteAddr
|
||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||
}
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, cfg.RemotePort)
|
||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||
} else {
|
||||
psr.RemoteAddr = config.ClientCommonCfg.ServerAddr + status.RemoteAddr
|
||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
@@ -164,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
|
||||
@@ -176,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 {
|
||||
@@ -209,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
|
||||
}
|
||||
}
|
||||
|
@@ -17,39 +17,38 @@ package client
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"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/crypto"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/shutdown"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
// tcp stream multiplexing, if enabled
|
||||
session *smux.Session
|
||||
session *fmux.Session
|
||||
|
||||
// put a message in this channel to send it over control connection to server
|
||||
sendCh chan (msg.Message)
|
||||
@@ -57,14 +56,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
|
||||
@@ -78,54 +73,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: config.ClientCommonCfg.PoolCount,
|
||||
User: config.ClientCommonCfg.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,
|
||||
sendCh: make(chan msg.Message, 10),
|
||||
readCh: make(chan msg.Message, 10),
|
||||
closedCh: make(chan int),
|
||||
runId: runId,
|
||||
conn: conn,
|
||||
session: session,
|
||||
pxyCfgs: pxyCfgs,
|
||||
sendCh: make(chan msg.Message, 100),
|
||||
readCh: make(chan msg.Message, 100),
|
||||
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)
|
||||
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 config.ClientCommonCfg.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()
|
||||
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) {
|
||||
@@ -167,82 +145,19 @@ 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()
|
||||
ctl.pm.Close()
|
||||
ctl.conn.Close()
|
||||
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()
|
||||
}
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
|
||||
conn, err := frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if config.ClientCommonCfg.TcpMux {
|
||||
session, errRet := smux.Client(conn, nil)
|
||||
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(config.ClientCommonCfg.PrivilegeToken, 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
|
||||
config.ClientCommonCfg.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 config.ClientCommonCfg.TcpMux {
|
||||
if g.GlbClientCfg.TcpMux {
|
||||
stream, errRet := ctl.session.OpenStream()
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
@@ -251,8 +166,8 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
}
|
||||
conn = frpNet.WrapConn(stream)
|
||||
} else {
|
||||
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
if err != nil {
|
||||
ctl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
@@ -266,12 +181,13 @@ func (ctl *Control) reader() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.Error("panic error: %v", err)
|
||||
ctl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.readerShutdown.Done()
|
||||
defer close(ctl.closedCh)
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
if err == io.EOF {
|
||||
@@ -290,7 +206,7 @@ func (ctl *Control) reader() {
|
||||
// writer writes messages got from sendCh to frps
|
||||
func (ctl *Control) writer() {
|
||||
defer ctl.writerShutdown.Done()
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
ctl.conn.Close()
|
||||
@@ -314,11 +230,12 @@ func (ctl *Control) msgHandler() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.Error("panic error: %v", err)
|
||||
ctl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
|
||||
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
@@ -332,7 +249,7 @@ func (ctl *Control) msgHandler() {
|
||||
ctl.Debug("send heartbeat to server")
|
||||
ctl.sendCh <- &msg.Ping{}
|
||||
case <-hbCheck.C:
|
||||
if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second {
|
||||
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
|
||||
ctl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
ctl.conn.Close()
|
||||
@@ -356,87 +273,32 @@ 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.writer()
|
||||
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 := 10 * time.Second
|
||||
checkProxyTicker := time.NewTicker(checkInterval)
|
||||
for {
|
||||
select {
|
||||
case <-checkProxyTicker.C:
|
||||
// every 10 seconds, check which proxy registered failed and reregister it to server
|
||||
ctl.pm.CheckAndStartProxy()
|
||||
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, 10)
|
||||
ctl.readCh = make(chan msg.Message, 10)
|
||||
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()
|
||||
|
||||
checkProxyTicker.Stop()
|
||||
checkProxyTicker = time.NewTicker(checkInterval)
|
||||
}
|
||||
}
|
||||
close(ctl.closedDoneCh)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
|
||||
err := ctl.pm.Reload(pxyCfgs, visitorCfgs)
|
||||
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
|
||||
}
|
178
client/health/health.go
Normal file
178
client/health/health.go
Normal file
@@ -0,0 +1,178 @@
|
||||
// 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"
|
||||
"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
|
||||
}
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -22,18 +22,20 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/plugin"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
@@ -46,37 +48,37 @@ type Proxy interface {
|
||||
|
||||
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
baseProxy := BaseProxy{
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetName()),
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -91,7 +93,7 @@ type BaseProxy struct {
|
||||
|
||||
// TCP
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.TcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -115,12 +117,12 @@ func (pxy *TcpProxy) Close() {
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -144,12 +146,12 @@ func (pxy *HttpProxy) Close() {
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -173,12 +175,12 @@ func (pxy *HttpsProxy) Close() {
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
}
|
||||
|
||||
// STCP
|
||||
type StcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.StcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -202,12 +204,12 @@ func (pxy *StcpProxy) Close() {
|
||||
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XtcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.XtcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -243,7 +245,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
Sid: natHoleSidMsg.Sid,
|
||||
}
|
||||
raddr, _ := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||
defer clientConn.Close()
|
||||
|
||||
@@ -270,6 +272,12 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
}
|
||||
clientConn.SetReadDeadline(time.Time{})
|
||||
clientConn.Close()
|
||||
|
||||
if natHoleRespMsg.Error != "" {
|
||||
pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
|
||||
// Send sid to visitor udp address.
|
||||
@@ -300,7 +308,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
|
||||
// UDP
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.UdpProxyConf
|
||||
|
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) {
|
||||
pm.mu.RLock()
|
||||
pw, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if ok {
|
||||
pw.InWorkConn(workConn)
|
||||
} 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) {
|
||||
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) 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,349 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
const (
|
||||
ProxyStatusNew = "new"
|
||||
ProxyStatusStartErr = "start error"
|
||||
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.GetName(),
|
||||
Type: cfg.GetType(),
|
||||
Status: ProxyStatusNew,
|
||||
Cfg: cfg,
|
||||
pxy: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) IsRunning() bool {
|
||||
pw.mu.RLock()
|
||||
defer pw.mu.RUnlock()
|
||||
if pw.Status == ProxyStatusRunning {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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) 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()
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) CheckAndStartProxy() {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
if pm.closed {
|
||||
pm.Warn("CheckAndStartProxy error: ProxyManager is closed now")
|
||||
return
|
||||
}
|
||||
|
||||
for _, pxy := range pm.proxies {
|
||||
if !pxy.IsRunning() {
|
||||
var newProxyMsg msg.NewProxy
|
||||
pxy.Cfg.UnMarshalToMsg(&newProxyMsg)
|
||||
err := pm.sendMsg(&newProxyMsg)
|
||||
if err != nil {
|
||||
pm.Warn("[%s] proxy send NewProxy message error")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, cfg := range pm.visitorCfgs {
|
||||
if _, exist := pm.visitors[cfg.GetName()]; !exist {
|
||||
pm.Info("try to start visitor [%s]", cfg.GetName())
|
||||
visitor := NewVisitor(pm.ctl, cfg)
|
||||
err := visitor.Run()
|
||||
if err != nil {
|
||||
visitor.Warn("start error: %v", err)
|
||||
continue
|
||||
}
|
||||
pm.visitors[cfg.GetName()] = visitor
|
||||
visitor.Info("start visitor success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
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,44 +15,217 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
if config.ClientCommonCfg.AdminPort != 0 {
|
||||
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
|
||||
go svr.keepControllerWorking()
|
||||
|
||||
if g.GlbClientCfg.AdminPort != 0 {
|
||||
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
|
||||
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||
}
|
||||
|
||||
<-svr.closedCh
|
||||
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) {
|
||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
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)
|
||||
}
|
||||
|
@@ -26,13 +26,15 @@ import (
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
// Visitor is used for forward traffics from local port tot remote service.
|
||||
@@ -42,20 +44,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.GetName()),
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -71,9 +73,9 @@ type BaseVisitor struct {
|
||||
}
|
||||
|
||||
type StcpVisitor struct {
|
||||
BaseVisitor
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.StcpProxyConf
|
||||
cfg *config.StcpVisitorConf
|
||||
}
|
||||
|
||||
func (sv *StcpVisitor) Run() (err error) {
|
||||
@@ -158,9 +160,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) {
|
||||
@@ -181,7 +183,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
|
||||
}
|
||||
|
||||
@@ -193,14 +195,23 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
defer userConn.Close()
|
||||
|
||||
sv.Debug("get a new xtcp user connection")
|
||||
if config.ClientCommonCfg.ServerUdpPort == 0 {
|
||||
if g.GlbClientCfg.ServerUdpPort == 0 {
|
||||
sv.Error("xtcp is not supported by server")
|
||||
return
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp",
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
|
||||
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()
|
||||
@@ -233,6 +244,11 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
visitorConn.SetReadDeadline(time.Time{})
|
||||
pool.PutBuf(buf)
|
||||
|
||||
if natHoleRespMsg.Error != "" {
|
||||
sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
|
||||
// Close visitorConn, so we can use it's local address.
|
||||
|
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()
|
||||
}
|
||||
}
|
287
cmd/frpc/main.go
287
cmd/frpc/main.go
@@ -15,291 +15,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
"github.com/rodaine/table"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile string = "./frpc.ini"
|
||||
)
|
||||
|
||||
var usage string = `frpc is the client of frp
|
||||
|
||||
Usage:
|
||||
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
|
||||
frpc reload [-c config_file]
|
||||
frpc status [-c config_file]
|
||||
frpc -h | --help
|
||||
frpc -v | --version
|
||||
|
||||
Options:
|
||||
-c config_file set config file
|
||||
-L log_file set output log file, including console
|
||||
--log-level=<log_level> set log level: debug, info, warn, error
|
||||
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
|
||||
-h --help show this screen
|
||||
-v --version show version
|
||||
`
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
confFile := "./frpc.ini"
|
||||
// the configures parsed from file will be replaced by those from command line if exist
|
||||
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
|
||||
crypto.DefaultSalt = "frp"
|
||||
|
||||
if args["-c"] != nil {
|
||||
confFile = args["-c"].(string)
|
||||
}
|
||||
|
||||
conf, err := ini.LoadFile(confFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ClientCommonCfg.ConfigFile = confFile
|
||||
|
||||
// check if reload command
|
||||
if args["reload"] != nil {
|
||||
if args["reload"].(bool) {
|
||||
if err = CmdReload(); err != nil {
|
||||
fmt.Printf("frps reload error: %v\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Printf("reload success\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if status command
|
||||
if args["status"] != nil {
|
||||
if args["status"].(bool) {
|
||||
if err = CmdStatus(); err != nil {
|
||||
fmt.Printf("frps get status error: %v\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args["-L"] != nil {
|
||||
if args["-L"].(string) == "console" {
|
||||
config.ClientCommonCfg.LogWay = "console"
|
||||
} else {
|
||||
config.ClientCommonCfg.LogWay = "file"
|
||||
config.ClientCommonCfg.LogFile = args["-L"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if args["--log-level"] != nil {
|
||||
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
|
||||
}
|
||||
|
||||
if args["--server-addr"] != nil {
|
||||
addr := strings.Split(args["--server-addr"].(string), ":")
|
||||
if len(addr) != 2 {
|
||||
fmt.Println("--server-addr format error: example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
serverPort, err := strconv.ParseInt(addr[1], 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ClientCommonCfg.ServerAddr = addr[0]
|
||||
config.ClientCommonCfg.ServerPort = int(serverPort)
|
||||
}
|
||||
|
||||
if args["-v"] != nil {
|
||||
if args["-v"].(bool) {
|
||||
fmt.Println(version.Full())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
||||
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
||||
|
||||
svr := client.NewService(pxyCfgs, visitorCfgs)
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if config.ClientCommonCfg.Protocol == "kcp" {
|
||||
go HandleSignal(svr)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSignal(svr *client.Service) {
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
svr.Close()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func CmdReload() error {
|
||||
if config.ClientCommonCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
|
||||
config.ClientCommonCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.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 nil
|
||||
}
|
||||
|
||||
func CmdStatus() error {
|
||||
if config.ClientCommonCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/status", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
|
||||
config.ClientCommonCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
sub.Execute()
|
||||
}
|
||||
|
96
cmd/frpc/sub/http.go
Normal file
96
cmd/frpc/sub/http.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
|
||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
|
||||
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
|
||||
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
|
||||
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
|
||||
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(httpCmd)
|
||||
}
|
||||
|
||||
var httpCmd = &cobra.Command{
|
||||
Use: "http",
|
||||
Short: "Run frpc with a single http proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.HttpProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.HttpProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||
cfg.SubDomain = subDomain
|
||||
cfg.Locations = strings.Split(locations, ",")
|
||||
cfg.HttpUser = httpUser
|
||||
cfg.HttpPwd = httpPwd
|
||||
cfg.HostHeaderRewrite = hostHeaderRewrite
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
88
cmd/frpc/sub/https.go
Normal file
88
cmd/frpc/sub/https.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
|
||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(httpsCmd)
|
||||
}
|
||||
|
||||
var httpsCmd = &cobra.Command{
|
||||
Use: "https",
|
||||
Short: "Run frpc with a single https proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.HttpsProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.HttpsProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||
cfg.SubDomain = subDomain
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
92
cmd/frpc/sub/reload.go
Normal file
92
cmd/frpc/sub/reload.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(reloadCmd)
|
||||
}
|
||||
|
||||
var reloadCmd = &cobra.Command{
|
||||
Use: "reload",
|
||||
Short: "Hot-Reload frpc configuration",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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)
|
||||
}
|
||||
|
||||
err = reload()
|
||||
if err != nil {
|
||||
fmt.Printf("frpc reload error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("reload success\n")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func reload() error {
|
||||
if g.GlbClientCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||
g.GlbClientCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return nil
|
||||
}
|
224
cmd/frpc/sub/root.go
Normal file
224
cmd/frpc/sub/root.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
)
|
||||
|
||||
const (
|
||||
CfgFileTypeIni = iota
|
||||
CfgFileTypeCmd
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
|
||||
serverAddr string
|
||||
user string
|
||||
protocol string
|
||||
token string
|
||||
logLevel string
|
||||
logFile string
|
||||
logMaxDays int
|
||||
|
||||
proxyName string
|
||||
localIp string
|
||||
localPort int
|
||||
remotePort int
|
||||
useEncryption bool
|
||||
useCompression bool
|
||||
customDomains string
|
||||
subDomain string
|
||||
httpUser string
|
||||
httpPwd string
|
||||
locations string
|
||||
hostHeaderRewrite string
|
||||
role string
|
||||
sk string
|
||||
serverName string
|
||||
bindAddr string
|
||||
bindPort int
|
||||
|
||||
kcpDoneCh chan struct{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "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{
|
||||
Use: "frpc",
|
||||
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if showVersion {
|
||||
fmt.Println(version.Full())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not show command usage here.
|
||||
err := runClient(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSignal(svr *client.Service) {
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
svr.Close()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
close(kcpDoneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfg(fileType int, content string) (err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseClientCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseClientCommonCfgFromCmd()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.GlbClientCfg.ClientCommonConf.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.GlbClientCfg.ClientCommonConf = *cfg
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (err error) {
|
||||
strs := strings.Split(serverAddr, ":")
|
||||
if len(strs) < 2 {
|
||||
err = fmt.Errorf("invalid server_addr")
|
||||
return
|
||||
}
|
||||
if strs[0] != "" {
|
||||
g.GlbClientCfg.ServerAddr = strs[0]
|
||||
}
|
||||
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid server_addr")
|
||||
return
|
||||
}
|
||||
|
||||
g.GlbClientCfg.User = user
|
||||
g.GlbClientCfg.Protocol = protocol
|
||||
g.GlbClientCfg.Token = token
|
||||
g.GlbClientCfg.LogLevel = logLevel
|
||||
g.GlbClientCfg.LogFile = logFile
|
||||
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
||||
if logFile == "console" {
|
||||
g.GlbClientCfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) (err error) {
|
||||
var content string
|
||||
content, err = config.GetRenderedConfFromFile(cfgFilePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
g.GlbClientCfg.CfgFile = cfgFilePath
|
||||
|
||||
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = startService(pxyCfgs, visitorCfgs)
|
||||
return
|
||||
}
|
||||
|
||||
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
|
||||
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
|
||||
if g.GlbClientCfg.DnsServer != "" {
|
||||
s := g.GlbClientCfg.DnsServer
|
||||
if !strings.Contains(s, ":") {
|
||||
s += ":53"
|
||||
}
|
||||
// Change default dns server for frpc
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return net.Dial("udp", s)
|
||||
},
|
||||
}
|
||||
}
|
||||
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
go handleSignal(svr)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
<-kcpDoneCh
|
||||
}
|
||||
return
|
||||
}
|
153
cmd/frpc/sub/status.go
Normal file
153
cmd/frpc/sub/status.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
}
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Overview of all proxies status",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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)
|
||||
}
|
||||
|
||||
err = status()
|
||||
if err != nil {
|
||||
fmt.Printf("frpc get status error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func status() error {
|
||||
if g.GlbClientCfg.AdminPort == 0 {
|
||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+
|
||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||
g.GlbClientCfg.AdminPwd))
|
||||
|
||||
req.Header.Add("Authorization", authStr)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
113
cmd/frpc/sub/stcp.go
Normal file
113
cmd/frpc/sub/stcp.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
|
||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(stcpCmd)
|
||||
}
|
||||
|
||||
var stcpCmd = &cobra.Command{
|
||||
Use: "stcp",
|
||||
Short: "Run frpc with a single stcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := make(map[string]config.ProxyConf)
|
||||
visitorConfs := make(map[string]config.VisitorConf)
|
||||
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
cfg := &config.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)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
85
cmd/frpc/sub/tcp.go
Normal file
85
cmd/frpc/sub/tcp.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
|
||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(tcpCmd)
|
||||
}
|
||||
|
||||
var tcpCmd = &cobra.Command{
|
||||
Use: "tcp",
|
||||
Short: "Run frpc with a single tcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.TcpProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.TcpProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
cfg.RemotePort = remotePort
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
85
cmd/frpc/sub/udp.go
Normal file
85
cmd/frpc/sub/udp.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
|
||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(udpCmd)
|
||||
}
|
||||
|
||||
var udpCmd = &cobra.Command{
|
||||
Use: "udp",
|
||||
Short: "Run frpc with a single udp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.UdpProxyConf{}
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.UdpProxy
|
||||
cfg.LocalIp = localIp
|
||||
cfg.LocalPort = localPort
|
||||
cfg.RemotePort = remotePort
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
|
||||
err = cfg.CheckForCli()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := map[string]config.ProxyConf{
|
||||
cfg.ProxyName: cfg,
|
||||
}
|
||||
err = startService(proxyConfs, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
113
cmd/frpc/sub/xtcp.go
Normal file
113
cmd/frpc/sub/xtcp.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
|
||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||
|
||||
rootCmd.AddCommand(xtcpCmd)
|
||||
}
|
||||
|
||||
var xtcpCmd = &cobra.Command{
|
||||
Use: "xtcp",
|
||||
Short: "Run frpc with a single xtcp proxy",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
proxyConfs := make(map[string]config.ProxyConf)
|
||||
visitorConfs := make(map[string]config.VisitorConf)
|
||||
|
||||
var prefix string
|
||||
if user != "" {
|
||||
prefix = user + "."
|
||||
}
|
||||
|
||||
if role == "server" {
|
||||
cfg := &config.XtcpProxyConf{}
|
||||
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.XtcpVisitorConf{}
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
101
cmd/frps/main.go
101
cmd/frps/main.go
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
// 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.
|
||||
@@ -15,104 +15,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/server"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||
)
|
||||
|
||||
var usage string = `frps is the server of frp
|
||||
|
||||
Usage:
|
||||
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
|
||||
frps -h | --help
|
||||
frps -v | --version
|
||||
|
||||
Options:
|
||||
-c config_file set config file
|
||||
-L log_file set output log file, including console
|
||||
--log-level=<log_level> set log level: debug, info, warn, error
|
||||
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
|
||||
-h --help show this screen
|
||||
-v --version show version
|
||||
`
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
confFile := "./frps.ini"
|
||||
// the configures parsed from file will be replaced by those from command line if exist
|
||||
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
|
||||
crypto.DefaultSalt = "frp"
|
||||
|
||||
if args["-c"] != nil {
|
||||
confFile = args["-c"].(string)
|
||||
}
|
||||
|
||||
conf, err := ini.LoadFile(confFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if args["-L"] != nil {
|
||||
if args["-L"].(string) == "console" {
|
||||
config.ServerCommonCfg.LogWay = "console"
|
||||
} else {
|
||||
config.ServerCommonCfg.LogWay = "file"
|
||||
config.ServerCommonCfg.LogFile = args["-L"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if args["--log-level"] != nil {
|
||||
config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
|
||||
}
|
||||
|
||||
if args["--addr"] != nil {
|
||||
addr := strings.Split(args["--addr"].(string), ":")
|
||||
if len(addr) != 2 {
|
||||
fmt.Println("--addr format error: example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println("--addr format error, example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ServerCommonCfg.BindAddr = addr[0]
|
||||
config.ServerCommonCfg.BindPort = int(bindPort)
|
||||
}
|
||||
|
||||
if args["-v"] != nil {
|
||||
if args["-v"].(bool) {
|
||||
fmt.Println(version.Full())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
|
||||
config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
|
||||
|
||||
svr, err := server.NewService()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Start frps success")
|
||||
if config.ServerCommonCfg.PrivilegeMode == true {
|
||||
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
|
||||
}
|
||||
server.ServerService = svr
|
||||
svr.Run()
|
||||
Execute()
|
||||
}
|
||||
|
208
cmd/frps/root.go
Normal file
208
cmd/frps/root.go
Normal file
@@ -0,0 +1,208 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
CfgFileTypeIni = iota
|
||||
CfgFileTypeCmd
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
|
||||
bindAddr string
|
||||
bindPort int
|
||||
bindUdpPort int
|
||||
kcpBindPort int
|
||||
proxyBindAddr string
|
||||
vhostHttpPort int
|
||||
vhostHttpsPort int
|
||||
vhostHttpTimeout int64
|
||||
dashboardAddr string
|
||||
dashboardPort int
|
||||
dashboardUser string
|
||||
dashboardPwd string
|
||||
assetsDir string
|
||||
logFile string
|
||||
logLevel string
|
||||
logMaxDays int64
|
||||
token string
|
||||
subDomainHost string
|
||||
tcpMux bool
|
||||
allowPorts string
|
||||
maxPoolCount int64
|
||||
maxPortsPerClient int64
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "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")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
||||
rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port")
|
||||
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
|
||||
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
|
||||
rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
|
||||
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
|
||||
rootCmd.PersistentFlags().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")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frps",
|
||||
Short: "frps is the server of frp (https://github.com/fatedier/frp)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if showVersion {
|
||||
fmt.Println(version.Full())
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if 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, "")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = runServer()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerCommonCfg(fileType int, content string) (err error) {
|
||||
if fileType == CfgFileTypeIni {
|
||||
err = parseServerCommonCfgFromIni(content)
|
||||
} else if fileType == CfgFileTypeCmd {
|
||||
err = parseServerCommonCfgFromCmd()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.GlbServerCfg.ServerCommonConf.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
|
||||
return
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromIni(content string) (err error) {
|
||||
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.GlbServerCfg.ServerCommonConf = *cfg
|
||||
return
|
||||
}
|
||||
|
||||
func parseServerCommonCfgFromCmd() (err error) {
|
||||
g.GlbServerCfg.BindAddr = bindAddr
|
||||
g.GlbServerCfg.BindPort = bindPort
|
||||
g.GlbServerCfg.BindUdpPort = bindUdpPort
|
||||
g.GlbServerCfg.KcpBindPort = kcpBindPort
|
||||
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
|
||||
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
|
||||
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
|
||||
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
|
||||
g.GlbServerCfg.DashboardAddr = dashboardAddr
|
||||
g.GlbServerCfg.DashboardPort = dashboardPort
|
||||
g.GlbServerCfg.DashboardUser = dashboardUser
|
||||
g.GlbServerCfg.DashboardPwd = dashboardPwd
|
||||
g.GlbServerCfg.LogFile = logFile
|
||||
g.GlbServerCfg.LogLevel = logLevel
|
||||
g.GlbServerCfg.LogMaxDays = logMaxDays
|
||||
g.GlbServerCfg.Token = token
|
||||
g.GlbServerCfg.SubDomainHost = subDomainHost
|
||||
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.GlbClientCfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func runServer() (err error) {
|
||||
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
|
||||
g.GlbServerCfg.LogMaxDays)
|
||||
svr, err := server.NewService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Start frps success")
|
||||
server.ServerService = svr
|
||||
svr.Run()
|
||||
return
|
||||
}
|
@@ -5,9 +5,10 @@
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
|
||||
# if you want to connect frps by http proxy or socks5 proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
# http_proxy = socks5://user:passwd@192.168.1.128:1080
|
||||
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
@@ -18,7 +19,7 @@ log_level = info
|
||||
log_max_days = 3
|
||||
|
||||
# for authentication
|
||||
privilege_token = 12345678
|
||||
token = 12345678
|
||||
|
||||
# set admin address for control frpc's action by http api such as reload
|
||||
admin_addr = 127.0.0.1
|
||||
@@ -40,9 +41,12 @@ 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
|
||||
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start divided by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
@@ -52,10 +56,10 @@ protocol = tcp
|
||||
# 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
|
||||
@@ -65,14 +69,37 @@ 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
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# if remote_port is 0, frps will assgin a random port for you
|
||||
# if remote_port is 0, frps will assign a random port for you
|
||||
remote_port = 0
|
||||
|
||||
# if you want to expose multiple ports, add 'range:' prefix to the section name
|
||||
# frpc will generate multiple proxies such as 'tcp_port_6010', 'tcp_port_6011' and so on.
|
||||
[range:tcp_port]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6010-6020,6022,6024-6028
|
||||
remote_port = 6010-6020,6022,6024-6028
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
@@ -81,6 +108,14 @@ remote_port = 6002
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[range:udp_port]
|
||||
type = udp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6010-6020
|
||||
remote_port = 6010-6020
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||
[web01]
|
||||
type = http
|
||||
@@ -98,13 +133,22 @@ 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
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
use_compression = false
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
|
||||
@@ -114,7 +158,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]
|
||||
@@ -124,6 +168,22 @@ plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_socks5]
|
||||
type = tcp
|
||||
remote_port = 6005
|
||||
plugin = socks5
|
||||
plugin_user = abc
|
||||
plugin_passwd = abc
|
||||
|
||||
[plugin_static_file]
|
||||
type = tcp
|
||||
remote_port = 6006
|
||||
plugin = static_file
|
||||
plugin_local_path = /var/www/blog
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[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
|
||||
|
@@ -16,16 +16,20 @@ kcp_bind_port = 7000
|
||||
# proxy_bind_addr = 127.0.0.1
|
||||
|
||||
# if you want to support virtual host, you must set the http port for listening (optional)
|
||||
# Note: http port and https port can be same with bind_port
|
||||
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
|
||||
dashboard_addr = 0.0.0.0
|
||||
dashboard_port = 7500
|
||||
|
||||
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
||||
# dashboard user and passwd for basic auth protect, if not set, both default value is admin
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
|
||||
@@ -39,22 +43,21 @@ log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# privilege mode is the only supported mode since v0.10.0
|
||||
privilege_token = 12345678
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_timeout is 90
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
|
||||
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
|
||||
max_pool_count = 5
|
||||
|
||||
# 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
|
||||
# max ports can be used for each client, default value is 0 means no limit
|
||||
max_ports_per_client = 0
|
||||
|
||||
# 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
|
||||
|
BIN
doc/pic/zsxq.jpg
Normal file
BIN
doc/pic/zsxq.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@@ -1,135 +0,0 @@
|
||||
# Quick Start
|
||||
|
||||
frp is easier to use compared with other similar projects.
|
||||
|
||||
We will use two simple demo to demonstrate how to use frp.
|
||||
|
||||
1. How to create a connection to **server A**'s **ssh port** by **server B** with **public IP address** x.x.x.x(replace to the real IP address of your server).
|
||||
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
|
||||
|
||||
### Download SourceCode
|
||||
|
||||
`go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`.
|
||||
|
||||
Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`.
|
||||
|
||||
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
|
||||
|
||||
### Compile
|
||||
|
||||
Enter the root directory and execute `make`, then wait until finished.
|
||||
|
||||
**bin** include all executable programs when **conf** include corresponding configuration files.
|
||||
|
||||
### Pre-requirement
|
||||
|
||||
* Go environment. Version of go >= 1.4.
|
||||
* Godep (if not exist, `go get` will be executed to download godep when compiling)
|
||||
|
||||
### Deploy
|
||||
|
||||
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
|
||||
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
|
||||
3. Modify all configuration files, details in next paragraph.
|
||||
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
|
||||
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
|
||||
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
|
||||
|
||||
## Tcp port forwarding
|
||||
|
||||
### Configuration files
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
# for accept connections from frpc
|
||||
bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# finally we connect to server A by this port
|
||||
listen_port = 6000
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
# server address of frps
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
|
||||
# ssh is proxy name same with configure in frps.ini
|
||||
[ssh]
|
||||
# local port which need to be transferred
|
||||
local_port = 22
|
||||
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## Http port forwarding and Custom domains binding
|
||||
|
||||
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
|
||||
|
||||
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
|
||||
|
||||
After that, you can visit your web pages in local server by custom domains.
|
||||
|
||||
### Configuration files
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# if you want to support vhost, specify one port for http services
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
type = http
|
||||
auth_token = 123
|
||||
# # if proxy type equals http, custom_domains must be set separated by commas
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
# custom domains are set in frps.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# encryption is optional, default is false
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
@@ -1,137 +0,0 @@
|
||||
# frp 使用文档
|
||||
|
||||
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
|
||||
|
||||
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。
|
||||
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务,**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
|
||||
|
||||
### 下载源码
|
||||
|
||||
推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。
|
||||
|
||||
或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。
|
||||
|
||||
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
|
||||
|
||||
### 编译
|
||||
|
||||
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
|
||||
|
||||
编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。
|
||||
|
||||
### 依赖
|
||||
|
||||
* go 1.4 以上版本
|
||||
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
|
||||
|
||||
### 部署
|
||||
|
||||
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
|
||||
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
|
||||
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
|
||||
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`。
|
||||
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`。
|
||||
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**({user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
|
||||
|
||||
## tcp 端口转发
|
||||
|
||||
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
|
||||
|
||||
### 配置文件
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
# 用于接收 frpc 连接的端口
|
||||
bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# ssh 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# 最后将通过此端口访问后端服务
|
||||
listen_port = 6000
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
# frps 所在服务器绑定的IP地址
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# 用于身份验证
|
||||
auth_token = 123
|
||||
|
||||
# ssh 需要和 frps.ini 中配置一致
|
||||
[ssh]
|
||||
# 需要转发的本地端口
|
||||
local_port = 22
|
||||
# 启用加密,frpc与frps之间通信加密,默认为 false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## http 端口转发,自定义域名绑定
|
||||
|
||||
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口,则需要指定代理的类型为 http,并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
|
||||
|
||||
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
|
||||
|
||||
之后就可以通过自定义域名访问到本地的多个 web 服务。
|
||||
|
||||
### 配置文件
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# 如果需要支持http类型的代理则需要指定一个端口
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
# type 默认为 tcp,这里需要特别指定为 http
|
||||
type = http
|
||||
auth_token = 123
|
||||
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
|
||||
# 自定义域名在 frps.ini 中配置,方便做统一管理
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# 可选是否加密
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
32
g/g.go
Normal file
32
g/g.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
|
||||
var (
|
||||
GlbClientCfg *ClientCfg
|
||||
GlbServerCfg *ServerCfg
|
||||
)
|
||||
|
||||
func init() {
|
||||
GlbClientCfg = &ClientCfg{
|
||||
ClientCommonConf: *config.GetDefaultClientConf(),
|
||||
}
|
||||
GlbServerCfg = &ServerCfg{
|
||||
ServerCommonConf: *config.GetDefaultServerConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type ClientCfg struct {
|
||||
config.ClientCommonConf
|
||||
|
||||
CfgFile string
|
||||
ServerUdpPort int // this is configured by login response from frps
|
||||
}
|
||||
|
||||
type ServerCfg struct {
|
||||
config.ServerCommonConf
|
||||
|
||||
CfgFile string
|
||||
}
|
79
glide.lock
generated
79
glide.lock
generated
@@ -1,79 +0,0 @@
|
||||
hash: 4095d78a15bf0e7ffdd63331ce75d7199d663cc8710dcd08b9dcd09ba3183eac
|
||||
updated: 2018-01-23T14:48:38.764359+08:00
|
||||
imports:
|
||||
- name: github.com/armon/go-socks5
|
||||
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docopt/docopt-go
|
||||
version: 784ddc588536785e7299f7272f39101f7faccc3f
|
||||
- name: github.com/fatedier/beego
|
||||
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
|
||||
subpackages:
|
||||
- logs
|
||||
- name: github.com/fatedier/kcp-go
|
||||
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||
- name: github.com/golang/snappy
|
||||
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||
- name: github.com/gorilla/websocket
|
||||
version: 292fd08b2560ad524ee37396253d71570339a821
|
||||
- name: github.com/julienschmidt/httprouter
|
||||
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||
- name: github.com/klauspost/cpuid
|
||||
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
|
||||
- name: github.com/klauspost/reedsolomon
|
||||
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
|
||||
- name: github.com/pkg/errors
|
||||
version: c605e284fe17294bda444b34710735b29d1a9d90
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/rakyll/statik
|
||||
version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
|
||||
subpackages:
|
||||
- fs
|
||||
- name: github.com/rodaine/table
|
||||
version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd
|
||||
- name: github.com/stretchr/testify
|
||||
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||
subpackages:
|
||||
- assert
|
||||
- name: github.com/templexxx/cpufeat
|
||||
version: 3794dfbfb04749f896b521032f69383f24c3687e
|
||||
- name: github.com/templexxx/reedsolomon
|
||||
version: 7092926d7d05c415fabb892b1464a03f8228ab80
|
||||
- name: github.com/templexxx/xor
|
||||
version: 0af8e873c554da75f37f2049cdffda804533d44c
|
||||
- name: github.com/tjfoc/gmsm
|
||||
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
|
||||
subpackages:
|
||||
- sm4
|
||||
- name: github.com/vaughan0/go-ini
|
||||
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
|
||||
- name: github.com/xtaci/kcp-go
|
||||
version: df437e2b8ec365a336200f9d9da53441cf72ed47
|
||||
- name: github.com/xtaci/smux
|
||||
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
|
||||
- name: golang.org/x/crypto
|
||||
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||
subpackages:
|
||||
- blowfish
|
||||
- cast5
|
||||
- pbkdf2
|
||||
- salsa20
|
||||
- salsa20/salsa
|
||||
- tea
|
||||
- twofish
|
||||
- xtea
|
||||
- name: golang.org/x/net
|
||||
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
|
||||
subpackages:
|
||||
- bpf
|
||||
- context
|
||||
- internal/iana
|
||||
- internal/socket
|
||||
- ipv4
|
||||
testImports: []
|
76
glide.yaml
76
glide.yaml
@@ -1,76 +0,0 @@
|
||||
package: github.com/fatedier/frp
|
||||
import:
|
||||
- package: github.com/armon/go-socks5
|
||||
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||
- package: github.com/davecgh/go-spew
|
||||
version: v1.1.0
|
||||
subpackages:
|
||||
- spew
|
||||
- package: github.com/docopt/docopt-go
|
||||
version: 0.6.2
|
||||
- package: github.com/fatedier/beego
|
||||
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
|
||||
subpackages:
|
||||
- logs
|
||||
- package: github.com/fatedier/kcp-go
|
||||
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||
- package: github.com/golang/snappy
|
||||
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||
- package: github.com/julienschmidt/httprouter
|
||||
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||
- package: github.com/klauspost/cpuid
|
||||
version: v1.0
|
||||
- package: github.com/klauspost/reedsolomon
|
||||
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
|
||||
- package: github.com/pkg/errors
|
||||
version: c605e284fe17294bda444b34710735b29d1a9d90
|
||||
- package: github.com/pmezard/go-difflib
|
||||
version: v1.0.0
|
||||
subpackages:
|
||||
- difflib
|
||||
- package: github.com/rakyll/statik
|
||||
version: v0.1.0
|
||||
subpackages:
|
||||
- fs
|
||||
- package: github.com/stretchr/testify
|
||||
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||
subpackages:
|
||||
- assert
|
||||
- package: github.com/templexxx/cpufeat
|
||||
version: 3794dfbfb04749f896b521032f69383f24c3687e
|
||||
- package: github.com/templexxx/reedsolomon
|
||||
version: 7092926d7d05c415fabb892b1464a03f8228ab80
|
||||
- package: github.com/templexxx/xor
|
||||
version: 0.1.2
|
||||
- package: github.com/tjfoc/gmsm
|
||||
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
|
||||
subpackages:
|
||||
- sm4
|
||||
- package: github.com/vaughan0/go-ini
|
||||
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
|
||||
- package: github.com/xtaci/kcp-go
|
||||
version: v3.17
|
||||
- package: github.com/xtaci/smux
|
||||
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
|
||||
- package: golang.org/x/crypto
|
||||
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||
subpackages:
|
||||
- blowfish
|
||||
- cast5
|
||||
- pbkdf2
|
||||
- salsa20
|
||||
- salsa20/salsa
|
||||
- tea
|
||||
- twofish
|
||||
- xtea
|
||||
- package: golang.org/x/net
|
||||
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
|
||||
subpackages:
|
||||
- bpf
|
||||
- context
|
||||
- internal/iana
|
||||
- internal/socket
|
||||
- ipv4
|
||||
- package: github.com/rodaine/table
|
||||
version: v1.0.0
|
||||
- package: github.com/gorilla/websocket
|
32
go.mod
Normal file
32
go.mod
Normal file
@@ -0,0 +1,32 @@
|
||||
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 v0.0.0-20171023144637-cd167d2f15f4
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gorilla/websocket v1.2.0
|
||||
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/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/reedsolomon v0.0.0-20170926020725-5e06b81a1c76 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
|
||||
)
|
30
go.sum
Normal file
30
go.sum
Normal file
@@ -0,0 +1,30 @@
|
||||
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 v0.0.0-20171023144637-cd167d2f15f4/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
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/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/reedsolomon v0.0.0-20170926020725-5e06b81a1c76/go.mod h1:ToWcj2sZ6xHl14JjZiVDktYpFtrFZJXBlsu7TV23lNg=
|
||||
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=
|
@@ -23,46 +23,41 @@ import (
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var ClientCommonCfg *ClientCommonConf
|
||||
|
||||
// client common config
|
||||
type ClientCommonConf struct {
|
||||
ConfigFile string
|
||||
ServerAddr string
|
||||
ServerPort int
|
||||
ServerUdpPort int // this is specified by login response message from frps
|
||||
HttpProxy string
|
||||
LogFile string
|
||||
LogWay string
|
||||
LogLevel string
|
||||
LogMaxDays int64
|
||||
PrivilegeToken string
|
||||
AdminAddr string
|
||||
AdminPort int
|
||||
AdminUser string
|
||||
AdminPwd string
|
||||
PoolCount int
|
||||
TcpMux bool
|
||||
User string
|
||||
LoginFailExit bool
|
||||
Start map[string]struct{}
|
||||
Protocol string
|
||||
HeartBeatInterval int64
|
||||
HeartBeatTimeout int64
|
||||
ServerAddr string `json:"server_addr"`
|
||||
ServerPort int `json:"server_port"`
|
||||
HttpProxy string `json:"http_proxy"`
|
||||
LogFile string `json:"log_file"`
|
||||
LogWay string `json:"log_way"`
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
AdminAddr string `json:"admin_addr"`
|
||||
AdminPort int `json:"admin_port"`
|
||||
AdminUser string `json:"admin_user"`
|
||||
AdminPwd string `json:"admin_pwd"`
|
||||
PoolCount int `json:"pool_count"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
User string `json:"user"`
|
||||
DnsServer string `json:"dns_server"`
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
Start map[string]struct{} `json:"start"`
|
||||
Protocol string `json:"protocol"`
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
}
|
||||
|
||||
func GetDeaultClientCommonConf() *ClientCommonConf {
|
||||
func GetDefaultClientConf() *ClientCommonConf {
|
||||
return &ClientCommonConf{
|
||||
ConfigFile: "./frpc.ini",
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
ServerUdpPort: 0,
|
||||
HttpProxy: "",
|
||||
HttpProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
PrivilegeToken: "",
|
||||
Token: "",
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
@@ -70,6 +65,7 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
|
||||
PoolCount: 1,
|
||||
TcpMux: true,
|
||||
User: "",
|
||||
DnsServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
@@ -78,21 +74,28 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
|
||||
}
|
||||
}
|
||||
|
||||
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
||||
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
|
||||
cfg = defaultCfg
|
||||
if cfg == nil {
|
||||
cfg = GetDefaultClientConf()
|
||||
}
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
cfg = GetDeaultClientCommonConf()
|
||||
|
||||
tmpStr, ok = conf.Get("common", "server_addr")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
|
||||
cfg.ServerAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "server_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "server_port"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid server_port")
|
||||
@@ -101,16 +104,11 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
||||
cfg.ServerPort = int(v)
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "http_proxy")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||
cfg.HttpProxy = tmpStr
|
||||
} else {
|
||||
// get http_proxy from env
|
||||
cfg.HttpProxy = os.Getenv("http_proxy")
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_file")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
@@ -119,30 +117,25 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_level")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "privilege_token")
|
||||
if ok {
|
||||
cfg.PrivilegeToken = tmpStr
|
||||
if tmpStr, ok = conf.Get("common", "token"); ok {
|
||||
cfg.Token = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "admin_addr")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
||||
cfg.AdminAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "admin_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.AdminPort = int(v)
|
||||
} else {
|
||||
@@ -151,66 +144,58 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "admin_user")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
|
||||
cfg.AdminUser = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "admin_pwd")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
|
||||
cfg.AdminPwd = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "pool_count")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
cfg.PoolCount = 1
|
||||
} else {
|
||||
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||
cfg.PoolCount = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "tcp_mux")
|
||||
if ok && tmpStr == "false" {
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "user")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "user"); ok {
|
||||
cfg.User = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "start")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
|
||||
cfg.DnsServer = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "start"); ok {
|
||||
proxyNames := strings.Split(tmpStr, ",")
|
||||
for _, name := range proxyNames {
|
||||
cfg.Start[strings.TrimSpace(name)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "login_fail_exit")
|
||||
if ok && tmpStr == "false" {
|
||||
if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
|
||||
cfg.LoginFailExit = false
|
||||
} else {
|
||||
cfg.LoginFailExit = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "protocol")
|
||||
if ok {
|
||||
// Now it only support tcp and kcp.
|
||||
if tmpStr != "kcp" {
|
||||
tmpStr = "tcp"
|
||||
if tmpStr, ok = conf.Get("common", "protocol"); ok {
|
||||
// Now it only support tcp and kcp and websocket.
|
||||
if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
|
||||
err = fmt.Errorf("Parse conf error: invalid protocol")
|
||||
return
|
||||
}
|
||||
cfg.Protocol = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||
return
|
||||
} else {
|
||||
@@ -218,17 +203,18 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "heartbeat_interval")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatInterval = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Check() (err error) {
|
||||
if cfg.HeartBeatInterval <= 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||
return
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -20,94 +20,115 @@ import (
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
)
|
||||
|
||||
var ServerCommonCfg *ServerCommonConf
|
||||
var (
|
||||
// server global configure used for generate proxy conf used in frps
|
||||
proxyBindAddr string
|
||||
subDomainHost string
|
||||
vhostHttpPort int
|
||||
vhostHttpsPort int
|
||||
)
|
||||
|
||||
func InitServerCfg(cfg *ServerCommonConf) {
|
||||
proxyBindAddr = cfg.ProxyBindAddr
|
||||
subDomainHost = cfg.SubDomainHost
|
||||
vhostHttpPort = cfg.VhostHttpPort
|
||||
vhostHttpsPort = cfg.VhostHttpsPort
|
||||
}
|
||||
|
||||
// common config
|
||||
type ServerCommonConf struct {
|
||||
ConfigFile string
|
||||
BindAddr string
|
||||
BindPort int
|
||||
BindUdpPort int
|
||||
KcpBindPort int
|
||||
ProxyBindAddr string
|
||||
BindAddr string `json:"bind_addr"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUdpPort int `json:"bind_udp_port"`
|
||||
KcpBindPort int `json:"kcp_bind_port"`
|
||||
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||
|
||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
||||
VhostHttpPort int
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
|
||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||
VhostHttpsPort int
|
||||
DashboardAddr string
|
||||
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
|
||||
DashboardUser string
|
||||
DashboardPwd string
|
||||
AssetsDir string
|
||||
LogFile string
|
||||
LogWay string // console or file
|
||||
LogLevel string
|
||||
LogMaxDays int64
|
||||
PrivilegeMode bool
|
||||
PrivilegeToken string
|
||||
AuthTimeout int64
|
||||
SubDomainHost string
|
||||
TcpMux bool
|
||||
DashboardPort int `json:"dashboard_port"`
|
||||
DashboardUser string `json:"dashboard_user"`
|
||||
DashboardPwd string `json:"dashboard_pwd"`
|
||||
AssetsDir string `json:"asserts_dir"`
|
||||
LogFile string `json:"log_file"`
|
||||
LogWay string `json:"log_way"` // console or file
|
||||
LogLevel string `json:"log_level"`
|
||||
LogMaxDays int64 `json:"log_max_days"`
|
||||
Token string `json:"token"`
|
||||
SubDomainHost string `json:"subdomain_host"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
|
||||
PrivilegeAllowPorts map[int]struct{}
|
||||
MaxPoolCount int64
|
||||
HeartBeatTimeout int64
|
||||
UserConnTimeout int64
|
||||
AllowPorts map[int]struct{}
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||
}
|
||||
|
||||
func GetDefaultServerCommonConf() *ServerCommonConf {
|
||||
func GetDefaultServerConf() *ServerCommonConf {
|
||||
return &ServerCommonConf{
|
||||
ConfigFile: "./frps.ini",
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUdpPort: 0,
|
||||
KcpBindPort: 0,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHttpPort: 0,
|
||||
VhostHttpsPort: 0,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
PrivilegeMode: true,
|
||||
PrivilegeToken: "",
|
||||
AuthTimeout: 900,
|
||||
SubDomainHost: "",
|
||||
TcpMux: true,
|
||||
PrivilegeAllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUdpPort: 0,
|
||||
KcpBindPort: 0,
|
||||
ProxyBindAddr: "0.0.0.0",
|
||||
VhostHttpPort: 0,
|
||||
VhostHttpsPort: 0,
|
||||
VhostHttpTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
Token: "",
|
||||
SubDomainHost: "",
|
||||
TcpMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
}
|
||||
}
|
||||
|
||||
// Load server common configure.
|
||||
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
|
||||
cfg = defaultCfg
|
||||
if cfg == nil {
|
||||
cfg = GetDefaultServerConf()
|
||||
}
|
||||
|
||||
conf, err := ini.Load(strings.NewReader(content))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
cfg = GetDefaultServerCommonConf()
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_addr")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
|
||||
cfg.BindAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_port")
|
||||
return
|
||||
@@ -116,8 +137,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_udp_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
|
||||
return
|
||||
@@ -126,8 +146,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "kcp_bind_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
|
||||
return
|
||||
@@ -136,15 +155,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "proxy_bind_addr")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
|
||||
cfg.ProxyBindAddr = tmpStr
|
||||
} else {
|
||||
cfg.ProxyBindAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "vhost_http_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
|
||||
return
|
||||
@@ -155,8 +172,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
cfg.VhostHttpPort = 0
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "vhost_https_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
|
||||
return
|
||||
@@ -167,15 +183,23 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
cfg.VhostHttpsPort = 0
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_addr")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil || v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
|
||||
return
|
||||
} else {
|
||||
cfg.VhostHttpTimeout = v
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
|
||||
cfg.DashboardAddr = tmpStr
|
||||
} else {
|
||||
cfg.DashboardAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_port")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
|
||||
return
|
||||
@@ -186,23 +210,19 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
cfg.DashboardPort = 0
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_user")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
|
||||
cfg.DashboardUser = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_pwd")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
|
||||
cfg.DashboardPwd = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "assets_dir")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||
cfg.AssetsDir = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_file")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
@@ -211,107 +231,69 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_level")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "privilege_mode")
|
||||
if ok {
|
||||
if tmpStr == "true" {
|
||||
cfg.PrivilegeMode = true
|
||||
cfg.Token, _ = conf.Get("common", "token")
|
||||
|
||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
cfg.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// PrivilegeMode configure
|
||||
if cfg.PrivilegeMode == true {
|
||||
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")
|
||||
|
||||
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
|
||||
if ok {
|
||||
// e.g. 1000-2000,2001,2002,3000-4000
|
||||
portRanges := strings.Split(allowPortsStr, ",")
|
||||
for _, portRangeStr := range portRanges {
|
||||
// 1000-2000 or 2001
|
||||
portArray := strings.Split(portRangeStr, "-")
|
||||
// length: only 1 or 2 is correct
|
||||
rangeType := len(portArray)
|
||||
if rangeType == 1 {
|
||||
// single port
|
||||
singlePort, errRet := strconv.ParseInt(portArray[0], 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
|
||||
return
|
||||
}
|
||||
cfg.PrivilegeAllowPorts[int(singlePort)] = struct{}{}
|
||||
} else if rangeType == 2 {
|
||||
// range ports
|
||||
min, errRet := strconv.ParseInt(portArray[0], 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
|
||||
return
|
||||
}
|
||||
max, errRet := strconv.ParseInt(portArray[1], 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
|
||||
return
|
||||
}
|
||||
if max < min {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
|
||||
return
|
||||
}
|
||||
for i := min; i <= max; i++ {
|
||||
cfg.PrivilegeAllowPorts[int(i)] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
|
||||
return
|
||||
}
|
||||
if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
} else {
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "max_pool_count")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil && v >= 0 {
|
||||
cfg.MaxPoolCount = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "authentication_timeout")
|
||||
if ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
|
||||
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
} else {
|
||||
cfg.AuthTimeout = v
|
||||
if v < 0 {
|
||||
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||
return
|
||||
}
|
||||
cfg.MaxPortsPerClient = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "subdomain_host")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
|
||||
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "tcp_mux")
|
||||
if ok && tmpStr == "false" {
|
||||
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
|
||||
if ok {
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||
@@ -322,3 +304,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Check() (err error) {
|
||||
return
|
||||
}
|
||||
|
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
|
||||
}
|
@@ -14,8 +14,11 @@
|
||||
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMsgType = errors.New("message type error")
|
||||
ErrMsgType = errors.New("message type error")
|
||||
ErrCtlClosed = errors.New("control is closed")
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
// 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.
|
||||
@@ -12,40 +12,35 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pool
|
||||
package msg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"io"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
jsonMsg "github.com/fatedier/golib/msg/json"
|
||||
)
|
||||
|
||||
func TestPutBuf(t *testing.T) {
|
||||
buf := make([]byte, 512)
|
||||
PutBuf(buf)
|
||||
type Message = jsonMsg.Message
|
||||
|
||||
buf = make([]byte, 1025)
|
||||
PutBuf(buf)
|
||||
var (
|
||||
msgCtl *jsonMsg.MsgCtl
|
||||
)
|
||||
|
||||
buf = make([]byte, 2*1025)
|
||||
PutBuf(buf)
|
||||
|
||||
buf = make([]byte, 5*1025)
|
||||
PutBuf(buf)
|
||||
func init() {
|
||||
msgCtl = jsonMsg.NewMsgCtl()
|
||||
for typeByte, msg := range msgTypeMap {
|
||||
msgCtl.RegisterMsg(typeByte, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBuf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buf := GetBuf(200)
|
||||
assert.Len(buf, 200)
|
||||
|
||||
buf = GetBuf(1025)
|
||||
assert.Len(buf, 1025)
|
||||
|
||||
buf = GetBuf(2 * 1024)
|
||||
assert.Len(buf, 2*1024)
|
||||
|
||||
buf = GetBuf(5 * 2000)
|
||||
assert.Len(buf, 5*2000)
|
||||
func ReadMsg(c io.Reader) (msg Message, err error) {
|
||||
return msgCtl.ReadMsg(c)
|
||||
}
|
||||
|
||||
func ReadMsgInto(c io.Reader, msg Message) (err error) {
|
||||
return msgCtl.ReadMsgInto(c, msg)
|
||||
}
|
||||
|
||||
func WriteMsg(c io.Writer, msg interface{}) (err error) {
|
||||
return msgCtl.WriteMsg(c, msg)
|
||||
}
|
@@ -14,10 +14,7 @@
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
import "net"
|
||||
|
||||
const (
|
||||
TypeLogin = 'o'
|
||||
@@ -40,39 +37,26 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
TypeMap map[byte]reflect.Type
|
||||
TypeStringMap map[reflect.Type]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
TypeMap = make(map[byte]reflect.Type)
|
||||
TypeStringMap = make(map[reflect.Type]byte)
|
||||
|
||||
TypeMap[TypeLogin] = reflect.TypeOf(Login{})
|
||||
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
|
||||
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
|
||||
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
|
||||
TypeMap[TypeCloseProxy] = reflect.TypeOf(CloseProxy{})
|
||||
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
||||
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
||||
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
||||
TypeMap[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{})
|
||||
TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{})
|
||||
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
||||
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
||||
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
||||
TypeMap[TypeNatHoleVisitor] = reflect.TypeOf(NatHoleVisitor{})
|
||||
TypeMap[TypeNatHoleClient] = reflect.TypeOf(NatHoleClient{})
|
||||
TypeMap[TypeNatHoleResp] = reflect.TypeOf(NatHoleResp{})
|
||||
TypeMap[TypeNatHoleSid] = reflect.TypeOf(NatHoleSid{})
|
||||
|
||||
for k, v := range TypeMap {
|
||||
TypeStringMap[v] = k
|
||||
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{},
|
||||
}
|
||||
}
|
||||
|
||||
// Message wraps socket packages for communicating between frpc and frps.
|
||||
type Message interface{}
|
||||
)
|
||||
|
||||
// When frpc start, client send this message to login to server.
|
||||
type Login struct {
|
||||
@@ -102,17 +86,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"`
|
||||
@@ -179,6 +166,7 @@ type NatHoleResp struct {
|
||||
Sid string `json:"sid"`
|
||||
VisitorAddr string `json:"visitor_addr"`
|
||||
ClientAddr string `json:"client_addr"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type NatHoleSid struct {
|
||||
|
@@ -1,87 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
)
|
||||
|
||||
type TestStruct struct{}
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var (
|
||||
msg Message
|
||||
buffer []byte
|
||||
err error
|
||||
)
|
||||
|
||||
// error type
|
||||
msg = &TestStruct{}
|
||||
buffer, err = Pack(msg)
|
||||
assert.Error(err, errors.ErrMsgType.Error())
|
||||
|
||||
// correct
|
||||
msg = &Ping{}
|
||||
buffer, err = Pack(msg)
|
||||
assert.NoError(err)
|
||||
b := bytes.NewBuffer(nil)
|
||||
b.WriteByte(TypePing)
|
||||
binary.Write(b, binary.BigEndian, int64(2))
|
||||
b.WriteString("{}")
|
||||
assert.True(bytes.Equal(b.Bytes(), buffer))
|
||||
}
|
||||
|
||||
func TestUnPack(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var (
|
||||
msg Message
|
||||
err error
|
||||
)
|
||||
|
||||
// error message type
|
||||
msg, err = UnPack('-', []byte("{}"))
|
||||
assert.Error(err)
|
||||
|
||||
// correct
|
||||
msg, err = UnPack(TypePong, []byte("{}"))
|
||||
assert.NoError(err)
|
||||
assert.Equal(reflect.TypeOf(msg).Elem(), reflect.TypeOf(Pong{}))
|
||||
}
|
||||
|
||||
func TestUnPackInto(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var err error
|
||||
|
||||
// correct type
|
||||
pongMsg := &Pong{}
|
||||
err = UnPackInto([]byte("{}"), pongMsg)
|
||||
assert.NoError(err)
|
||||
|
||||
// wrong type
|
||||
loginMsg := &Login{}
|
||||
err = UnPackInto([]byte(`{"version": 123}`), loginMsg)
|
||||
assert.Error(err)
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var (
|
||||
msg Message
|
||||
resMsg Message
|
||||
err error
|
||||
)
|
||||
// empty struct
|
||||
msg = &Ping{}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
err = WriteMsg(buffer, msg)
|
||||
assert.NoError(err)
|
||||
|
||||
resMsg, err = ReadMsg(buffer)
|
||||
assert.NoError(err)
|
||||
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypePing])
|
||||
|
||||
// normal message
|
||||
msg = &StartWorkConn{
|
||||
ProxyName: "test",
|
||||
}
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
err = WriteMsg(buffer, msg)
|
||||
assert.NoError(err)
|
||||
|
||||
resMsg, err = ReadMsg(buffer)
|
||||
assert.NoError(err)
|
||||
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypeStartWorkConn])
|
||||
|
||||
startWorkConnMsg, ok := resMsg.(*StartWorkConn)
|
||||
assert.True(ok)
|
||||
assert.Equal("test", startWorkConnMsg.ProxyName)
|
||||
|
||||
// ReadMsgInto correct
|
||||
msg = &Pong{}
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
err = WriteMsg(buffer, msg)
|
||||
assert.NoError(err)
|
||||
|
||||
err = ReadMsgInto(buffer, msg)
|
||||
assert.NoError(err)
|
||||
|
||||
// ReadMsgInto error type
|
||||
content := []byte(`{"run_id": 123}`)
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
buffer.WriteByte(TypeNewWorkConn)
|
||||
binary.Write(buffer, binary.BigEndian, int64(len(content)))
|
||||
buffer.Write(content)
|
||||
|
||||
resMsg = &NewWorkConn{}
|
||||
err = ReadMsgInto(buffer, resMsg)
|
||||
assert.Error(err)
|
||||
|
||||
// message format error
|
||||
buffer = bytes.NewBuffer([]byte("1234"))
|
||||
|
||||
resMsg = &NewProxyResp{}
|
||||
err = ReadMsgInto(buffer, resMsg)
|
||||
assert.Error(err)
|
||||
|
||||
// MaxLength, real message length is 2
|
||||
MaxMsgLength = 1
|
||||
msg = &Ping{}
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
err = WriteMsg(buffer, msg)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = ReadMsg(buffer)
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package nathole
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
// Timeout seconds.
|
||||
@@ -105,10 +106,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)
|
||||
@@ -129,7 +141,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):
|
||||
@@ -148,16 +160,27 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
|
||||
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)
|
@@ -17,16 +17,15 @@ package plugin
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
gnet "github.com/fatedier/golib/net"
|
||||
)
|
||||
|
||||
const PluginHttpProxy = "http_proxy"
|
||||
@@ -35,47 +34,6 @@ func init() {
|
||||
Register(PluginHttpProxy, NewHttpProxyPlugin)
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
conns chan net.Conn
|
||||
closed bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewProxyListener() *Listener {
|
||||
return &Listener{
|
||||
conns: make(chan net.Conn, 64),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (net.Conn, error) {
|
||||
conn, ok := <-l.conns
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) PutConn(conn net.Conn) error {
|
||||
err := errors.PanicToError(func() {
|
||||
l.conns <- conn
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if !l.closed {
|
||||
close(l.conns)
|
||||
l.closed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return (*net.TCPAddr)(nil)
|
||||
}
|
||||
|
||||
type HttpProxy struct {
|
||||
l *Listener
|
||||
s *http.Server
|
||||
@@ -109,15 +67,22 @@ func (hp *HttpProxy) Name() string {
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
|
||||
sc, rd := frpNet.NewShareConn(wrapConn)
|
||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||
firstBytes := make([]byte, 7)
|
||||
_, err := rd.Read(firstBytes)
|
||||
if err != nil {
|
||||
wrapConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if request.Method == http.MethodConnect {
|
||||
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(rd, wrapConn, nil))
|
||||
if strings.ToUpper(string(firstBytes)) == "CONNECT" {
|
||||
bufRd := bufio.NewReader(sc)
|
||||
request, err := http.ReadRequest(bufRd)
|
||||
if err != nil {
|
||||
wrapConn.Close()
|
||||
return
|
||||
}
|
||||
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(bufRd, wrapConn, wrapConn.Close))
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -17,8 +17,12 @@ package plugin
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
)
|
||||
|
||||
// Creators is used for create plugins to handle connections.
|
||||
@@ -45,3 +49,44 @@ type Plugin interface {
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
conns chan net.Conn
|
||||
closed bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewProxyListener() *Listener {
|
||||
return &Listener{
|
||||
conns: make(chan net.Conn, 64),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (net.Conn, error) {
|
||||
conn, ok := <-l.conns
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("listener closed")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) PutConn(conn net.Conn) error {
|
||||
err := errors.PanicToError(func() {
|
||||
l.conns <- conn
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if !l.closed {
|
||||
close(l.conns)
|
||||
l.closed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return (*net.TCPAddr)(nil)
|
||||
}
|
||||
|
@@ -32,13 +32,23 @@ func init() {
|
||||
|
||||
type Socks5Plugin struct {
|
||||
Server *gosocks5.Server
|
||||
|
||||
user string
|
||||
passwd string
|
||||
}
|
||||
|
||||
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||
sp := &Socks5Plugin{}
|
||||
sp.Server, err = gosocks5.New(&gosocks5.Config{
|
||||
user := params["plugin_user"]
|
||||
passwd := params["plugin_passwd"]
|
||||
|
||||
cfg := &gosocks5.Config{
|
||||
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
|
||||
})
|
||||
}
|
||||
if user != "" || passwd != "" {
|
||||
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
|
||||
}
|
||||
sp := &Socks5Plugin{}
|
||||
sp.Server, err = gosocks5.New(cfg)
|
||||
p = sp
|
||||
return
|
||||
}
|
||||
|
88
models/plugin/static_file.go
Normal file
88
models/plugin/static_file.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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 plugin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const PluginStaticFile = "static_file"
|
||||
|
||||
func init() {
|
||||
Register(PluginStaticFile, NewStaticFilePlugin)
|
||||
}
|
||||
|
||||
type StaticFilePlugin struct {
|
||||
localPath string
|
||||
stripPrefix string
|
||||
httpUser string
|
||||
httpPasswd string
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
localPath := params["plugin_local_path"]
|
||||
stripPrefix := params["plugin_strip_prefix"]
|
||||
httpUser := params["plugin_http_user"]
|
||||
httpPasswd := params["plugin_http_passwd"]
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
sp := &StaticFilePlugin{
|
||||
localPath: localPath,
|
||||
stripPrefix: stripPrefix,
|
||||
httpUser: httpUser,
|
||||
httpPasswd: httpPasswd,
|
||||
|
||||
l: listener,
|
||||
}
|
||||
var prefix string
|
||||
if stripPrefix != "" {
|
||||
prefix = "/" + stripPrefix + "/"
|
||||
} else {
|
||||
prefix = "/"
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
go sp.s.Serve(listener)
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Name() string {
|
||||
return PluginStaticFile
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Close() error {
|
||||
sp.s.Close()
|
||||
sp.l.Close()
|
||||
return nil
|
||||
}
|
@@ -19,8 +19,9 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
)
|
||||
|
||||
const PluginUnixDomainSocket = "unix_domain_socket"
|
||||
|
@@ -21,8 +21,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"github.com/fatedier/frp/utils/pool"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
|
||||
@@ -66,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) {
|
||||
@@ -82,6 +82,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
|
||||
mu.Lock()
|
||||
delete(udpConnMap, addr)
|
||||
mu.Unlock()
|
||||
udpConn.Close()
|
||||
}()
|
||||
|
||||
buf := pool.GetBuf(1500)
|
||||
|
@@ -14,8 +14,8 @@ make -f ./Makefile.cross-compiles
|
||||
rm -rf ./packages
|
||||
mkdir ./packages
|
||||
|
||||
os_all='linux windows darwin'
|
||||
arch_all='386 amd64 arm mips64 mips64le mips mipsle'
|
||||
os_all='linux windows darwin freebsd'
|
||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
|
||||
|
||||
for os in $os_all; do
|
||||
for arch in $arch_all; do
|
||||
|
@@ -17,22 +17,76 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
frpErr "github.com/fatedier/frp/models/errors"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/crypto"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
"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/shutdown"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
"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
|
||||
@@ -50,11 +104,14 @@ type Control struct {
|
||||
workConnCh chan net.Conn
|
||||
|
||||
// proxies in one client
|
||||
proxies map[string]Proxy
|
||||
proxies map[string]proxy.Proxy
|
||||
|
||||
// pool count
|
||||
poolCount int
|
||||
|
||||
// ports used, for limitations
|
||||
portsUsedNum int
|
||||
|
||||
// last time got the Ping message
|
||||
lastPing time.Time
|
||||
|
||||
@@ -74,16 +131,21 @@ 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(),
|
||||
runId: loginMsg.RunId,
|
||||
status: consts.Working,
|
||||
@@ -99,7 +161,7 @@ func (ctl *Control) Start() {
|
||||
loginRespMsg := &msg.LoginResp{
|
||||
Version: version.Full(),
|
||||
RunId: ctl.runId,
|
||||
ServerUdpPort: config.ServerCommonCfg.BindUdpPort,
|
||||
ServerUdpPort: g.GlbServerCfg.BindUdpPort,
|
||||
Error: "",
|
||||
}
|
||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||
@@ -118,6 +180,7 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.conn.Error("panic error: %v", err)
|
||||
ctl.conn.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -138,6 +201,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.conn.Error("panic error: %v", err)
|
||||
ctl.conn.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -146,7 +210,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
select {
|
||||
case workConn, ok = <-ctl.workConnCh:
|
||||
if !ok {
|
||||
err = errors.ErrCtlClosed
|
||||
err = frpErr.ErrCtlClosed
|
||||
return
|
||||
}
|
||||
ctl.conn.Debug("get work connection from pool")
|
||||
@@ -163,12 +227,12 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
select {
|
||||
case workConn, ok = <-ctl.workConnCh:
|
||||
if !ok {
|
||||
err = errors.ErrCtlClosed
|
||||
err = frpErr.ErrCtlClosed
|
||||
ctl.conn.Warn("no work connections avaiable, %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second):
|
||||
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
|
||||
err = fmt.Errorf("timeout trying to get work connection")
|
||||
ctl.conn.Warn("%v", err)
|
||||
return
|
||||
@@ -192,13 +256,14 @@ func (ctl *Control) writer() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.conn.Error("panic error: %v", err)
|
||||
ctl.conn.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.writerShutdown.Done()
|
||||
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
ctl.allShutdown.Start()
|
||||
@@ -221,13 +286,14 @@ func (ctl *Control) reader() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.conn.Error("panic error: %v", err)
|
||||
ctl.conn.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.readerShutdown.Done()
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
if err == io.EOF {
|
||||
@@ -247,6 +313,7 @@ func (ctl *Control) stoper() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.conn.Error("panic error: %v", err)
|
||||
ctl.conn.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -261,29 +328,39 @@ func (ctl *Control) stoper() {
|
||||
ctl.conn.Close()
|
||||
ctl.readerShutdown.WaitDone()
|
||||
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
|
||||
close(ctl.workConnCh)
|
||||
for workConn := range ctl.workConnCh {
|
||||
workConn.Close()
|
||||
}
|
||||
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
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() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.conn.Error("panic error: %v", err)
|
||||
ctl.conn.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -296,9 +373,9 @@ func (ctl *Control) manager() {
|
||||
for {
|
||||
select {
|
||||
case <-heartbeat.C:
|
||||
if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second {
|
||||
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
|
||||
ctl.conn.Warn("heartbeat timeout")
|
||||
ctl.allShutdown.Start()
|
||||
return
|
||||
}
|
||||
case rawMsg, ok := <-ctl.readCh:
|
||||
if !ok {
|
||||
@@ -318,7 +395,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:
|
||||
@@ -336,18 +416,38 @@ func (ctl *Control) manager() {
|
||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||
var pxyConf config.ProxyConf
|
||||
// Load configures from NewProxy message and check.
|
||||
pxyConf, err = config.NewProxyConf(pxyMsg)
|
||||
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Check ports used number in each client
|
||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
||||
ctl.mu.Lock()
|
||||
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
|
||||
ctl.mu.Unlock()
|
||||
err = fmt.Errorf("exceed the max_ports_per_client")
|
||||
return
|
||||
}
|
||||
ctl.portsUsedNum = ctl.portsUsedNum + pxy.GetUsedPortsNum()
|
||||
ctl.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ctl.mu.Lock()
|
||||
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||
ctl.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
remoteAddr, err = pxy.Run()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -358,7 +458,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
|
||||
}
|
||||
@@ -371,16 +471,23 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||
|
||||
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
|
||||
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
||||
if !ok {
|
||||
ctl.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
||||
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||
}
|
||||
pxy.Close()
|
||||
ctl.svr.DelProxy(pxy.GetName())
|
||||
ctl.pxyManager.Del(pxy.GetName())
|
||||
delete(ctl.proxies, closeMsg.ProxyName)
|
||||
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
ctl.mu.Unlock()
|
||||
|
||||
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,87 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
|
||||
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
|
@@ -21,10 +21,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -32,28 +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 := config.ServerCommonCfg.DashboardUser, config.ServerCommonCfg.DashboardPwd
|
||||
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", 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{
|
||||
|
@@ -18,30 +18,31 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"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"`
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
VhostHttpsPort int `json:"vhost_https_port"`
|
||||
AuthTimeout int64 `json:"auth_timeout"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
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"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
|
||||
TotalTrafficIn int64 `json:"total_traffic_in"`
|
||||
TotalTrafficOut int64 `json:"total_traffic_out"`
|
||||
@@ -50,26 +51,31 @@ 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]")
|
||||
cfg := config.ServerCommonCfg
|
||||
serverStats := StatsGetServer()
|
||||
res = ServerInfoResp{
|
||||
Version: version.Full(),
|
||||
VhostHttpPort: cfg.VhostHttpPort,
|
||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
||||
AuthTimeout: cfg.AuthTimeout,
|
||||
SubdomainHost: cfg.SubDomainHost,
|
||||
MaxPoolCount: cfg.MaxPoolCount,
|
||||
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
serverStats := svr.statsCollector.GetServer()
|
||||
svrResp := ServerInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: cfg.BindPort,
|
||||
BindUdpPort: cfg.BindUdpPort,
|
||||
VhostHttpPort: cfg.VhostHttpPort,
|
||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
||||
KcpBindPort: cfg.KcpBindPort,
|
||||
SubdomainHost: cfg.SubDomainHost,
|
||||
MaxPoolCount: cfg.MaxPoolCount,
|
||||
MaxPortsPerClient: cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
@@ -78,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
|
||||
@@ -189,37 +210,114 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
// api/proxy/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
GeneralResponse
|
||||
// Get proxy info by name.
|
||||
type GetProxyStatsResp struct {
|
||||
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/: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 [%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: [%s]", r.URL.Path)
|
||||
|
||||
proxyStatsResp := GetProxyStatsResp{}
|
||||
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||
if res.Code != 200 {
|
||||
return
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(&proxyStatsResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||
proxyInfo.Name = proxyName
|
||||
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||
if ps == nil {
|
||||
code = 404
|
||||
msg = "no proxy info found"
|
||||
} else {
|
||||
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
|
||||
}
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// api/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
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)
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
// 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
|
||||
// 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,
|
||||
@@ -12,14 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
package group
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// GetFuncName get function name
|
||||
func GetFuncName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
||||
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)
|
||||
}
|
285
server/metric.go
285
server/metric.go
@@ -1,285 +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/models/config"
|
||||
"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 config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.ClientCounts.Inc(1)
|
||||
}
|
||||
}
|
||||
|
||||
func StatsCloseClient() {
|
||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
||||
globalStats.ClientCounts.Dec(1)
|
||||
}
|
||||
}
|
||||
|
||||
func StatsNewProxy(name string, proxyType string) {
|
||||
if config.ServerCommonCfg.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 config.ServerCommonCfg.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 config.ServerCommonCfg.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 config.ServerCommonCfg.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 config.ServerCommonCfg.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 config.ServerCommonCfg.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
|
||||
}
|
||||
|
||||
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"
|
656
server/proxy.go
656
server/proxy.go
@@ -1,656 +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/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
Run() (remoteAddr string, err error)
|
||||
GetControl() *Control
|
||||
GetName() string
|
||||
GetConf() config.ProxyConf
|
||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
name string
|
||||
ctl *Control
|
||||
listeners []frpNet.Listener
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetName() string {
|
||||
return pxy.name
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetControl() *Control {
|
||||
return pxy.ctl
|
||||
}
|
||||
|
||||
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.GetName(),
|
||||
ctl: ctl,
|
||||
listeners: make([]frpNet.Listener, 0),
|
||||
Logger: log.NewPrefixLogger(ctl.runId),
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
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:
|
||||
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(config.ServerCommonCfg.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(config.ServerCommonCfg.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 + "." + config.ServerCommonCfg.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, int(config.ServerCommonCfg.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(config.ServerCommonCfg.PrivilegeToken))
|
||||
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, int(config.ServerCommonCfg.VhostHttpsPort)))
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.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(config.ServerCommonCfg.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", config.ServerCommonCfg.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(config.ServerCommonCfg.PrivilegeToken))
|
||||
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")
|
||||
}
|
138
server/proxy/http.go
Normal file
138
server/proxy/http.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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"
|
||||
"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 {
|
||||
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() (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)
|
||||
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()
|
||||
}
|
||||
}
|
72
server/proxy/https.go
Normal file
72
server/proxy/https.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 {
|
||||
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()
|
||||
}
|
250
server/proxy/proxy.go
Normal file
250
server/proxy/proxy.go
Normal file
@@ -0,0 +1,250 @@
|
||||
// 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"
|
||||
"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() (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() (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())
|
||||
|
||||
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, 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()
|
||||
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()
|
||||
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)
|
||||
}
|
73
server/proxy/xtcp.go
Normal file
73
server/proxy/xtcp.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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 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.rc.NatHoleController.CloseClient(pxy.GetName())
|
||||
errors.PanicToError(func() {
|
||||
close(pxy.closeCh)
|
||||
})
|
||||
}
|
@@ -15,21 +15,30 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"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"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/fatedier/frp/utils/vhost"
|
||||
|
||||
"github.com/xtaci/smux"
|
||||
"github.com/fatedier/golib/net/mux"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,61 +47,80 @@ const (
|
||||
|
||||
var ServerService *Service
|
||||
|
||||
// Server service.
|
||||
// Server service
|
||||
type Service struct {
|
||||
// Accept connections from client.
|
||||
// Dispatch connections to different handlers listen on same port
|
||||
muxer *mux.Mux
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// Manage all udp ports.
|
||||
udpPortManager *PortManager
|
||||
|
||||
// Controller for nat hole connections.
|
||||
natHoleController *NatHoleController
|
||||
// stats collector to store server and proxies stats info
|
||||
statsCollector stats.Collector
|
||||
}
|
||||
|
||||
func NewService() (svr *Service, err error) {
|
||||
cfg := config.ServerCommonCfg
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
svr = &Service{
|
||||
ctlManager: NewControlManager(),
|
||||
pxyManager: NewProxyManager(),
|
||||
visitorManager: NewVisitorManager(),
|
||||
tcpPortManager: NewPortManager("tcp", cfg.ProxyBindAddr, cfg.PrivilegeAllowPorts),
|
||||
udpPortManager: NewPortManager("udp", cfg.ProxyBindAddr, cfg.PrivilegeAllowPorts),
|
||||
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),
|
||||
},
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var (
|
||||
httpMuxOn bool
|
||||
httpsMuxOn bool
|
||||
)
|
||||
if cfg.BindAddr == cfg.ProxyBindAddr {
|
||||
if cfg.BindPort == cfg.VhostHttpPort {
|
||||
httpMuxOn = true
|
||||
}
|
||||
if cfg.BindPort == cfg.VhostHttpsPort {
|
||||
httpsMuxOn = true
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for accepting connections from client.
|
||||
svr.listener, err = frpNet.ListenTcp(cfg.BindAddr, cfg.BindPort)
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindPort))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Listen for accepting connections from client using kcp protocol.
|
||||
@@ -105,10 +133,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{
|
||||
@@ -116,10 +153,14 @@ func NewService() (svr *Service, err error) {
|
||||
Handler: rp,
|
||||
}
|
||||
var l net.Listener
|
||||
l, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create vhost http listener error, %v", err)
|
||||
return
|
||||
if httpMuxOn {
|
||||
l = svr.muxer.ListenHttp(1)
|
||||
} else {
|
||||
l, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create vhost http listener error, %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
go server.Serve(l)
|
||||
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||
@@ -127,13 +168,18 @@ func NewService() (svr *Service, err error) {
|
||||
|
||||
// Create https vhost muxer.
|
||||
if cfg.VhostHttpsPort > 0 {
|
||||
var l frpNet.Listener
|
||||
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create vhost https listener error, %v", err)
|
||||
return
|
||||
var l net.Listener
|
||||
if httpsMuxOn {
|
||||
l = svr.muxer.ListenHttps(1)
|
||||
} else {
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(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
|
||||
@@ -143,38 +189,44 @@ func NewService() (svr *Service, err error) {
|
||||
|
||||
// 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 config.ServerCommonCfg.KcpBindPort > 0 {
|
||||
if g.GlbServerCfg.KcpBindPort > 0 {
|
||||
go svr.HandleListener(svr.kcpListener)
|
||||
}
|
||||
svr.HandleListener(svr.listener)
|
||||
|
||||
go svr.HandleListener(svr.websocketListener)
|
||||
|
||||
svr.HandleListener(svr.listener)
|
||||
}
|
||||
|
||||
func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
@@ -233,8 +285,11 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
}
|
||||
}
|
||||
|
||||
if config.ServerCommonCfg.TcpMux {
|
||||
session, err := smux.Server(frpConn, nil)
|
||||
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 {
|
||||
log.Warn("Failed to create mux connection: %v", err)
|
||||
frpConn.Close()
|
||||
@@ -244,7 +299,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
log.Warn("Accept new mux stream error: %v", err)
|
||||
log.Debug("Accept new mux stream error: %v", err)
|
||||
session.Close()
|
||||
return
|
||||
}
|
||||
@@ -269,12 +324,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
}
|
||||
|
||||
// Check auth.
|
||||
nowTime := time.Now().Unix()
|
||||
if config.ServerCommonCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > config.ServerCommonCfg.AuthTimeout {
|
||||
err = fmt.Errorf("authorization timeout")
|
||||
return
|
||||
}
|
||||
if util.GetAuthKey(config.ServerCommonCfg.PrivilegeToken, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||
err = fmt.Errorf("authorization failed")
|
||||
return
|
||||
}
|
||||
@@ -288,7 +338,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()
|
||||
@@ -298,7 +348,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
|
||||
}
|
||||
|
||||
@@ -314,14 +370,6 @@ 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)
|
||||
}
|
||||
|
||||
func (svr *Service) DelProxy(name string) {
|
||||
svr.pxyManager.Del(name)
|
||||
}
|
||||
|
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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user