mirror of
https://github.com/fatedier/frp.git
synced 2026-03-20 08:49:16 +08:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea62bc5a34 | ||
|
|
23bb76397a | ||
|
|
859a330e6c | ||
|
|
86ac511763 | ||
|
|
f2e98ef8a4 | ||
|
|
495d999b6c | ||
|
|
6d1af85e80 | ||
|
|
1db091b381 | ||
|
|
0b9124d4fd | ||
|
|
6c6607ae68 | ||
|
|
83d80857fd | ||
|
|
98fa3855bd | ||
|
|
9440bc5d72 | ||
|
|
95753ebf1c | ||
|
|
f8c6795119 | ||
|
|
7033f3e72b | ||
|
|
e3101b7aa8 | ||
|
|
c747f160aa | ||
|
|
c8748a2948 | ||
|
|
487c8d7c29 | ||
|
|
69fa7ed16e | ||
|
|
5336155365 | ||
|
|
4feb74cb89 | ||
|
|
4a4cf552af | ||
|
|
0f59b8f329 | ||
|
|
f480160e2d | ||
|
|
4832a2a1e9 | ||
|
|
52ecd84d8a | ||
|
|
30c246c488 | ||
|
|
42014eea23 | ||
|
|
c2da396230 | ||
|
|
e91c9473be | ||
|
|
13e48c6ca0 | ||
|
|
31e2cb76bb | ||
|
|
91e46a2c53 | ||
|
|
a57679f837 | ||
|
|
75f3bce04d | ||
|
|
df18375308 | ||
|
|
c63737ab3e | ||
|
|
1cdceee347 | ||
|
|
694c434b9e | ||
|
|
62af5c8844 | ||
|
|
56c53909aa | ||
|
|
21a126e4e4 | ||
|
|
8affab1a2b | ||
|
|
12cc53d699 | ||
|
|
2ab832bb89 | ||
|
|
42425d8218 | ||
|
|
6da093a402 | ||
|
|
adc3adc13b | ||
|
|
22a79710d8 | ||
|
|
0927553fe4 | ||
|
|
858d8f0ba7 | ||
|
|
8eb945ee9b | ||
|
|
dc0fd60d30 | ||
|
|
cd44c9f55c | ||
|
|
649f47c345 | ||
|
|
6ca3160b33 | ||
|
|
5f8ed4fc60 | ||
|
|
bf0993d2a6 | ||
|
|
5dc8175fc8 | ||
|
|
dc6a5a29c1 | ||
|
|
e62d9a5242 | ||
|
|
94212ac8b8 | ||
|
|
e9e86fccf0 | ||
|
|
58745992ef | ||
|
|
234d634bfe | ||
|
|
fdc6902a90 | ||
|
|
d8d587fd93 | ||
|
|
92791260a7 | ||
|
|
4dfd851c46 | ||
|
|
bc4df74b5e | ||
|
|
666f122a72 | ||
|
|
f999c8a87e | ||
|
|
90a32ab75d | ||
|
|
0713fd28da | ||
|
|
f5b33e6de8 | ||
|
|
fc6043bb4d | ||
|
|
bc46e3330a | ||
|
|
5fc7b3ceb5 | ||
|
|
6277af4790 | ||
|
|
00bd0a8af4 | ||
|
|
a415573e45 | ||
|
|
e68012858e | ||
|
|
ca8a5b753c | ||
|
|
d1f4ac0f2d | ||
|
|
ff357882ac | ||
|
|
934ac2b836 | ||
|
|
1ad50d5982 | ||
|
|
388b016842 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,6 +27,7 @@ _testmain.go
|
|||||||
bin/
|
bin/
|
||||||
packages/
|
packages/
|
||||||
test/bin/
|
test/bin/
|
||||||
|
vendor/
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
*.swp
|
*.swp
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ language: go
|
|||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.12.x
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make
|
- make
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -1,4 +1,5 @@
|
|||||||
export PATH := $(GOPATH)/bin:$(PATH)
|
export PATH := $(GOPATH)/bin:$(PATH)
|
||||||
|
export GO111MODULE=on
|
||||||
|
|
||||||
all: fmt build
|
all: fmt build
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ file:
|
|||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|
||||||
frps:
|
frps:
|
||||||
go build -o bin/frps ./cmd/frps
|
go build -o bin/frps ./cmd/frps
|
||||||
|
|
||||||
|
|||||||
488
README.md
488
README.md
@@ -6,53 +6,61 @@
|
|||||||
|
|
||||||
## What is frp?
|
## What is frp?
|
||||||
|
|
||||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
|
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
||||||
|
|
||||||
Now it also try to support p2p connect.
|
frp also has a P2P connect mode.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Status](#status)
|
* [Development Status](#development-status)
|
||||||
* [Architecture](#architecture)
|
* [Architecture](#architecture)
|
||||||
* [Example Usage](#example-usage)
|
* [Example Usage](#example-usage)
|
||||||
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
||||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
* [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 DNS query request](#forward-dns-query-request)
|
||||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
* [Forward Unix domain socket](#forward-unix-domain-socket)
|
||||||
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
* [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
|
||||||
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||||
* [Expose your service in security](#expose-your-service-in-security)
|
* [Expose your service privately](#expose-your-service-privately)
|
||||||
* [P2P Mode](#p2p-mode)
|
* [P2P Mode](#p2p-mode)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
* [Configuration File](#configuration-file)
|
* [Configuration Files](#configuration-files)
|
||||||
* [Configuration file template](#configuration-file-template)
|
* [Using Environment Variables](#using-environment-variables)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
* [Admin UI](#admin-ui)
|
* [Admin UI](#admin-ui)
|
||||||
* [Authentication](#authentication)
|
* [Monitor](#monitor)
|
||||||
|
* [Prometheus](#prometheus)
|
||||||
|
* [Authenticating the Client](#authenticating-the-client)
|
||||||
|
* [Token Authentication](#token-authentication)
|
||||||
|
* [OIDC Authentication](#oidc-authentication)
|
||||||
* [Encryption and Compression](#encryption-and-compression)
|
* [Encryption and Compression](#encryption-and-compression)
|
||||||
* [TLS](#tls)
|
* [TLS](#tls)
|
||||||
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
* [Hot-Reloading frpc configuration](#hot-reloading-frpc-configuration)
|
||||||
* [Get proxy status from client](#get-proxy-status-from-client)
|
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||||
* [Port White List](#port-white-list)
|
* [Only allowing certain ports on the server](#only-allowing-certain-ports-on-the-server)
|
||||||
* [Port Reuse](#port-reuse)
|
* [Port Reuse](#port-reuse)
|
||||||
|
* [Bandwidth Limit](#bandwidth-limit)
|
||||||
|
* [For Each Proxy](#for-each-proxy)
|
||||||
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||||
* [Support KCP Protocol](#support-kcp-protocol)
|
* [Support KCP Protocol](#support-kcp-protocol)
|
||||||
* [Connection Pool](#connection-pool)
|
* [Connection Pooling](#connection-pooling)
|
||||||
* [Load balancing](#load-balancing)
|
* [Load balancing](#load-balancing)
|
||||||
* [Health Check](#health-check)
|
* [Service Health Check](#service-health-check)
|
||||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
* [Rewriting the HTTP Host Header](#rewriting-the-http-host-header)
|
||||||
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
* [Setting other HTTP Headers](#setting-other-http-headers)
|
||||||
* [Get Real IP](#get-real-ip)
|
* [Get Real IP](#get-real-ip)
|
||||||
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||||
* [Proxy Protocol](#proxy-protocol)
|
* [Proxy Protocol](#proxy-protocol)
|
||||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
* [Require HTTP Basic Auth (Password) for Web Services](#require-http-basic-auth-password-for-web-services)
|
||||||
* [Custom subdomain names](#custom-subdomain-names)
|
* [Custom Subdomain Names](#custom-subdomain-names)
|
||||||
* [URL routing](#url-routing)
|
* [URL Routing](#url-routing)
|
||||||
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
* [TCP Port Multiplexing](#tcp-port-multiplexing)
|
||||||
|
* [Connecting to frps via HTTP PROXY](#connecting-to-frps-via-http-proxy)
|
||||||
* [Range ports mapping](#range-ports-mapping)
|
* [Range ports mapping](#range-ports-mapping)
|
||||||
* [Plugin](#plugin)
|
* [Client Plugins](#client-plugins)
|
||||||
|
* [Server Manage Plugins](#server-manage-plugins)
|
||||||
* [Development Plan](#development-plan)
|
* [Development Plan](#development-plan)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
@@ -62,11 +70,11 @@ Now it also try to support p2p connect.
|
|||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
|
|
||||||
## Status
|
## Development Status
|
||||||
|
|
||||||
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
|
||||||
|
|
||||||
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
|
**The protocol might change at a release and we don't promise backwards compatibility. Please check the release log when upgrading the client and the server.**
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -74,15 +82,15 @@ frp is under development and you can try it with latest release version. Master
|
|||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch.
|
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your operating system and architecture.
|
||||||
|
|
||||||
Put **frps** and **frps.ini** to your server with public IP.
|
Put `frps` and `frps.ini` onto your server A with public IP.
|
||||||
|
|
||||||
Put **frpc** and **frpc.ini** to your server in LAN.
|
Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected from public Internet).
|
||||||
|
|
||||||
### Access your computer in LAN by SSH
|
### Access your computer in LAN by SSH
|
||||||
|
|
||||||
1. Modify frps.ini:
|
1. Modify `frps.ini` on server A:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
@@ -90,11 +98,11 @@ Put **frpc** and **frpc.ini** to your server in LAN.
|
|||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Start `frps` on server A:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
`./frps -c ./frps.ini`
|
||||||
|
|
||||||
3. Modify frpc.ini, `server_addr` is your frps's server IP:
|
3. On server B, modify `frpc.ini` to put in your `frps` server public IP as `server_addr` field:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -109,21 +117,21 @@ Put **frpc** and **frpc.ini** to your server in LAN.
|
|||||||
remote_port = 6000
|
remote_port = 6000
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start frpc:
|
4. Start `frpc` on server B:
|
||||||
|
|
||||||
`./frpc -c ./frpc.ini`
|
`./frpc -c ./frpc.ini`
|
||||||
|
|
||||||
5. Connect to server in LAN by ssh assuming that username is test:
|
5. From another machine, SSH to server B like this (assuming that username is `test`):
|
||||||
|
|
||||||
`ssh -oPort=6000 test@x.x.x.x`
|
`ssh -oPort=6000 test@x.x.x.x`
|
||||||
|
|
||||||
### Visit your web service in LAN by custom domains
|
### Visit your web service in LAN by custom domains
|
||||||
|
|
||||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
|
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local IP.
|
||||||
|
|
||||||
However, we can expose a http or https service using frp.
|
However, we can expose an HTTP(S) service using frp.
|
||||||
|
|
||||||
1. Modify frps.ini, configure http port 8080:
|
1. Modify `frps.ini`, set the vhost HTTP port to 8080:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
@@ -132,11 +140,11 @@ However, we can expose a http or https service using frp.
|
|||||||
vhost_http_port = 8080
|
vhost_http_port = 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Start `frps`:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
`./frps -c ./frps.ini`
|
||||||
|
|
||||||
3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service:
|
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. The `local_port` is the port of your web service:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -147,20 +155,20 @@ However, we can expose a http or https service using frp.
|
|||||||
[web]
|
[web]
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = www.yourdomain.com
|
custom_domains = www.example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start frpc:
|
4. Start `frpc`:
|
||||||
|
|
||||||
`./frpc -c ./frpc.ini`
|
`./frpc -c ./frpc.ini`
|
||||||
|
|
||||||
5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain.
|
5. Resolve A record of `www.example.com` to the public IP of the remote frps server or CNAME record to your origin domain.
|
||||||
|
|
||||||
6. Now visit your local web service using url `http://www.yourdomain.com:8080`.
|
6. Now visit your local web service using url `http://www.example.com:8080`.
|
||||||
|
|
||||||
### Forward DNS query request
|
### Forward DNS query request
|
||||||
|
|
||||||
1. Modify frps.ini:
|
1. Modify `frps.ini`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
@@ -168,11 +176,11 @@ However, we can expose a http or https service using frp.
|
|||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Start `frps`:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
`./frps -c ./frps.ini`
|
||||||
|
|
||||||
3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to google dns server `8.8.8.8:53`:
|
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server, forward DNS query request to Google Public DNS server `8.8.8.8:53`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -191,17 +199,17 @@ However, we can expose a http or https service using frp.
|
|||||||
|
|
||||||
`./frpc -c ./frpc.ini`
|
`./frpc -c ./frpc.ini`
|
||||||
|
|
||||||
5. Send dns query request by dig:
|
5. Test DNS resolution using `dig` command:
|
||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.google.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
### Forward unix domain socket
|
### Forward Unix domain socket
|
||||||
|
|
||||||
Using tcp port to connect unix domain socket like docker daemon.
|
Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP.
|
||||||
|
|
||||||
Configure frps same as above.
|
Configure `frps` same as above.
|
||||||
|
|
||||||
1. Start frpc with configurations:
|
1. Start `frpc` with configuration:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -216,17 +224,17 @@ Configure frps same as above.
|
|||||||
plugin_unix_path = /var/run/docker.sock
|
plugin_unix_path = /var/run/docker.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Get docker version by curl command:
|
2. Test: Get Docker version using `curl`:
|
||||||
|
|
||||||
`curl http://x.x.x.x:6000/version`
|
`curl http://x.x.x.x:6000/version`
|
||||||
|
|
||||||
### Expose a simple http file server
|
### Expose a simple HTTP file server
|
||||||
|
|
||||||
A simple way to visit files in the LAN.
|
Browser your files stored in the LAN, from public Internet.
|
||||||
|
|
||||||
Configure frps same as above.
|
Configure `frps` same as above.
|
||||||
|
|
||||||
1. Start frpc with configurations:
|
1. Start `frpc` with configuration:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -238,17 +246,17 @@ Configure frps same as above.
|
|||||||
type = tcp
|
type = tcp
|
||||||
remote_port = 6000
|
remote_port = 6000
|
||||||
plugin = static_file
|
plugin = static_file
|
||||||
plugin_local_path = /tmp/file
|
plugin_local_path = /tmp/files
|
||||||
plugin_strip_prefix = static
|
plugin_strip_prefix = static
|
||||||
plugin_http_user = abc
|
plugin_http_user = abc
|
||||||
plugin_http_passwd = abc
|
plugin_http_passwd = abc
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct user and password to view files in `/tmp/files` on the `frpc` machine.
|
||||||
|
|
||||||
### Enable HTTPS for local HTTP service
|
### Enable HTTPS for local HTTP service
|
||||||
|
|
||||||
1. Start frpc with configurations:
|
1. Start `frpc` with configuration:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -256,28 +264,27 @@ Configure frps same as above.
|
|||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
[test_htts2http]
|
[test_https2http]
|
||||||
type = https
|
type = https
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.example.com
|
||||||
|
|
||||||
plugin = https2http
|
plugin = https2http
|
||||||
plugin_local_addr = 127.0.0.1:80
|
plugin_local_addr = 127.0.0.1:80
|
||||||
plugin_crt_path = ./server.crt
|
plugin_crt_path = ./server.crt
|
||||||
plugin_key_path = ./server.key
|
plugin_key_path = ./server.key
|
||||||
plugin_host_header_rewrite = 127.0.0.1
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Visit `https://test.yourdomain.com`.
|
2. Visit `https://test.example.com`.
|
||||||
|
|
||||||
### Expose your service in security
|
### Expose your service privately
|
||||||
|
|
||||||
For some services, if expose them to the public network directly will be a security risk.
|
Some services will be at risk if exposed directly to the public network. With **STCP** (secret TCP) mode, a preshared key is needed to access the service from another client.
|
||||||
|
|
||||||
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
|
Configure `frps` same as above.
|
||||||
|
|
||||||
Configure frps same as above.
|
1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `sk` field for the preshared key, and that the `remote_port` field is removed here:
|
||||||
|
|
||||||
1. Start frpc, forward ssh port and `remote_port` is useless:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -292,7 +299,7 @@ Configure frps same as above.
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start another frpc in which you want to connect this ssh server:
|
2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`sk` field):
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -309,23 +316,24 @@ Configure frps same as above.
|
|||||||
bind_port = 6000
|
bind_port = 6000
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Connect to server in LAN by ssh assuming that username is test:
|
3. On machine C, connect to SSH on machine B, using this command:
|
||||||
|
|
||||||
`ssh -oPort=6000 test@127.0.0.1`
|
`ssh -oPort=6000 127.0.0.1`
|
||||||
|
|
||||||
### P2P Mode
|
### P2P Mode
|
||||||
|
|
||||||
**xtcp** is designed for transmitting a large amount of data directly between two client.
|
**xtcp** is designed for transmitting large amounts of data directly between clients. A frps server is still needed, as P2P here only refers the actual data transmission.
|
||||||
|
|
||||||
Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
|
Note it can't penetrate all types of NAT devices. You might want to fallback to **stcp** if **xtcp** doesn't work.
|
||||||
|
|
||||||
1. Configure a udp port for xtcp:
|
1. In `frps.ini` configure a UDP port for xtcp:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
# frps.ini
|
||||||
bind_udp_port = 7001
|
bind_udp_port = 7001
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frpc, forward ssh port and `remote_port` is useless:
|
2. Start `frpc` on machine B, expose the SSH port. Note that `remote_port` field is removed:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -340,7 +348,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Start another frpc in which you want to connect this ssh server:
|
3. Start another `frpc` (typically on another machine C) with the config to connect to SSH using P2P mode:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -357,23 +365,23 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
|
|||||||
bind_port = 6000
|
bind_port = 6000
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Connect to server in LAN by ssh assuming that username is test:
|
4. On machine C, connect to SSH on machine B, using this command:
|
||||||
|
|
||||||
`ssh -oPort=6000 test@127.0.0.1`
|
`ssh -oPort=6000 127.0.0.1`
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Configuration File
|
### Configuration Files
|
||||||
|
|
||||||
You can find features which this document not metioned from full example configuration files.
|
Read the full example configuration files to find out even more features not described here.
|
||||||
|
|
||||||
[frps full configuration file](./conf/frps_full.ini)
|
[Full configuration file for frps (Server)](./conf/frps_full.ini)
|
||||||
|
|
||||||
[frpc full configuration file](./conf/frpc_full.ini)
|
[Full configuration file for frpc (Client)](./conf/frpc_full.ini)
|
||||||
|
|
||||||
### Configuration file template
|
### Using Environment Variables
|
||||||
|
|
||||||
Configuration file tempalte can be rendered using os environments. Template uses Go's standard format.
|
Environment variables can be referenced in the configuration file, using Go's standard format:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -388,7 +396,7 @@ local_port = 22
|
|||||||
remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
|
remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
|
||||||
```
|
```
|
||||||
|
|
||||||
Start frpc program:
|
With the config above, variables can be passed into `frpc` program like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
export FRP_SERVER_ADDR="x.x.x.x"
|
export FRP_SERVER_ADDR="x.x.x.x"
|
||||||
@@ -396,12 +404,11 @@ export FRP_SSH_REMOTE_PORT="6000"
|
|||||||
./frpc -c ./frpc.ini
|
./frpc -c ./frpc.ini
|
||||||
```
|
```
|
||||||
|
|
||||||
frpc will auto render configuration file template using os environments.
|
`frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`.
|
||||||
All environments has prefix `.Envs`.
|
|
||||||
|
|
||||||
### Dashboard
|
### Dashboard
|
||||||
|
|
||||||
Check frp's status and proxies's statistics information by Dashboard.
|
Check frp's status and proxies' statistics information by Dashboard.
|
||||||
|
|
||||||
Configure a port for dashboard to enable this feature:
|
Configure a port for dashboard to enable this feature:
|
||||||
|
|
||||||
@@ -413,15 +420,15 @@ dashboard_user = admin
|
|||||||
dashboard_pwd = admin
|
dashboard_pwd = admin
|
||||||
```
|
```
|
||||||
|
|
||||||
Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`.
|
Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin` by default.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Admin UI
|
### Admin UI
|
||||||
|
|
||||||
Admin UI help you check and manage frpc's configure.
|
The Admin UI helps you check and manage frpc's configuration.
|
||||||
|
|
||||||
Configure a address for admin UI to enable this feature:
|
Configure an address for admin UI to enable this feature:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[common]
|
[common]
|
||||||
@@ -431,15 +438,65 @@ admin_user = admin
|
|||||||
admin_pwd = admin
|
admin_pwd = admin
|
||||||
```
|
```
|
||||||
|
|
||||||
Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
|
Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin` by default.
|
||||||
|
|
||||||
### Authentication
|
### Monitor
|
||||||
|
|
||||||
`token` in frps.ini and frpc.ini should be same.
|
When dashboard is enabled, frps will save monitor data in cache. It will be cleared after process restart.
|
||||||
|
|
||||||
|
Prometheus is also supported.
|
||||||
|
|
||||||
|
#### Prometheus
|
||||||
|
|
||||||
|
Enable dashboard first, then configure `enable_prometheus = true` in `frps.ini`.
|
||||||
|
|
||||||
|
`http://{dashboard_addr}/metrics` will provide prometheus monitor data.
|
||||||
|
|
||||||
|
### Authenticating the Client
|
||||||
|
|
||||||
|
There are 2 authentication methods to authenticate frpc with frps.
|
||||||
|
|
||||||
|
You can decide which one to use by configuring `authentication_method` under `[common]` in `frpc.ini` and `frps.ini`.
|
||||||
|
|
||||||
|
Configuring `authenticate_heartbeats = true` under `[common]` will use the configured authentication method to add and validate authentication on every heartbeat between frpc and frps.
|
||||||
|
|
||||||
|
Configuring `authenticate_new_work_conns = true` under `[common]` will do the same for every new work connection between frpc and frps.
|
||||||
|
|
||||||
|
#### Token Authentication
|
||||||
|
|
||||||
|
When specifying `authentication_method = token` under `[common]` in `frpc.ini` and `frps.ini` - token based authentication will be used.
|
||||||
|
|
||||||
|
Make sure to specify the same `token` in the `[common]` section in `frps.ini` and `frpc.ini` for frpc to pass frps validation
|
||||||
|
|
||||||
|
#### OIDC Authentication
|
||||||
|
|
||||||
|
When specifying `authentication_method = oidc` under `[common]` in `frpc.ini` and `frps.ini` - OIDC based authentication will be used.
|
||||||
|
|
||||||
|
OIDC stands for OpenID Connect, and the flow used is called [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).
|
||||||
|
|
||||||
|
To use this authentication type - configure `frpc.ini` and `frps.ini` as follows:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
authentication_method = oidc
|
||||||
|
oidc_issuer = https://example-oidc-issuer.com/
|
||||||
|
oidc_audience = https://oidc-audience.com/.default
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
authentication_method = oidc
|
||||||
|
oidc_client_id = 98692467-37de-409a-9fac-bb2585826f18 # Replace with OIDC client ID
|
||||||
|
oidc_client_secret = oidc_secret
|
||||||
|
oidc_audience = https://oidc-audience.com/.default
|
||||||
|
oidc_token_endpoint_url = https://example-oidc-endpoint.com/oauth2/v2.0/token
|
||||||
|
```
|
||||||
|
|
||||||
### Encryption and Compression
|
### Encryption and Compression
|
||||||
|
|
||||||
Defalut value is false, you could decide if the proxy will use encryption or compression:
|
The features are off by default. You can turn on encryption and/or compression:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -453,15 +510,17 @@ use_compression = true
|
|||||||
|
|
||||||
#### TLS
|
#### TLS
|
||||||
|
|
||||||
frp support TLS protocol between frpc and frps since v0.25.0.
|
frp supports the TLS protocol between `frpc` and `frps` since v0.25.0.
|
||||||
|
|
||||||
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
|
Config `tls_enable = true` in the `[common]` section to `frpc.ini` to enable this feature.
|
||||||
|
|
||||||
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
|
For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection.
|
||||||
|
|
||||||
### Hot-Reload frpc configuration
|
To enforce `frps` to only accept TLS connections - configure `tls_only = true` in the `[common]` section in `frps.ini`.
|
||||||
|
|
||||||
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
### Hot-Reloading frpc configuration
|
||||||
|
|
||||||
|
The `admin_addr` and `admin_port` fields are required for enabling HTTP API:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -470,17 +529,17 @@ admin_addr = 127.0.0.1
|
|||||||
admin_port = 7400
|
admin_port = 7400
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
|
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let `frpc` create or update or delete proxies.
|
||||||
|
|
||||||
**Note that parameters in [common] section won't be modified except 'start' now.**
|
**Note that parameters in [common] section won't be modified except 'start'.**
|
||||||
|
|
||||||
### Get proxy status from client
|
### Get proxy status from client
|
||||||
|
|
||||||
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
|
Use `frpc status -c ./frpc.ini` to get status of all proxies. The `admin_addr` and `admin_port` fields are required for enabling HTTP API.
|
||||||
|
|
||||||
### Port White List
|
### Only allowing certain ports on the server
|
||||||
|
|
||||||
`allow_ports` in frps.ini is used for preventing abuse of ports:
|
`allow_ports` in `frps.ini` is used to avoid abuse of ports:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
@@ -488,19 +547,34 @@ Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set ad
|
|||||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||||
```
|
```
|
||||||
|
|
||||||
`allow_ports` consists of a specific port or a range of ports divided by `,`.
|
`allow_ports` consists of specific ports or port ranges (lowest port number, dash `-`, highest port number), separated by comma `,`.
|
||||||
|
|
||||||
### Port Reuse
|
### Port Reuse
|
||||||
|
|
||||||
Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly.
|
`vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect the connection's protocol and handle it correspondingly.
|
||||||
|
|
||||||
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
|
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
|
||||||
|
|
||||||
|
### Bandwidth Limit
|
||||||
|
|
||||||
|
#### For Each Proxy
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[ssh]
|
||||||
|
type = tcp
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
|
bandwidth_limit = 1MB
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `bandwidth_limit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`.
|
||||||
|
|
||||||
### TCP Stream Multiplexing
|
### TCP Stream Multiplexing
|
||||||
|
|
||||||
frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
|
frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.
|
||||||
|
|
||||||
You can disable this feature by modify frps.ini and frpc.ini:
|
You can disable this feature by modify `frps.ini` and `frpc.ini`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini and frpc.ini, must be same
|
# frps.ini and frpc.ini, must be same
|
||||||
@@ -512,36 +586,38 @@ tcp_mux = false
|
|||||||
|
|
||||||
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||||
|
|
||||||
Using kcp in frp:
|
KCP mode uses UDP as the underlying transport. Using KCP in frp:
|
||||||
|
|
||||||
1. Enable kcp protocol in frps:
|
1. Enable KCP in frps:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
[common]
|
[common]
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
# kcp needs to bind a udp port, it can be same with 'bind_port'
|
# Specify a UDP port for KCP.
|
||||||
kcp_bind_port = 7000
|
kcp_bind_port = 7000
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Configure the protocol used in frpc to connect frps:
|
The `kcp_bind_port` number can be the same number as `bind_port`, since `bind_port` field specifies a TCP port.
|
||||||
|
|
||||||
|
2. Configure `frpc.ini` to use KCP to connect to frps:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
# specify the 'kcp_bind_port' in frps
|
# Same as the 'kcp_bind_port' in frps.ini
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
protocol = kcp
|
protocol = kcp
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connection Pool
|
### Connection Pooling
|
||||||
|
|
||||||
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
By default, frps creates a new frpc connection to the backend service upon a user request. With connection pooling, frps keeps a certain number of pre-established connections, reducing the time needed to establish a connection.
|
||||||
|
|
||||||
This feature is fit for a large number of short connections.
|
This feature is suitable for a large number of short connections.
|
||||||
|
|
||||||
1. Configure the limit of pool count each proxy can use in frps.ini:
|
1. Configure the limit of pool count each proxy can use in `frps.ini`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
@@ -561,7 +637,7 @@ This feature is fit for a large number of short connections.
|
|||||||
|
|
||||||
Load balancing is supported by `group`.
|
Load balancing is supported by `group`.
|
||||||
|
|
||||||
This feature is available only for type `tcp` and `http` now.
|
This feature is only available for types `tcp` and `http` now.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -582,23 +658,19 @@ group_key = 123
|
|||||||
|
|
||||||
`group_key` is used for authentication.
|
`group_key` is used for authentication.
|
||||||
|
|
||||||
Proxies in same group will accept connections from port 80 randomly.
|
Connections to port 80 will be dispatched to proxies in the same group randomly.
|
||||||
|
|
||||||
For `tcp` type, `remote_port` in one group shoud be same.
|
For type `tcp`, `remote_port` in the same group should be the same.
|
||||||
|
|
||||||
For `http` type, `custom_domains, subdomain, locations` shoud be same.
|
For type `http`, `custom_domains`, `subdomain`, `locations` should be the same.
|
||||||
|
|
||||||
### Health Check
|
### Service Health Check
|
||||||
|
|
||||||
Health check feature can help you achieve high availability with load balancing.
|
Health check feature can help you achieve high availability with load balancing.
|
||||||
|
|
||||||
Add `health_check_type = {type}` to enable health check.
|
Add `health_check_type = tcp` or `health_check_type = http` to enable health check.
|
||||||
|
|
||||||
**type** can be tcp or http.
|
With health check type **tcp**, the service port will be pinged (TCPing):
|
||||||
|
|
||||||
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
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -606,77 +678,81 @@ Type tcp configuration:
|
|||||||
type = tcp
|
type = tcp
|
||||||
local_port = 22
|
local_port = 22
|
||||||
remote_port = 6000
|
remote_port = 6000
|
||||||
# enable tcp health check
|
# Enable TCP health check
|
||||||
health_check_type = tcp
|
health_check_type = tcp
|
||||||
# dial timeout seconds
|
# TCPing timeout seconds
|
||||||
health_check_timeout_s = 3
|
health_check_timeout_s = 3
|
||||||
# if continuous failed in 3 times, the proxy will be removed from frps
|
# If health check failed 3 times in a row, the proxy will be removed from frps
|
||||||
health_check_max_failed = 3
|
health_check_max_failed = 3
|
||||||
# every 10 seconds will do a health check
|
# A health check every 10 seconds
|
||||||
health_check_interval_s = 10
|
health_check_interval_s = 10
|
||||||
```
|
```
|
||||||
|
|
||||||
Type http configuration:
|
With health check type **http**, an HTTP request will be sent to the service and an HTTP 2xx OK response is expected:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web]
|
[web]
|
||||||
type = http
|
type = http
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.example.com
|
||||||
# enable http health check
|
# Enable HTTP health check
|
||||||
health_check_type = http
|
health_check_type = http
|
||||||
# frpc will send a GET http request '/status' to local http service
|
# frpc will send a GET request to '/status'
|
||||||
# http service is alive when it return 2xx http response code
|
# and expect an HTTP 2xx OK response
|
||||||
health_check_url = /status
|
health_check_url = /status
|
||||||
health_check_interval_s = 10
|
|
||||||
health_check_max_failed = 3
|
|
||||||
health_check_timeout_s = 3
|
health_check_timeout_s = 3
|
||||||
|
health_check_max_failed = 3
|
||||||
|
health_check_interval_s = 10
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rewriting the Host Header
|
### Rewriting the HTTP Host Header
|
||||||
|
|
||||||
When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
|
By default frp does not modify the tunneled HTTP requests at all as it's a byte-for-byte copy.
|
||||||
|
|
||||||
|
However, speaking of web servers and HTTP requests, your web server might rely on the `Host` HTTP header to determine the website to be accessed. frp can rewrite the `Host` header when forwarding the HTTP requests, with the `host_header_rewrite` field:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web]
|
[web]
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.example.com
|
||||||
host_header_rewrite = dev.yourdomain.com
|
host_header_rewrite = dev.example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
|
The HTTP request will have the the `Host` header rewritten to `Host: dev.example.com` when it reaches the actual web server, although the request from the browser probably has `Host: test.example.com`.
|
||||||
|
|
||||||
### Set Headers In HTTP Request
|
### Setting other HTTP Headers
|
||||||
|
|
||||||
You can set headers for proxy which type is `http`.
|
Similar to `Host`, You can override other HTTP request headers with proxy type `http`.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web]
|
[web]
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.example.com
|
||||||
host_header_rewrite = dev.yourdomain.com
|
host_header_rewrite = dev.example.com
|
||||||
header_X-From-Where = frp
|
header_X-From-Where = frp
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that params which have prefix `header_` will be added to http request headers.
|
Note that parameter(s) prefixed with `header_` will be added to HTTP request headers.
|
||||||
In this example, it will set header `X-From-Where: frp` to http request.
|
|
||||||
|
In this example, it will set header `X-From-Where: frp` in the HTTP request.
|
||||||
|
|
||||||
### Get Real IP
|
### Get Real IP
|
||||||
|
|
||||||
#### HTTP X-Forwarded-For
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
Features for http proxy only.
|
This feature is for http proxy only.
|
||||||
|
|
||||||
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
|
You can get user's real IP from HTTP request headers `X-Forwarded-For` and `X-Real-IP`.
|
||||||
|
|
||||||
#### Proxy Protocol
|
#### Proxy Protocol
|
||||||
|
|
||||||
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
|
frp supports Proxy Protocol to send user's real IP to local services. It support all types except UDP.
|
||||||
|
|
||||||
Here is an example for https service:
|
Here is an example for https service:
|
||||||
|
|
||||||
@@ -685,21 +761,19 @@ Here is an example for https service:
|
|||||||
[web]
|
[web]
|
||||||
type = https
|
type = https
|
||||||
local_port = 443
|
local_port = 443
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.example.com
|
||||||
|
|
||||||
# now v1 and v2 is supported
|
# now v1 and v2 are supported
|
||||||
proxy_protocol_version = v2
|
proxy_protocol_version = v2
|
||||||
```
|
```
|
||||||
|
|
||||||
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
|
You can enable Proxy Protocol support in nginx to expose user's real IP in HTTP header `X-Real-IP`, and then read `X-Real-IP` header in your web service for the real IP.
|
||||||
|
|
||||||
Then you can get it from HTTP request header in your local service.
|
### Require HTTP Basic Auth (Password) for Web Services
|
||||||
|
|
||||||
### 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.
|
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
|
||||||
|
|
||||||
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
|
This enforces HTTP Basic Auth on all requests with the username and password specified in frpc's configure file.
|
||||||
|
|
||||||
It can only be enabled when proxy type is http.
|
It can only be enabled when proxy type is http.
|
||||||
|
|
||||||
@@ -708,23 +782,23 @@ It can only be enabled when proxy type is http.
|
|||||||
[web]
|
[web]
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.example.com
|
||||||
http_user = abc
|
http_user = abc
|
||||||
http_pwd = abc
|
http_pwd = abc
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit `http://test.yourdomain.com` and now you need to input username and password.
|
Visit `http://test.example.com` in the browser and now you are prompted to enter the username and password.
|
||||||
|
|
||||||
### Custom subdomain names
|
### Custom Subdomain Names
|
||||||
|
|
||||||
It is convenient to use `subdomain` configure for http、https type when many people use one frps server together.
|
It is convenient to use `subdomain` configure for http and https types when many people share one frps server.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
subdomain_host = frps.com
|
subdomain_host = frps.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Resolve `*.frps.com` to the frps server's IP.
|
Resolve `*.frps.com` to the frps server's IP. This is usually called a Wildcard DNS record.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -734,35 +808,79 @@ local_port = 80
|
|||||||
subdomain = test
|
subdomain = test
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can visit your web service by host `test.frps.com`.
|
Now you can visit your web service on `test.frps.com`.
|
||||||
|
|
||||||
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
|
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
|
||||||
|
|
||||||
### URL routing
|
### URL Routing
|
||||||
|
|
||||||
frp support forward http requests to different backward web services by url routing.
|
frp supports forwarding HTTP requests to different backend web services by url routing.
|
||||||
|
|
||||||
`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
|
`locations` specifies the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web01]
|
[web01]
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = web.yourdomain.com
|
custom_domains = web.example.com
|
||||||
locations = /
|
locations = /
|
||||||
|
|
||||||
[web02]
|
[web02]
|
||||||
type = http
|
type = http
|
||||||
local_port = 81
|
local_port = 81
|
||||||
custom_domains = web.yourdomain.com
|
custom_domains = web.example.com
|
||||||
locations = /news,/about
|
locations = /news,/about
|
||||||
```
|
```
|
||||||
Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**.
|
|
||||||
|
|
||||||
### Connect frps by HTTP PROXY
|
HTTP requests with URL prefix `/news` or `/about` will be forwarded to **web02** and other requests to **web01**.
|
||||||
|
|
||||||
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
### TCP Port Multiplexing
|
||||||
|
|
||||||
|
frp supports receiving TCP sockets directed to different proxies on a single port on frps, similar to `vhost_http_port` and `vhost_https_port`.
|
||||||
|
|
||||||
|
The only supported TCP port multiplexing method available at the moment is `httpconnect` - HTTP CONNECT tunnel.
|
||||||
|
|
||||||
|
When setting `tcpmux_httpconnect_port` to anything other than 0 in frps under `[common]`, frps will listen on this port for HTTP CONNECT requests.
|
||||||
|
|
||||||
|
The host of the HTTP CONNECT request will be used to match the proxy in frps. Proxy hosts can be configured in frpc by configuring `custom_domain` and / or `subdomain` under `type = tcpmux` proxies, when `multiplexer = httpconnect`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
bind_port = 7000
|
||||||
|
tcpmux_httpconnect_port = 1337
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[proxy1]
|
||||||
|
type = tcpmux
|
||||||
|
multiplexer = httpconnect
|
||||||
|
custom_domains = test1
|
||||||
|
|
||||||
|
[proxy2]
|
||||||
|
type = tcpmux
|
||||||
|
multiplexer = httpconnect
|
||||||
|
custom_domains = test2
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
CONNECT test1 HTTP/1.1\r\n\r\n
|
||||||
|
```
|
||||||
|
and the connection will be routed to `proxy1`.
|
||||||
|
|
||||||
|
### Connecting to frps via HTTP PROXY
|
||||||
|
|
||||||
|
frpc can connect to frps using HTTP proxy if you set OS environment variable `HTTP_PROXY`, or if `http_proxy` is set in frpc.ini file.
|
||||||
|
|
||||||
It only works when protocol is tcp.
|
It only works when protocol is tcp.
|
||||||
|
|
||||||
@@ -776,7 +894,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080
|
|||||||
|
|
||||||
### Range ports mapping
|
### Range ports mapping
|
||||||
|
|
||||||
Proxy name has prefix `range:` will support mapping range ports.
|
Proxy with names that start with `range:` will support mapping range ports.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -787,15 +905,15 @@ local_port = 6000-6006,6007
|
|||||||
remote_port = 6000-6006,6007
|
remote_port = 6000-6006,6007
|
||||||
```
|
```
|
||||||
|
|
||||||
frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
|
frpc will generate 8 proxies like `test_tcp_0`, `test_tcp_1`, ..., `test_tcp_7`.
|
||||||
|
|
||||||
### Plugin
|
### Client Plugins
|
||||||
|
|
||||||
frpc only forward request to local tcp or udp port by default.
|
frpc only forwards requests to local TCP or UDP ports by default.
|
||||||
|
|
||||||
Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
||||||
|
|
||||||
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
Specify which plugin to use with the `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` are not used for plugin.
|
||||||
|
|
||||||
Using plugin **http_proxy**:
|
Using plugin **http_proxy**:
|
||||||
|
|
||||||
@@ -811,9 +929,13 @@ plugin_http_passwd = abc
|
|||||||
|
|
||||||
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||||
|
|
||||||
|
### Server Manage Plugins
|
||||||
|
|
||||||
|
Read the [document](/doc/server_plugin.md).
|
||||||
|
|
||||||
## Development Plan
|
## Development Plan
|
||||||
|
|
||||||
* Log http request information in frps.
|
* Log HTTP request information in frps.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -821,14 +943,14 @@ Interested in getting involved? We would like to help you!
|
|||||||
|
|
||||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
||||||
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
||||||
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
* Sorry for my poor English. Improvements for this document are welcome, even some typo fixes.
|
||||||
* If you have some wonderful ideas, send email to fatedier@gmail.com.
|
* If you have great ideas, send an email to fatedier@gmail.com.
|
||||||
|
|
||||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatedly.**
|
||||||
|
|
||||||
## Donation
|
## Donation
|
||||||
|
|
||||||
If frp help you a lot, you can support us by:
|
If frp helps you a lot, you can support us by:
|
||||||
|
|
||||||
frp QQ group: 606194980
|
frp QQ group: 606194980
|
||||||
|
|
||||||
|
|||||||
137
README_zh.md
137
README_zh.md
@@ -26,13 +26,19 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [配置文件模版渲染](#配置文件模版渲染)
|
* [配置文件模版渲染](#配置文件模版渲染)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
* [Admin UI](#admin-ui)
|
* [Admin UI](#admin-ui)
|
||||||
* [身份验证](#身份验证)
|
* [监控](#监控)
|
||||||
|
* [Prometheus](#prometheus)
|
||||||
|
* [客户端身份验证](#客户端身份验证)
|
||||||
|
* [Token](#token)
|
||||||
|
* [OIDC](#oidc)
|
||||||
* [加密与压缩](#加密与压缩)
|
* [加密与压缩](#加密与压缩)
|
||||||
* [TLS](#tls)
|
* [TLS](#tls)
|
||||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||||
* [客户端查看代理状态](#客户端查看代理状态)
|
* [客户端查看代理状态](#客户端查看代理状态)
|
||||||
* [端口白名单](#端口白名单)
|
* [端口白名单](#端口白名单)
|
||||||
* [端口复用](#端口复用)
|
* [端口复用](#端口复用)
|
||||||
|
* [限速](#限速)
|
||||||
|
* [代理限速](#代理限速)
|
||||||
* [TCP 多路复用](#tcp-多路复用)
|
* [TCP 多路复用](#tcp-多路复用)
|
||||||
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
|
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
|
||||||
* [连接池](#连接池)
|
* [连接池](#连接池)
|
||||||
@@ -46,9 +52,11 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||||
* [自定义二级域名](#自定义二级域名)
|
* [自定义二级域名](#自定义二级域名)
|
||||||
* [URL 路由](#url-路由)
|
* [URL 路由](#url-路由)
|
||||||
|
* [TCP 端口复用类型](#tcp-端口复用类型)
|
||||||
* [通过代理连接 frps](#通过代理连接-frps)
|
* [通过代理连接 frps](#通过代理连接-frps)
|
||||||
* [范围端口映射](#范围端口映射)
|
* [范围端口映射](#范围端口映射)
|
||||||
* [插件](#插件)
|
* [客户端插件](#客户端插件)
|
||||||
|
* [服务端管理插件](#服务端管理插件)
|
||||||
* [开发计划](#开发计划)
|
* [开发计划](#开发计划)
|
||||||
* [为 frp 做贡献](#为-frp-做贡献)
|
* [为 frp 做贡献](#为-frp-做贡献)
|
||||||
* [捐助](#捐助)
|
* [捐助](#捐助)
|
||||||
@@ -129,7 +137,7 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试
|
|||||||
vhost_http_port = 8080
|
vhost_http_port = 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 启动 frps;
|
2. 启动 frps:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
`./frps -c ./frps.ini`
|
||||||
|
|
||||||
@@ -270,6 +278,7 @@ frps 的部署步骤同上。
|
|||||||
plugin_crt_path = ./server.crt
|
plugin_crt_path = ./server.crt
|
||||||
plugin_key_path = ./server.key
|
plugin_key_path = ./server.key
|
||||||
plugin_host_header_rewrite = 127.0.0.1
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
||||||
@@ -455,9 +464,56 @@ admin_pwd = admin
|
|||||||
|
|
||||||
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
|
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
|
||||||
|
|
||||||
### 身份验证
|
### 监控
|
||||||
|
|
||||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
frps 当启用 Dashboard 后,会默认开启内部的监控,数据存放在内存中,每次重启进程后会清空,监控数据可以通过 dashboard 的地址发送 HTTP 请求获取。
|
||||||
|
|
||||||
|
目前还支持 Prometheus 作为可选的监控系统。
|
||||||
|
|
||||||
|
#### Prometheus
|
||||||
|
|
||||||
|
在 `frps.ini` 中启用 Dashboard,并且设置 `enable_prometheus = true`,则通过 `http://{dashboard_addr}/metrics` 可以获取到 Prometheus 的监控数据。
|
||||||
|
|
||||||
|
### 客户端身份验证
|
||||||
|
|
||||||
|
目前 frpc 和 frps 之间支持两种身份验证方式,`token` 和 `oidc`。
|
||||||
|
|
||||||
|
通过 `frpc.ini` 和 `frps.ini` 中 `[common]` section 的 `authentication_method` 参数配置需要使用的验证方法。
|
||||||
|
|
||||||
|
`authenticate_heartbeats = true` 将会在每一个心跳包中附加上鉴权信息。
|
||||||
|
|
||||||
|
`authenticate_new_work_conns = true` 将会在每次建立新的工作连接时附加上鉴权信息。
|
||||||
|
|
||||||
|
#### Token
|
||||||
|
|
||||||
|
当 `authentication_method = token`,将会启用基于 token 的验证方式。
|
||||||
|
|
||||||
|
需要在 `frpc.ini` 和 `frps.ini` 的 `[common]` section 中设置相同的 `token`。
|
||||||
|
|
||||||
|
#### OIDC
|
||||||
|
|
||||||
|
当 `authentication_method = oidc`,将会启用基于 OIDC 的身份验证。
|
||||||
|
|
||||||
|
验证流程参考 [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4)
|
||||||
|
|
||||||
|
启用这一验证方式,配置 `frpc.ini` 和 `frps.ini` 如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
authentication_method = oidc
|
||||||
|
oidc_issuer = https://example-oidc-issuer.com/
|
||||||
|
oidc_audience = https://oidc-audience.com/.default
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
authentication_method = oidc
|
||||||
|
oidc_client_id = 98692467-37de-409a-9fac-bb2585826f18 # Replace with OIDC client ID
|
||||||
|
oidc_client_secret = oidc_secret
|
||||||
|
oidc_audience = https://oidc-audience.com/.default
|
||||||
|
oidc_token_endpoint_url = https://example-oidc-endpoint.com/oauth2/v2.0/token
|
||||||
|
```
|
||||||
|
|
||||||
### 加密与压缩
|
### 加密与压缩
|
||||||
|
|
||||||
@@ -483,6 +539,8 @@ use_compression = true
|
|||||||
|
|
||||||
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
|
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
|
||||||
|
|
||||||
|
通过将 frps.ini 的 `[common]` 中 `tls_only` 设置为 true,可以强制 frps 只接受 TLS 连接。
|
||||||
|
|
||||||
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
|
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
|
||||||
|
|
||||||
### 客户端热加载配置文件
|
### 客户端热加载配置文件
|
||||||
@@ -530,6 +588,23 @@ allow_ports = 2000-3000,3001,3003,4000-50000
|
|||||||
|
|
||||||
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
|
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
|
||||||
|
|
||||||
|
### 限速
|
||||||
|
|
||||||
|
#### 代理限速
|
||||||
|
|
||||||
|
目前支持在客户端的代理配置中设置代理级别的限速,限制单个 proxy 可以占用的带宽。
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[ssh]
|
||||||
|
type = tcp
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
|
bandwidth_limit = 1MB
|
||||||
|
```
|
||||||
|
|
||||||
|
在代理配置中增加 `bandwidth_limit` 字段启用此功能,目前仅支持 `MB` 和 `KB` 单位。
|
||||||
|
|
||||||
### TCP 多路复用
|
### TCP 多路复用
|
||||||
|
|
||||||
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
|
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
|
||||||
@@ -803,6 +878,50 @@ locations = /news,/about
|
|||||||
|
|
||||||
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。
|
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。
|
||||||
|
|
||||||
|
### TCP 端口复用类型
|
||||||
|
|
||||||
|
frp 支持将单个端口收到的连接路由到不同的代理,类似 `vhost_http_port` 和 `vhost_https_port`。
|
||||||
|
|
||||||
|
目前支持的复用器只有 `httpconnect`。
|
||||||
|
|
||||||
|
当在 `frps.ini` 的 `[common]` 中设置 `tcpmux_httpconnect_port`,frps 将会监听在这个端口,接收 HTTP CONNECT 请求。
|
||||||
|
|
||||||
|
frps 会根据 HTTP CONNECT 请求中的 host 路由到不同的后端代理。
|
||||||
|
|
||||||
|
示例配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
bind_port = 7000
|
||||||
|
tcpmux_httpconnect_port = 1337
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[proxy1]
|
||||||
|
type = tcpmux
|
||||||
|
multiplexer = httpconnect
|
||||||
|
custom_domains = test1
|
||||||
|
|
||||||
|
[proxy2]
|
||||||
|
type = tcpmux
|
||||||
|
multiplexer = httpconnect
|
||||||
|
custom_domains = test2
|
||||||
|
```
|
||||||
|
|
||||||
|
通过上面的配置,frps 如果接收到 HTTP CONNECT 请求内容:
|
||||||
|
|
||||||
|
```
|
||||||
|
CONNECT test1 HTTP/1.1\r\n\r\n
|
||||||
|
```
|
||||||
|
|
||||||
|
该连接将会被路由到 proxy1 。
|
||||||
|
|
||||||
### 通过代理连接 frps
|
### 通过代理连接 frps
|
||||||
|
|
||||||
在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。
|
在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。
|
||||||
@@ -838,11 +957,11 @@ remote_port = 6000-6006,6007
|
|||||||
|
|
||||||
实际连接成功后会创建 8 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
|
实际连接成功后会创建 8 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
|
||||||
|
|
||||||
### 插件
|
### 客户端插件
|
||||||
|
|
||||||
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||||
|
|
||||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
|
客户端插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
|
||||||
|
|
||||||
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
||||||
|
|
||||||
@@ -860,6 +979,10 @@ plugin_http_passwd = abc
|
|||||||
|
|
||||||
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
|
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
|
||||||
|
|
||||||
|
### 服务端管理插件
|
||||||
|
|
||||||
|
[使用说明](/doc/server_plugin_zh.md)
|
||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
|
|
||||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func ReadFile(file string) (content string, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return content, err
|
return content, err
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
buf, err := ioutil.ReadAll(file)
|
buf, err := ioutil.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return content, err
|
return content, err
|
||||||
@@ -65,6 +66,7 @@ func ReadFile(file string) (content string, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return content, err
|
return content, err
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
buf, err := ioutil.ReadAll(file)
|
buf, err := ioutil.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return content, err
|
return content, err
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -36,7 +35,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
|||||||
// url router
|
// url router
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
|
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
)
|
)
|
||||||
@@ -47,7 +46,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
@@ -55,7 +54,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
@@ -63,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
@@ -107,7 +106,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) 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 (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
|
||||||
|
|
||||||
func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
|
||||||
psr := ProxyStatusResp{
|
psr := ProxyStatusResp{
|
||||||
Name: status.Name,
|
Name: status.Name,
|
||||||
Type: status.Type,
|
Type: status.Type,
|
||||||
@@ -121,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
|||||||
}
|
}
|
||||||
psr.Plugin = cfg.Plugin
|
psr.Plugin = cfg.Plugin
|
||||||
if status.Err != "" {
|
if status.Err != "" {
|
||||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||||
} else {
|
} else {
|
||||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
if cfg.LocalPort != 0 {
|
if cfg.LocalPort != 0 {
|
||||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
}
|
}
|
||||||
if status.Err != "" {
|
if status.Err != "" {
|
||||||
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
|
||||||
} else {
|
} else {
|
||||||
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||||
}
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
if cfg.LocalPort != 0 {
|
if cfg.LocalPort != 0 {
|
||||||
@@ -184,17 +183,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
for _, status := range ps {
|
for _, status := range ps {
|
||||||
switch status.Type {
|
switch status.Type {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
|
res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "udp":
|
case "udp":
|
||||||
res.Udp = append(res.Udp, NewProxyStatusResp(status))
|
res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "http":
|
case "http":
|
||||||
res.Http = append(res.Http, NewProxyStatusResp(status))
|
res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "https":
|
case "https":
|
||||||
res.Https = append(res.Https, NewProxyStatusResp(status))
|
res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "stcp":
|
case "stcp":
|
||||||
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
|
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "xtcp":
|
case "xtcp":
|
||||||
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
|
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(ByProxyStatusResp(res.Tcp))
|
sort.Sort(ByProxyStatusResp(res.Tcp))
|
||||||
@@ -219,14 +218,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if g.GlbClientCfg.CfgFile == "" {
|
if svr.cfgFile == "" {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = "frpc has no config file path"
|
res.Msg = "frpc has no config file path"
|
||||||
log.Warn("%s", res.Msg)
|
log.Warn("%s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
@@ -277,7 +276,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// get token from origin content
|
// get token from origin content
|
||||||
token := ""
|
token := ""
|
||||||
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
b, err := ioutil.ReadFile(svr.cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
@@ -316,7 +315,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
content = strings.Join(newRows, "\n")
|
content = strings.Join(newRows, "\n")
|
||||||
|
|
||||||
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 500
|
res.Code = 500
|
||||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||||
|
|||||||
@@ -15,19 +15,21 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/auth"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
"github.com/fatedier/golib/control/shutdown"
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
@@ -46,7 +48,7 @@ type Control struct {
|
|||||||
vm *VisitorManager
|
vm *VisitorManager
|
||||||
|
|
||||||
// control connection
|
// control connection
|
||||||
conn frpNet.Conn
|
conn net.Conn
|
||||||
|
|
||||||
// tcp stream multiplexing, if enabled
|
// tcp stream multiplexing, if enabled
|
||||||
session *fmux.Session
|
session *fmux.Session
|
||||||
@@ -65,16 +67,35 @@ type Control struct {
|
|||||||
// last time got the Pong message
|
// last time got the Pong message
|
||||||
lastPong time.Time
|
lastPong time.Time
|
||||||
|
|
||||||
|
// The client configuration
|
||||||
|
clientCfg config.ClientCommonConf
|
||||||
|
|
||||||
readerShutdown *shutdown.Shutdown
|
readerShutdown *shutdown.Shutdown
|
||||||
writerShutdown *shutdown.Shutdown
|
writerShutdown *shutdown.Shutdown
|
||||||
msgHandlerShutdown *shutdown.Shutdown
|
msgHandlerShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
|
// The UDP port that the server is listening on
|
||||||
|
serverUDPPort int
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
log.Logger
|
xl *xlog.Logger
|
||||||
|
|
||||||
|
// service context
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
// sets authentication based on selected method
|
||||||
|
authSetter auth.Setter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
|
func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.Session,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
pxyCfgs map[string]config.ProxyConf,
|
||||||
|
visitorCfgs map[string]config.VisitorConf,
|
||||||
|
serverUDPPort int,
|
||||||
|
authSetter auth.Setter) *Control {
|
||||||
|
|
||||||
|
// new xlog instance
|
||||||
ctl := &Control{
|
ctl := &Control{
|
||||||
runId: runId,
|
runId: runId,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
@@ -84,14 +105,18 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
|
|||||||
readCh: make(chan msg.Message, 100),
|
readCh: make(chan msg.Message, 100),
|
||||||
closedCh: make(chan struct{}),
|
closedCh: make(chan struct{}),
|
||||||
closedDoneCh: make(chan struct{}),
|
closedDoneCh: make(chan struct{}),
|
||||||
|
clientCfg: clientCfg,
|
||||||
readerShutdown: shutdown.New(),
|
readerShutdown: shutdown.New(),
|
||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
msgHandlerShutdown: shutdown.New(),
|
msgHandlerShutdown: shutdown.New(),
|
||||||
Logger: log.NewPrefixLogger(""),
|
serverUDPPort: serverUDPPort,
|
||||||
|
xl: xlog.FromContextSafe(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
authSetter: authSetter,
|
||||||
}
|
}
|
||||||
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
|
ctl.pm = proxy.NewProxyManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
|
||||||
|
|
||||||
ctl.vm = NewVisitorManager(ctl)
|
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
|
||||||
ctl.vm.Reload(visitorCfgs)
|
ctl.vm.Reload(visitorCfgs)
|
||||||
return ctl
|
return ctl
|
||||||
}
|
}
|
||||||
@@ -108,6 +133,7 @@ func (ctl *Control) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||||
|
xl := ctl.xl
|
||||||
workConn, err := ctl.connectServer()
|
workConn, err := ctl.connectServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -116,32 +142,41 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
|||||||
m := &msg.NewWorkConn{
|
m := &msg.NewWorkConn{
|
||||||
RunId: ctl.runId,
|
RunId: ctl.runId,
|
||||||
}
|
}
|
||||||
|
if err = ctl.authSetter.SetNewWorkConn(m); err != nil {
|
||||||
|
xl.Warn("error during NewWorkConn authentication: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = msg.WriteMsg(workConn, m); err != nil {
|
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||||
ctl.Warn("work connection write to server error: %v", err)
|
xl.Warn("work connection write to server error: %v", err)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var startMsg msg.StartWorkConn
|
var startMsg msg.StartWorkConn
|
||||||
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
||||||
ctl.Error("work connection closed, %v", err)
|
xl.Error("work connection closed before response StartWorkConn message: %v", err)
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if startMsg.Error != "" {
|
||||||
|
xl.Error("StartWorkConn contains error: %s", startMsg.Error)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
|
||||||
|
|
||||||
// dispatch this work connection to related proxy
|
// dispatch this work connection to related proxy
|
||||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||||
|
xl := ctl.xl
|
||||||
// Server will return NewProxyResp message to each NewProxy message.
|
// Server will return NewProxyResp message to each NewProxy message.
|
||||||
// Start a new proxy handler if no error got
|
// Start a new proxy handler if no error got
|
||||||
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
xl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
||||||
} else {
|
} else {
|
||||||
ctl.Info("[%s] start proxy success", inMsg.ProxyName)
|
xl.Info("[%s] start proxy success", inMsg.ProxyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,26 +195,27 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// connectServer return a new connection to frps
|
// connectServer return a new connection to frps
|
||||||
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||||
if g.GlbClientCfg.TcpMux {
|
xl := ctl.xl
|
||||||
|
if ctl.clientCfg.TcpMux {
|
||||||
stream, errRet := ctl.session.OpenStream()
|
stream, errRet := ctl.session.OpenStream()
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
ctl.Warn("start new connection to server error: %v", err)
|
xl.Warn("start new connection to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn = frpNet.WrapConn(stream)
|
conn = stream
|
||||||
} else {
|
} else {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if g.GlbClientCfg.TLSEnable {
|
if ctl.clientCfg.TLSEnable {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.Warn("start new connection to server error: %v", err)
|
xl.Warn("start new connection to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,23 +224,24 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
|||||||
|
|
||||||
// reader read all messages from frps and send to readCh
|
// reader read all messages from frps and send to readCh
|
||||||
func (ctl *Control) reader() {
|
func (ctl *Control) reader() {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
defer ctl.readerShutdown.Done()
|
defer ctl.readerShutdown.Done()
|
||||||
defer close(ctl.closedCh)
|
defer close(ctl.closedCh)
|
||||||
|
|
||||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
|
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||||
for {
|
for {
|
||||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
ctl.Debug("read from control connection EOF")
|
xl.Debug("read from control connection EOF")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.Warn("read error: %v", err)
|
xl.Warn("read error: %v", err)
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -216,20 +253,21 @@ func (ctl *Control) reader() {
|
|||||||
|
|
||||||
// writer writes messages got from sendCh to frps
|
// writer writes messages got from sendCh to frps
|
||||||
func (ctl *Control) writer() {
|
func (ctl *Control) writer() {
|
||||||
|
xl := ctl.xl
|
||||||
defer ctl.writerShutdown.Done()
|
defer ctl.writerShutdown.Done()
|
||||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.conn.Error("crypto new writer error: %v", err)
|
xl.Error("crypto new writer error: %v", err)
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if m, ok := <-ctl.sendCh; !ok {
|
if m, ok := <-ctl.sendCh; !ok {
|
||||||
ctl.Info("control writer is closing")
|
xl.Info("control writer is closing")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||||
ctl.Warn("write message to control connection error: %v", err)
|
xl.Warn("write message to control connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,15 +276,16 @@ func (ctl *Control) writer() {
|
|||||||
|
|
||||||
// msgHandler handles all channel events and do corresponding operations.
|
// msgHandler handles all channel events and do corresponding operations.
|
||||||
func (ctl *Control) msgHandler() {
|
func (ctl *Control) msgHandler() {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
defer ctl.msgHandlerShutdown.Done()
|
defer ctl.msgHandlerShutdown.Done()
|
||||||
|
|
||||||
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
|
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
|
||||||
defer hbSend.Stop()
|
defer hbSend.Stop()
|
||||||
hbCheck := time.NewTicker(time.Second)
|
hbCheck := time.NewTicker(time.Second)
|
||||||
defer hbCheck.Stop()
|
defer hbCheck.Stop()
|
||||||
@@ -257,11 +296,16 @@ func (ctl *Control) msgHandler() {
|
|||||||
select {
|
select {
|
||||||
case <-hbSend.C:
|
case <-hbSend.C:
|
||||||
// send heartbeat to server
|
// send heartbeat to server
|
||||||
ctl.Debug("send heartbeat to server")
|
xl.Debug("send heartbeat to server")
|
||||||
ctl.sendCh <- &msg.Ping{}
|
pingMsg := &msg.Ping{}
|
||||||
|
if err := ctl.authSetter.SetPing(pingMsg); err != nil {
|
||||||
|
xl.Warn("error during ping authentication: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctl.sendCh <- pingMsg
|
||||||
case <-hbCheck.C:
|
case <-hbCheck.C:
|
||||||
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
|
||||||
ctl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
// let reader() stop
|
// let reader() stop
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
@@ -277,8 +321,13 @@ func (ctl *Control) msgHandler() {
|
|||||||
case *msg.NewProxyResp:
|
case *msg.NewProxyResp:
|
||||||
ctl.HandleNewProxyResp(m)
|
ctl.HandleNewProxyResp(m)
|
||||||
case *msg.Pong:
|
case *msg.Pong:
|
||||||
|
if m.Error != "" {
|
||||||
|
xl.Error("Pong contains error: %s", m.Error)
|
||||||
|
ctl.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
ctl.lastPong = time.Now()
|
ctl.lastPong = time.Now()
|
||||||
ctl.Debug("receive heartbeat from server")
|
xl.Debug("receive heartbeat from server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -50,11 +50,11 @@ type HealthCheckMonitor struct {
|
|||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
l log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string,
|
func NewHealthCheckMonitor(ctx context.Context, checkType string,
|
||||||
|
intervalS int, timeoutS int, maxFailedTimes int,
|
||||||
|
addr string, url string,
|
||||||
statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
|
statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
|
||||||
|
|
||||||
if intervalS <= 0 {
|
if intervalS <= 0 {
|
||||||
@@ -66,7 +66,7 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai
|
|||||||
if maxFailedTimes <= 0 {
|
if maxFailedTimes <= 0 {
|
||||||
maxFailedTimes = 1
|
maxFailedTimes = 1
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
newctx, cancel := context.WithCancel(ctx)
|
||||||
return &HealthCheckMonitor{
|
return &HealthCheckMonitor{
|
||||||
checkType: checkType,
|
checkType: checkType,
|
||||||
interval: time.Duration(intervalS) * time.Second,
|
interval: time.Duration(intervalS) * time.Second,
|
||||||
@@ -77,15 +77,11 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai
|
|||||||
statusOK: false,
|
statusOK: false,
|
||||||
statusNormalFn: statusNormalFn,
|
statusNormalFn: statusNormalFn,
|
||||||
statusFailedFn: statusFailedFn,
|
statusFailedFn: statusFailedFn,
|
||||||
ctx: ctx,
|
ctx: newctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
|
|
||||||
monitor.l = l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (monitor *HealthCheckMonitor) Start() {
|
func (monitor *HealthCheckMonitor) Start() {
|
||||||
go monitor.checkWorker()
|
go monitor.checkWorker()
|
||||||
}
|
}
|
||||||
@@ -95,6 +91,7 @@ func (monitor *HealthCheckMonitor) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (monitor *HealthCheckMonitor) checkWorker() {
|
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||||
|
xl := xlog.FromContextSafe(monitor.ctx)
|
||||||
for {
|
for {
|
||||||
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||||
err := monitor.doCheck(doCtx)
|
err := monitor.doCheck(doCtx)
|
||||||
@@ -109,25 +106,17 @@ func (monitor *HealthCheckMonitor) checkWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if monitor.l != nil {
|
xl.Trace("do one health check success")
|
||||||
monitor.l.Trace("do one health check success")
|
|
||||||
}
|
|
||||||
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
||||||
if monitor.l != nil {
|
xl.Info("health check status change to success")
|
||||||
monitor.l.Info("health check status change to success")
|
|
||||||
}
|
|
||||||
monitor.statusOK = true
|
monitor.statusOK = true
|
||||||
monitor.statusNormalFn()
|
monitor.statusNormalFn()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if monitor.l != nil {
|
xl.Warn("do one health check failed: %v", err)
|
||||||
monitor.l.Warn("do one health check failed: %v", err)
|
|
||||||
}
|
|
||||||
monitor.failedTimes++
|
monitor.failedTimes++
|
||||||
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
||||||
if monitor.l != nil {
|
xl.Warn("health check status change to failed")
|
||||||
monitor.l.Warn("health check status change to failed")
|
|
||||||
}
|
|
||||||
monitor.statusOK = false
|
monitor.statusOK = false
|
||||||
monitor.statusFailedFn()
|
monitor.statusFailedFn()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package proxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -25,19 +26,20 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/models/plugin"
|
plugin "github.com/fatedier/frp/models/plugin/client"
|
||||||
"github.com/fatedier/frp/models/proto/udp"
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/limit"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
pp "github.com/pires/go-proxyproto"
|
pp "github.com/pires/go-proxyproto"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy defines how to handle work connections for different proxy type.
|
// Proxy defines how to handle work connections for different proxy type.
|
||||||
@@ -45,15 +47,24 @@ type Proxy interface {
|
|||||||
Run() error
|
Run() error
|
||||||
|
|
||||||
// InWorkConn accept work connections registered to server.
|
// InWorkConn accept work connections registered to server.
|
||||||
InWorkConn(frpNet.Conn, *msg.StartWorkConn)
|
InWorkConn(net.Conn, *msg.StartWorkConn)
|
||||||
|
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
||||||
|
var limiter *rate.Limiter
|
||||||
|
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
||||||
|
if limitBytes > 0 {
|
||||||
|
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||||
|
}
|
||||||
|
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
clientCfg: clientCfg,
|
||||||
|
serverUDPPort: serverUDPPort,
|
||||||
|
limiter: limiter,
|
||||||
|
xl: xlog.FromContextSafe(ctx),
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
@@ -61,6 +72,11 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
|||||||
BaseProxy: &baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.TcpMuxProxyConf:
|
||||||
|
pxy = &TcpMuxProxy{
|
||||||
|
BaseProxy: &baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
pxy = &UdpProxy{
|
pxy = &UdpProxy{
|
||||||
BaseProxy: &baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
@@ -91,9 +107,14 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
clientCfg config.ClientCommonConf
|
||||||
log.Logger
|
serverUDPPort int
|
||||||
|
limiter *rate.Limiter
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
xl *xlog.Logger
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP
|
// TCP
|
||||||
@@ -120,9 +141,38 @@ func (pxy *TcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||||
[]byte(g.GlbClientCfg.Token), m)
|
conn, []byte(pxy.clientCfg.Token), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP Multiplexer
|
||||||
|
type TcpMuxProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.TcpMuxProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) Run() (err error) {
|
||||||
|
if pxy.cfg.Plugin != "" {
|
||||||
|
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||||
|
conn, []byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
@@ -149,9 +199,9 @@ func (pxy *HttpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pxy *HttpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||||
[]byte(g.GlbClientCfg.Token), m)
|
conn, []byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
@@ -178,9 +228,9 @@ func (pxy *HttpsProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pxy *HttpsProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||||
[]byte(g.GlbClientCfg.Token), m)
|
conn, []byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
@@ -207,9 +257,9 @@ func (pxy *StcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pxy *StcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||||
[]byte(g.GlbClientCfg.Token), m)
|
conn, []byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
// XTCP
|
||||||
@@ -236,12 +286,13 @@ func (pxy *XtcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pxy *XtcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
var natHoleSidMsg msg.NatHoleSid
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("xtcp read from workConn error: %v", err)
|
xl.Error("xtcp read from workConn error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,13 +301,13 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
Sid: natHoleSidMsg.Sid,
|
Sid: natHoleSidMsg.Sid,
|
||||||
}
|
}
|
||||||
raddr, _ := net.ResolveUDPAddr("udp",
|
raddr, _ := net.ResolveUDPAddr("udp",
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
|
||||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
defer clientConn.Close()
|
defer clientConn.Close()
|
||||||
|
|
||||||
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("send natHoleClientMsg to server error: %v", err)
|
xl.Error("send natHoleClientMsg to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,28 +318,28 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
buf := pool.GetBuf(1024)
|
buf := pool.GetBuf(1024)
|
||||||
n, err := clientConn.Read(buf)
|
n, err := clientConn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("get natHoleRespMsg error: %v", err)
|
xl.Error("get natHoleRespMsg error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("get natHoleRespMsg error: %v", err)
|
xl.Error("get natHoleRespMsg error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientConn.SetReadDeadline(time.Time{})
|
clientConn.SetReadDeadline(time.Time{})
|
||||||
clientConn.Close()
|
clientConn.Close()
|
||||||
|
|
||||||
if natHoleRespMsg.Error != "" {
|
if natHoleRespMsg.Error != "" {
|
||||||
pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Send detect message
|
// Send detect message
|
||||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||||
if len(array) <= 1 {
|
if len(array) <= 1 {
|
||||||
pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
}
|
}
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
/*
|
/*
|
||||||
@@ -298,18 +349,18 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
*/
|
*/
|
||||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
pxy.Trace("send all detect msg done")
|
xl.Trace("send all detect msg done")
|
||||||
|
|
||||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||||
|
|
||||||
// Listen for clientConn's address and wait for visitor connection
|
// Listen for clientConn's address and wait for visitor connection
|
||||||
lConn, err := net.ListenUDP("udp", laddr)
|
lConn, err := net.ListenUDP("udp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("listen on visitorConn's local adress error: %v", err)
|
xl.Error("listen on visitorConn's local adress error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer lConn.Close()
|
defer lConn.Close()
|
||||||
@@ -319,22 +370,22 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
var uAddr *net.UDPAddr
|
var uAddr *net.UDPAddr
|
||||||
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Warn("get sid from visitor error: %v", err)
|
xl.Warn("get sid from visitor error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lConn.SetReadDeadline(time.Time{})
|
lConn.SetReadDeadline(time.Time{})
|
||||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
pxy.Warn("incorrect sid from visitor")
|
xl.Warn("incorrect sid from visitor")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pool.PutBuf(sidBuf)
|
pool.PutBuf(sidBuf)
|
||||||
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||||
|
|
||||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
|
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, uAddr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create kcp connection from udp connection error: %v", err)
|
xl.Error("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,18 +394,18 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
fmuxCfg.LogOutput = ioutil.Discard
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create yamux server from kcp connection error: %v", err)
|
xl.Error("create yamux server from kcp connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer sess.Close()
|
defer sess.Close()
|
||||||
muxConn, err := sess.Accept()
|
muxConn, err := sess.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("accept for yamux connection error: %v", err)
|
xl.Error("accept for yamux connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
|
||||||
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
|
muxConn, []byte(pxy.cfg.Sk), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||||
@@ -387,7 +438,7 @@ type UdpProxy struct {
|
|||||||
|
|
||||||
// include msg.UdpPacket and msg.Ping
|
// include msg.UdpPacket and msg.Ping
|
||||||
sendCh chan msg.Message
|
sendCh chan msg.Message
|
||||||
workConn frpNet.Conn
|
workConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UdpProxy) Run() (err error) {
|
func (pxy *UdpProxy) Run() (err error) {
|
||||||
@@ -416,11 +467,19 @@ func (pxy *UdpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
xl := pxy.xl
|
||||||
|
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
// close resources releated with old workConn
|
// close resources releated with old workConn
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
|
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc := frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
}
|
||||||
|
|
||||||
pxy.mu.Lock()
|
pxy.mu.Lock()
|
||||||
pxy.workConn = conn
|
pxy.workConn = conn
|
||||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||||
@@ -432,32 +491,32 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
for {
|
for {
|
||||||
var udpMsg msg.UdpPacket
|
var udpMsg msg.UdpPacket
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if errRet := errors.PanicToError(func() {
|
if errRet := errors.PanicToError(func() {
|
||||||
pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
|
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||||
readCh <- &udpMsg
|
readCh <- &udpMsg
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
|
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
defer func() {
|
defer func() {
|
||||||
pxy.Info("writer goroutine for udp work connection closed")
|
xl.Info("writer goroutine for udp work connection closed")
|
||||||
}()
|
}()
|
||||||
var errRet error
|
var errRet error
|
||||||
for rawMsg := range sendCh {
|
for rawMsg := range sendCh {
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.UdpPacket:
|
case *msg.UdpPacket:
|
||||||
pxy.Trace("send udp package to workConn: %s", m.Content)
|
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
pxy.Trace("send ping message to udp workConn")
|
xl.Trace("send ping message to udp workConn")
|
||||||
}
|
}
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
pxy.Error("udp work write error: %v", errRet)
|
xl.Error("udp work write error: %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +528,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
if errRet = errors.PanicToError(func() {
|
if errRet = errors.PanicToError(func() {
|
||||||
sendCh <- &msg.Ping{}
|
sendCh <- &msg.Ping{}
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
pxy.Trace("heartbeat goroutine for udp work connection closed")
|
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,20 +541,27 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func HandleTcpWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
|
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
remote = workConn
|
remote = workConn
|
||||||
|
if limiter != nil {
|
||||||
|
remote = frpIo.WrapReadWriteCloser(limit.NewReader(workConn, limiter), limit.NewWriter(workConn, limiter), func() error {
|
||||||
|
return workConn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
||||||
|
baseInfo.UseEncryption, baseInfo.UseCompression)
|
||||||
if baseInfo.UseEncryption {
|
if baseInfo.UseEncryption {
|
||||||
remote, err = frpIo.WithEncryption(remote, encKey)
|
remote, err = frpIo.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
workConn.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,7 +584,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
DestinationPort: m.DstPort,
|
DestinationPort: m.DstPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.SourceAddress.To16() == nil {
|
if strings.Contains(m.SrcAddr, ".") {
|
||||||
h.TransportProtocol = pp.TCPv4
|
h.TransportProtocol = pp.TCPv4
|
||||||
} else {
|
} else {
|
||||||
h.TransportProtocol = pp.TCPv6
|
h.TransportProtocol = pp.TCPv6
|
||||||
@@ -538,19 +604,19 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
|
|
||||||
if proxyPlugin != nil {
|
if proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connections first
|
// if plugin is set, let plugin handle connections first
|
||||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
xl.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||||
workConn.Debug("handle by plugin finished")
|
xl.Debug("handle by plugin finished")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
|
|
||||||
if len(extraInfo) > 0 {
|
if len(extraInfo) > 0 {
|
||||||
@@ -558,6 +624,6 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
}
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, remote)
|
frpIo.Join(localConn, remote)
|
||||||
workConn.Debug("join connections closed")
|
xl.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/fatedier/frp/client/event"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
@@ -20,17 +21,22 @@ type ProxyManager struct {
|
|||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
logPrefix string
|
clientCfg config.ClientCommonConf
|
||||||
log.Logger
|
|
||||||
|
// The UDP port that the server is listening on
|
||||||
|
serverUDPPort int
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
|
func NewProxyManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
|
||||||
return &ProxyManager{
|
return &ProxyManager{
|
||||||
proxies: make(map[string]*ProxyWrapper),
|
sendCh: msgSendCh,
|
||||||
sendCh: msgSendCh,
|
proxies: make(map[string]*ProxyWrapper),
|
||||||
closed: false,
|
closed: false,
|
||||||
logPrefix: logPrefix,
|
clientCfg: clientCfg,
|
||||||
Logger: log.NewPrefixLogger(logPrefix),
|
serverUDPPort: serverUDPPort,
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +64,7 @@ func (pm *ProxyManager) Close() {
|
|||||||
pm.proxies = make(map[string]*ProxyWrapper)
|
pm.proxies = make(map[string]*ProxyWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pm *ProxyManager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
|
||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
pw, ok := pm.proxies[name]
|
pw, ok := pm.proxies[name]
|
||||||
pm.mu.RUnlock()
|
pm.mu.RUnlock()
|
||||||
@@ -97,6 +103,7 @@ func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||||
|
xl := xlog.FromContextSafe(pm.ctx)
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
@@ -120,13 +127,13 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(delPxyNames) > 0 {
|
if len(delPxyNames) > 0 {
|
||||||
pm.Info("proxy removed: %v", delPxyNames)
|
xl.Info("proxy removed: %v", delPxyNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for name, cfg := range pxyCfgs {
|
for name, cfg := range pxyCfgs {
|
||||||
if _, ok := pm.proxies[name]; !ok {
|
if _, ok := pm.proxies[name]; !ok {
|
||||||
pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
|
pxy := NewProxyWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
|
||||||
pm.proxies[name] = pxy
|
pm.proxies[name] = pxy
|
||||||
addPxyNames = append(addPxyNames, name)
|
addPxyNames = append(addPxyNames, name)
|
||||||
|
|
||||||
@@ -134,6 +141,6 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(addPxyNames) > 0 {
|
if len(addPxyNames) > 0 {
|
||||||
pm.Info("proxy added: %v", addPxyNames)
|
xl.Info("proxy added: %v", addPxyNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,8 +12,7 @@ import (
|
|||||||
"github.com/fatedier/frp/client/health"
|
"github.com/fatedier/frp/client/health"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
@@ -62,11 +63,13 @@ type ProxyWrapper struct {
|
|||||||
healthNotifyCh chan struct{}
|
healthNotifyCh chan struct{}
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
log.Logger
|
xl *xlog.Logger
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
|
func NewProxyWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, serverUDPPort int) *ProxyWrapper {
|
||||||
baseInfo := cfg.GetBaseInfo()
|
baseInfo := cfg.GetBaseInfo()
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
||||||
pw := &ProxyWrapper{
|
pw := &ProxyWrapper{
|
||||||
ProxyStatus: ProxyStatus{
|
ProxyStatus: ProxyStatus{
|
||||||
Name: baseInfo.ProxyName,
|
Name: baseInfo.ProxyName,
|
||||||
@@ -77,20 +80,19 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP
|
|||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
healthNotifyCh: make(chan struct{}),
|
healthNotifyCh: make(chan struct{}),
|
||||||
handler: eventHandler,
|
handler: eventHandler,
|
||||||
Logger: log.NewPrefixLogger(logPrefix),
|
xl: xl,
|
||||||
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
pw.AddLogPrefix(pw.Name)
|
|
||||||
|
|
||||||
if baseInfo.HealthCheckType != "" {
|
if baseInfo.HealthCheckType != "" {
|
||||||
pw.health = 1 // means failed
|
pw.health = 1 // means failed
|
||||||
pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
pw.monitor = health.NewHealthCheckMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
||||||
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
|
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
|
||||||
baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
|
baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
|
||||||
pw.monitor.SetLogger(pw.Logger)
|
xl.Trace("enable health check monitor")
|
||||||
pw.Trace("enable health check monitor")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.pxy = NewProxy(pw.Cfg)
|
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +112,7 @@ func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := pw.pxy.Run(); err != nil {
|
if err := pw.pxy.Run(); err != nil {
|
||||||
|
pw.close()
|
||||||
pw.Status = ProxyStatusStartErr
|
pw.Status = ProxyStatusStartErr
|
||||||
pw.Err = err.Error()
|
pw.Err = err.Error()
|
||||||
pw.lastStartErr = time.Now()
|
pw.lastStartErr = time.Now()
|
||||||
@@ -138,7 +141,10 @@ func (pw *ProxyWrapper) Stop() {
|
|||||||
pw.monitor.Stop()
|
pw.monitor.Stop()
|
||||||
}
|
}
|
||||||
pw.Status = ProxyStatusClosed
|
pw.Status = ProxyStatusClosed
|
||||||
|
pw.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) close() {
|
||||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||||
CloseProxyMsg: &msg.CloseProxy{
|
CloseProxyMsg: &msg.CloseProxy{
|
||||||
ProxyName: pw.Name,
|
ProxyName: pw.Name,
|
||||||
@@ -147,6 +153,7 @@ func (pw *ProxyWrapper) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) checkWorker() {
|
func (pw *ProxyWrapper) checkWorker() {
|
||||||
|
xl := pw.xl
|
||||||
if pw.monitor != nil {
|
if pw.monitor != nil {
|
||||||
// let monitor do check request first
|
// let monitor do check request first
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
@@ -161,7 +168,7 @@ func (pw *ProxyWrapper) checkWorker() {
|
|||||||
(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
||||||
(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
||||||
|
|
||||||
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
|
xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
|
||||||
pw.Status = ProxyStatusWaitStart
|
pw.Status = ProxyStatusWaitStart
|
||||||
|
|
||||||
var newProxyMsg msg.NewProxy
|
var newProxyMsg msg.NewProxy
|
||||||
@@ -175,12 +182,8 @@ func (pw *ProxyWrapper) checkWorker() {
|
|||||||
} else {
|
} else {
|
||||||
pw.mu.Lock()
|
pw.mu.Lock()
|
||||||
if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart {
|
if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart {
|
||||||
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
pw.close()
|
||||||
CloseProxyMsg: &msg.CloseProxy{
|
xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
|
||||||
ProxyName: pw.Name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
|
|
||||||
pw.Status = ProxyStatusCheckFailed
|
pw.Status = ProxyStatusCheckFailed
|
||||||
}
|
}
|
||||||
pw.mu.Unlock()
|
pw.mu.Unlock()
|
||||||
@@ -196,6 +199,7 @@ func (pw *ProxyWrapper) checkWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) statusNormalCallback() {
|
func (pw *ProxyWrapper) statusNormalCallback() {
|
||||||
|
xl := pw.xl
|
||||||
atomic.StoreUint32(&pw.health, 0)
|
atomic.StoreUint32(&pw.health, 0)
|
||||||
errors.PanicToError(func() {
|
errors.PanicToError(func() {
|
||||||
select {
|
select {
|
||||||
@@ -203,10 +207,11 @@ func (pw *ProxyWrapper) statusNormalCallback() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
pw.Info("health check success")
|
xl.Info("health check success")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) statusFailedCallback() {
|
func (pw *ProxyWrapper) statusFailedCallback() {
|
||||||
|
xl := pw.xl
|
||||||
atomic.StoreUint32(&pw.health, 1)
|
atomic.StoreUint32(&pw.health, 1)
|
||||||
errors.PanicToError(func() {
|
errors.PanicToError(func() {
|
||||||
select {
|
select {
|
||||||
@@ -214,15 +219,16 @@ func (pw *ProxyWrapper) statusFailedCallback() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
pw.Info("health check failed")
|
xl.Info("health check failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
|
func (pw *ProxyWrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
xl := pw.xl
|
||||||
pw.mu.RLock()
|
pw.mu.RLock()
|
||||||
pxy := pw.pxy
|
pxy := pw.pxy
|
||||||
pw.mu.RUnlock()
|
pw.mu.RUnlock()
|
||||||
if pxy != nil {
|
if pxy != nil {
|
||||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
go pxy.InWorkConn(workConn, m)
|
go pxy.InWorkConn(workConn, m)
|
||||||
} else {
|
} else {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
|
|||||||
@@ -15,26 +15,29 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/auth"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
"github.com/fatedier/frp/utils/util"
|
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Service is a client service.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// uniq id got from frps, attach it in loginMsg
|
// uniq id got from frps, attach it in loginMsg
|
||||||
runId string
|
runId string
|
||||||
@@ -43,27 +46,41 @@ type Service struct {
|
|||||||
ctl *Control
|
ctl *Control
|
||||||
ctlMu sync.RWMutex
|
ctlMu sync.RWMutex
|
||||||
|
|
||||||
|
// Sets authentication based on selected method
|
||||||
|
authSetter auth.Setter
|
||||||
|
|
||||||
|
cfg config.ClientCommonConf
|
||||||
pxyCfgs map[string]config.ProxyConf
|
pxyCfgs map[string]config.ProxyConf
|
||||||
visitorCfgs map[string]config.VisitorConf
|
visitorCfgs map[string]config.VisitorConf
|
||||||
cfgMu sync.RWMutex
|
cfgMu sync.RWMutex
|
||||||
|
|
||||||
exit uint32 // 0 means not exit
|
// The configuration file used to initialize this client, or an empty
|
||||||
closedCh chan int
|
// string if no configuration file was used.
|
||||||
|
cfgFile string
|
||||||
|
|
||||||
|
// This is configured by the login response from frps
|
||||||
|
serverUDPPort int
|
||||||
|
|
||||||
|
exit uint32 // 0 means not exit
|
||||||
|
|
||||||
|
// service context
|
||||||
|
ctx context.Context
|
||||||
|
// call cancel to stop service
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
|
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
|
||||||
// Init assets
|
|
||||||
err = assets.Load("")
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Load assets error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
|
authSetter: auth.NewAuthSetter(cfg.AuthClientConfig),
|
||||||
|
cfg: cfg,
|
||||||
|
cfgFile: cfgFile,
|
||||||
pxyCfgs: pxyCfgs,
|
pxyCfgs: pxyCfgs,
|
||||||
visitorCfgs: visitorCfgs,
|
visitorCfgs: visitorCfgs,
|
||||||
exit: 0,
|
exit: 0,
|
||||||
closedCh: make(chan int),
|
ctx: xlog.NewContext(ctx, xlog.New()),
|
||||||
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -75,22 +92,24 @@ func (svr *Service) GetController() *Control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Run() error {
|
func (svr *Service) Run() error {
|
||||||
// first login
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
|
|
||||||
|
// login to frps
|
||||||
for {
|
for {
|
||||||
conn, session, err := svr.login()
|
conn, session, err := svr.login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("login to server failed: %v", err)
|
xl.Warn("login to server failed: %v", err)
|
||||||
|
|
||||||
// if login_fail_exit is true, just exit this program
|
// if login_fail_exit is true, just exit this program
|
||||||
// otherwise sleep a while and try again to connect to server
|
// otherwise sleep a while and try again to connect to server
|
||||||
if g.GlbClientCfg.LoginFailExit {
|
if svr.cfg.LoginFailExit {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// login success
|
// login success
|
||||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
@@ -101,19 +120,25 @@ func (svr *Service) Run() error {
|
|||||||
|
|
||||||
go svr.keepControllerWorking()
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
if g.GlbClientCfg.AdminPort != 0 {
|
if svr.cfg.AdminPort != 0 {
|
||||||
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
// Init admin server assets
|
||||||
|
err := assets.Load(svr.cfg.AssetsDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Load assets error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("run admin server error: %v", err)
|
log.Warn("run admin server error: %v", err)
|
||||||
}
|
}
|
||||||
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
||||||
}
|
}
|
||||||
|
<-svr.ctx.Done()
|
||||||
<-svr.closedCh
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) keepControllerWorking() {
|
func (svr *Service) keepControllerWorking() {
|
||||||
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
maxDelayTime := 20 * time.Second
|
maxDelayTime := 20 * time.Second
|
||||||
delayTime := time.Second
|
delayTime := time.Second
|
||||||
|
|
||||||
@@ -124,10 +149,10 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
log.Info("try to reconnect to server...")
|
xl.Info("try to reconnect to server...")
|
||||||
conn, session, err := svr.login()
|
conn, session, err := svr.login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("reconnect to server error: %v", err)
|
xl.Warn("reconnect to server error: %v", err)
|
||||||
time.Sleep(delayTime)
|
time.Sleep(delayTime)
|
||||||
delayTime = delayTime * 2
|
delayTime = delayTime * 2
|
||||||
if delayTime > maxDelayTime {
|
if delayTime > maxDelayTime {
|
||||||
@@ -138,7 +163,7 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// reconnect success, init delayTime
|
// reconnect success, init delayTime
|
||||||
delayTime = time.Second
|
delayTime = time.Second
|
||||||
|
|
||||||
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
@@ -151,15 +176,16 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// login creates a connection to frps and registers it self as a client
|
// login creates a connection to frps and registers it self as a client
|
||||||
// conn: control connection
|
// conn: control connection
|
||||||
// session: if it's not nil, using tcp mux
|
// session: if it's not nil, using tcp mux
|
||||||
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||||
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if g.GlbClientCfg.TLSEnable {
|
if svr.cfg.TLSEnable {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -173,7 +199,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if g.GlbClientCfg.TcpMux {
|
if svr.cfg.TcpMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||||
fmuxCfg.LogOutput = ioutil.Discard
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
@@ -187,19 +213,23 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn = frpNet.WrapConn(stream)
|
conn = stream
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
loginMsg := &msg.Login{
|
loginMsg := &msg.Login{
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
PoolCount: g.GlbClientCfg.PoolCount,
|
PoolCount: svr.cfg.PoolCount,
|
||||||
User: g.GlbClientCfg.User,
|
User: svr.cfg.User,
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
|
Timestamp: time.Now().Unix(),
|
||||||
Timestamp: now,
|
RunId: svr.runId,
|
||||||
RunId: svr.runId,
|
Metas: svr.cfg.Metas,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add auth
|
||||||
|
if err = svr.authSetter.SetLogin(loginMsg); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = msg.WriteMsg(conn, loginMsg); err != nil {
|
if err = msg.WriteMsg(conn, loginMsg); err != nil {
|
||||||
@@ -215,13 +245,16 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
|
|
||||||
if loginRespMsg.Error != "" {
|
if loginRespMsg.Error != "" {
|
||||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||||
log.Error("%s", loginRespMsg.Error)
|
xl.Error("%s", loginRespMsg.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svr.runId = loginRespMsg.RunId
|
svr.runId = loginRespMsg.RunId
|
||||||
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
xl.ResetPrefixes()
|
||||||
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
xl.AppendPrefix(svr.runId)
|
||||||
|
|
||||||
|
svr.serverUDPPort = loginRespMsg.ServerUdpPort
|
||||||
|
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,5 +270,5 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
|
|||||||
func (svr *Service) Close() {
|
func (svr *Service) Close() {
|
||||||
atomic.StoreUint32(&svr.exit, 1)
|
atomic.StoreUint32(&svr.exit, 1)
|
||||||
svr.ctl.Close()
|
svr.ctl.Close()
|
||||||
close(svr.closedCh)
|
svr.cancel()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -23,12 +24,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
@@ -39,13 +39,13 @@ import (
|
|||||||
type Visitor interface {
|
type Visitor interface {
|
||||||
Run() error
|
Run() error
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
||||||
baseVisitor := BaseVisitor{
|
baseVisitor := BaseVisitor{
|
||||||
ctl: ctl,
|
ctl: ctl,
|
||||||
Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
switch cfg := cfg.(type) {
|
switch cfg := cfg.(type) {
|
||||||
case *config.StcpVisitorConf:
|
case *config.StcpVisitorConf:
|
||||||
@@ -64,10 +64,11 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
|||||||
|
|
||||||
type BaseVisitor struct {
|
type BaseVisitor struct {
|
||||||
ctl *Control
|
ctl *Control
|
||||||
l frpNet.Listener
|
l net.Listener
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
|
||||||
log.Logger
|
mu sync.RWMutex
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
type StcpVisitor struct {
|
type StcpVisitor struct {
|
||||||
@@ -77,7 +78,7 @@ type StcpVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *StcpVisitor) Run() (err error) {
|
func (sv *StcpVisitor) Run() (err error) {
|
||||||
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
|
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -91,10 +92,11 @@ func (sv *StcpVisitor) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *StcpVisitor) worker() {
|
func (sv *StcpVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
for {
|
for {
|
||||||
conn, err := sv.l.Accept()
|
conn, err := sv.l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("stcp local listener closed")
|
xl.Warn("stcp local listener closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +104,11 @@ func (sv *StcpVisitor) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
func (sv *StcpVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
sv.Debug("get a new stcp user connection")
|
xl.Debug("get a new stcp user connection")
|
||||||
visitorConn, err := sv.ctl.connectServer()
|
visitorConn, err := sv.ctl.connectServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -122,7 +125,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
}
|
}
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("send newVisitorConnMsg to server error: %v", err)
|
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,13 +133,13 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("get newVisitorConnRespMsg error: %v", err)
|
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
visitorConn.SetReadDeadline(time.Time{})
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
if newVisitorConnRespMsg.Error != "" {
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +148,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
if sv.cfg.UseEncryption {
|
if sv.cfg.UseEncryption {
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +167,7 @@ type XtcpVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XtcpVisitor) Run() (err error) {
|
func (sv *XtcpVisitor) Run() (err error) {
|
||||||
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
|
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -178,10 +181,11 @@ func (sv *XtcpVisitor) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XtcpVisitor) worker() {
|
func (sv *XtcpVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
for {
|
for {
|
||||||
conn, err := sv.l.Accept()
|
conn, err := sv.l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("xtcp local listener closed")
|
xl.Warn("xtcp local listener closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,25 +193,26 @@ func (sv *XtcpVisitor) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
func (sv *XtcpVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
sv.Debug("get a new xtcp user connection")
|
xl.Debug("get a new xtcp user connection")
|
||||||
if g.GlbClientCfg.ServerUdpPort == 0 {
|
if sv.ctl.serverUDPPort == 0 {
|
||||||
sv.Error("xtcp is not supported by server")
|
xl.Error("xtcp is not supported by server")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp",
|
raddr, err := net.ResolveUDPAddr("udp",
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("resolve server UDP addr error")
|
xl.Error("resolve server UDP addr error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("dial server udp addr error: %v", err)
|
xl.Warn("dial server udp addr error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer visitorConn.Close()
|
defer visitorConn.Close()
|
||||||
@@ -220,7 +225,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
}
|
}
|
||||||
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("send natHoleVisitorMsg to server error: %v", err)
|
xl.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,24 +235,24 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
buf := pool.GetBuf(1024)
|
buf := pool.GetBuf(1024)
|
||||||
n, err := visitorConn.Read(buf)
|
n, err := visitorConn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("get natHoleRespMsg error: %v", err)
|
xl.Warn("get natHoleRespMsg error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("get natHoleRespMsg error: %v", err)
|
xl.Warn("get natHoleRespMsg error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
visitorConn.SetReadDeadline(time.Time{})
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
pool.PutBuf(buf)
|
pool.PutBuf(buf)
|
||||||
|
|
||||||
if natHoleRespMsg.Error != "" {
|
if natHoleRespMsg.Error != "" {
|
||||||
sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Close visitorConn, so we can use it's local address.
|
// Close visitorConn, so we can use it's local address.
|
||||||
visitorConn.Close()
|
visitorConn.Close()
|
||||||
@@ -256,12 +261,12 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("resolve client udp address error: %v", err)
|
xl.Error("resolve client udp address error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("dial client udp address error: %v", err)
|
xl.Error("dial client udp address error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer lConn.Close()
|
defer lConn.Close()
|
||||||
@@ -273,53 +278,53 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
n, err = lConn.Read(sidBuf)
|
n, err = lConn.Read(sidBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("get sid from client error: %v", err)
|
xl.Warn("get sid from client error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lConn.SetReadDeadline(time.Time{})
|
lConn.SetReadDeadline(time.Time{})
|
||||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
sv.Warn("incorrect sid from client")
|
xl.Warn("incorrect sid from client")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pool.PutBuf(sidBuf)
|
pool.PutBuf(sidBuf)
|
||||||
|
|
||||||
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
// wrap kcp connection
|
// wrap kcp connection
|
||||||
var remote io.ReadWriteCloser
|
var remote io.ReadWriteCloser
|
||||||
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
|
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("create kcp connection from udp connection error: %v", err)
|
xl.Error("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
sv.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
remote = frpIo.WithCompression(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
fmuxCfg.LogOutput = ioutil.Discard
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
sess, err := fmux.Client(remote, fmuxCfg)
|
sess, err := fmux.Client(remote, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("create yamux session error: %v", err)
|
xl.Error("create yamux session error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer sess.Close()
|
defer sess.Close()
|
||||||
muxConn, err := sess.Open()
|
muxConn, err := sess.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("open yamux stream error: %v", err)
|
xl.Error("open yamux stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
frpIo.Join(userConn, muxConn)
|
var muxConnRWCloser io.ReadWriteCloser = muxConn
|
||||||
sv.Debug("join connections closed")
|
if sv.cfg.UseEncryption {
|
||||||
|
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, muxConnRWCloser)
|
||||||
|
xl.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,12 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VisitorManager struct {
|
type VisitorManager struct {
|
||||||
@@ -30,26 +31,29 @@ type VisitorManager struct {
|
|||||||
|
|
||||||
checkInterval time.Duration
|
checkInterval time.Duration
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVisitorManager(ctl *Control) *VisitorManager {
|
func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
|
||||||
return &VisitorManager{
|
return &VisitorManager{
|
||||||
ctl: ctl,
|
ctl: ctl,
|
||||||
cfgs: make(map[string]config.VisitorConf),
|
cfgs: make(map[string]config.VisitorConf),
|
||||||
visitors: make(map[string]Visitor),
|
visitors: make(map[string]Visitor),
|
||||||
checkInterval: 10 * time.Second,
|
checkInterval: 10 * time.Second,
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Run() {
|
func (vm *VisitorManager) Run() {
|
||||||
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
for {
|
for {
|
||||||
time.Sleep(vm.checkInterval)
|
time.Sleep(vm.checkInterval)
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
for _, cfg := range vm.cfgs {
|
for _, cfg := range vm.cfgs {
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
name := cfg.GetBaseInfo().ProxyName
|
||||||
if _, exist := vm.visitors[name]; !exist {
|
if _, exist := vm.visitors[name]; !exist {
|
||||||
log.Info("try to start visitor [%s]", name)
|
xl.Info("try to start visitor [%s]", name)
|
||||||
vm.startVisitor(cfg)
|
vm.startVisitor(cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,19 +63,21 @@ func (vm *VisitorManager) Run() {
|
|||||||
|
|
||||||
// Hold lock before calling this function.
|
// Hold lock before calling this function.
|
||||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||||
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
name := cfg.GetBaseInfo().ProxyName
|
||||||
visitor := NewVisitor(vm.ctl, cfg)
|
visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
|
||||||
err = visitor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
visitor.Warn("start error: %v", err)
|
xl.Warn("start error: %v", err)
|
||||||
} else {
|
} else {
|
||||||
vm.visitors[name] = visitor
|
vm.visitors[name] = visitor
|
||||||
visitor.Info("start visitor success")
|
xl.Info("start visitor success")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||||
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
@@ -97,7 +103,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(delNames) > 0 {
|
if len(delNames) > 0 {
|
||||||
log.Info("visitor removed: %v", delNames)
|
xl.Info("visitor removed: %v", delNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
addNames := make([]string, 0)
|
addNames := make([]string, 0)
|
||||||
@@ -109,7 +115,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(addNames) > 0 {
|
if len(addNames) > 0 {
|
||||||
log.Info("visitor added: %v", addNames)
|
xl.Info("visitor added: %v", addNames)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||||
|
|
||||||
@@ -23,6 +26,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
sub.Execute()
|
sub.Execute()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func init() {
|
|||||||
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
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().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
httpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{
|
|||||||
Use: "http",
|
Use: "http",
|
||||||
Short: "Run frpc with a single http proxy",
|
Short: "Run frpc with a single http proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func init() {
|
|||||||
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
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().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
httpsCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{
|
|||||||
Use: "https",
|
Use: "https",
|
||||||
Short: "Run frpc with a single https proxy",
|
Short: "Run frpc with a single https proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,13 +41,13 @@ var reloadCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = reload()
|
err = reload(clientCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("frpc reload error: %v\n", err)
|
fmt.Printf("frpc reload error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -58,19 +57,19 @@ var reloadCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func reload() error {
|
func reload(clientCfg config.ClientCommonConf) error {
|
||||||
if g.GlbClientCfg.AdminPort == 0 {
|
if clientCfg.AdminPort == 0 {
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
|
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||||
g.GlbClientCfg.AdminPwd))
|
clientCfg.AdminPwd))
|
||||||
|
|
||||||
req.Header.Add("Authorization", authStr)
|
req.Header.Add("Authorization", authStr)
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/auth"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
@@ -43,13 +43,14 @@ var (
|
|||||||
cfgFile string
|
cfgFile string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
|
||||||
serverAddr string
|
serverAddr string
|
||||||
user string
|
user string
|
||||||
protocol string
|
protocol string
|
||||||
token string
|
token string
|
||||||
logLevel string
|
logLevel string
|
||||||
logFile string
|
logFile string
|
||||||
logMaxDays int
|
logMaxDays int
|
||||||
|
disableLogColor bool
|
||||||
|
|
||||||
proxyName string
|
proxyName string
|
||||||
localIp string
|
localIp string
|
||||||
@@ -65,6 +66,7 @@ var (
|
|||||||
hostHeaderRewrite string
|
hostHeaderRewrite string
|
||||||
role string
|
role string
|
||||||
sk string
|
sk string
|
||||||
|
multiplexer string
|
||||||
serverName string
|
serverName string
|
||||||
bindAddr string
|
bindAddr string
|
||||||
bindPort int
|
bindPort int
|
||||||
@@ -113,59 +115,65 @@ func handleSignal(svr *client.Service) {
|
|||||||
close(kcpDoneCh)
|
close(kcpDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfg(fileType int, content string) (err error) {
|
func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
|
||||||
if fileType == CfgFileTypeIni {
|
if fileType == CfgFileTypeIni {
|
||||||
err = parseClientCommonCfgFromIni(content)
|
cfg, err = parseClientCommonCfgFromIni(content)
|
||||||
} else if fileType == CfgFileTypeCmd {
|
} else if fileType == CfgFileTypeCmd {
|
||||||
err = parseClientCommonCfgFromCmd()
|
cfg, err = parseClientCommonCfgFromCmd()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = g.GlbClientCfg.ClientCommonConf.Check()
|
err = cfg.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfgFromIni(content string) (err error) {
|
func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
|
||||||
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
cfg, err := config.UnmarshalClientConfFromIni(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return config.ClientCommonConf{}, err
|
||||||
}
|
}
|
||||||
g.GlbClientCfg.ClientCommonConf = *cfg
|
return cfg, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientCommonCfgFromCmd() (err error) {
|
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||||
|
cfg = config.GetDefaultClientConf()
|
||||||
|
|
||||||
strs := strings.Split(serverAddr, ":")
|
strs := strings.Split(serverAddr, ":")
|
||||||
if len(strs) < 2 {
|
if len(strs) < 2 {
|
||||||
err = fmt.Errorf("invalid server_addr")
|
err = fmt.Errorf("invalid server_addr")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strs[0] != "" {
|
if strs[0] != "" {
|
||||||
g.GlbClientCfg.ServerAddr = strs[0]
|
cfg.ServerAddr = strs[0]
|
||||||
}
|
}
|
||||||
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
|
cfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("invalid server_addr")
|
err = fmt.Errorf("invalid server_addr")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
g.GlbClientCfg.User = user
|
cfg.User = user
|
||||||
g.GlbClientCfg.Protocol = protocol
|
cfg.Protocol = protocol
|
||||||
g.GlbClientCfg.Token = token
|
cfg.LogLevel = logLevel
|
||||||
g.GlbClientCfg.LogLevel = logLevel
|
cfg.LogFile = logFile
|
||||||
g.GlbClientCfg.LogFile = logFile
|
cfg.LogMaxDays = int64(logMaxDays)
|
||||||
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
|
||||||
if logFile == "console" {
|
if logFile == "console" {
|
||||||
g.GlbClientCfg.LogWay = "console"
|
cfg.LogWay = "console"
|
||||||
} else {
|
} else {
|
||||||
g.GlbClientCfg.LogWay = "file"
|
cfg.LogWay = "file"
|
||||||
}
|
}
|
||||||
return nil
|
cfg.DisableLogColor = disableLogColor
|
||||||
|
|
||||||
|
// Only token authentication is supported in cmd mode
|
||||||
|
cfg.AuthClientConfig = auth.GetDefaultAuthClientConf()
|
||||||
|
cfg.Token = token
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClient(cfgFilePath string) (err error) {
|
func runClient(cfgFilePath string) (err error) {
|
||||||
@@ -174,26 +182,27 @@ func runClient(cfgFilePath string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.GlbClientCfg.CfgFile = cfgFilePath
|
|
||||||
|
|
||||||
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = startService(pxyCfgs, visitorCfgs)
|
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
|
func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
|
||||||
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
|
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||||
if g.GlbClientCfg.DnsServer != "" {
|
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||||
s := g.GlbClientCfg.DnsServer
|
|
||||||
|
if cfg.DnsServer != "" {
|
||||||
|
s := cfg.DnsServer
|
||||||
if !strings.Contains(s, ":") {
|
if !strings.Contains(s, ":") {
|
||||||
s += ":53"
|
s += ":53"
|
||||||
}
|
}
|
||||||
@@ -205,19 +214,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture the exit signal if we use kcp.
|
// Capture the exit signal if we use kcp.
|
||||||
if g.GlbClientCfg.Protocol == "kcp" {
|
if cfg.Protocol == "kcp" {
|
||||||
go handleSignal(svr)
|
go handleSignal(svr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svr.Run()
|
err = svr.Run()
|
||||||
if g.GlbClientCfg.Protocol == "kcp" {
|
if cfg.Protocol == "kcp" {
|
||||||
<-kcpDoneCh
|
<-kcpDoneCh
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = status()
|
err = status(clientCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("frpc get status error: %v\n", err)
|
fmt.Printf("frpc get status error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -60,19 +59,19 @@ var statusCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func status() error {
|
func status(clientCfg config.ClientCommonConf) error {
|
||||||
if g.GlbClientCfg.AdminPort == 0 {
|
if clientCfg.AdminPort == 0 {
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
|
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
||||||
g.GlbClientCfg.AdminPwd))
|
clientCfg.AdminPwd))
|
||||||
|
|
||||||
req.Header.Add("Authorization", authStr)
|
req.Header.Add("Authorization", authStr)
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func init() {
|
|||||||
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
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().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
stcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
@@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{
|
|||||||
Use: "stcp",
|
Use: "stcp",
|
||||||
Short: "Run frpc with a single stcp proxy",
|
Short: "Run frpc with a single stcp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = startService(proxyConfs, visitorConfs)
|
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func init() {
|
|||||||
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
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().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
tcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{
|
|||||||
Use: "tcp",
|
Use: "tcp",
|
||||||
Short: "Run frpc with a single tcp proxy",
|
Short: "Run frpc with a single tcp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
91
cmd/frpc/sub/tcpmux.go
Normal file
91
cmd/frpc/sub/tcpmux.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
tcpMuxCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
tcpMuxCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||||
|
tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
|
||||||
|
tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(tcpMuxCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcpMuxCmd = &cobra.Command{
|
||||||
|
Use: "tcpmux",
|
||||||
|
Short: "Run frpc with a single tcpmux proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &config.TcpMuxProxyConf{}
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.TcpMuxProxy
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||||
|
cfg.SubDomain = subDomain
|
||||||
|
cfg.Multiplexer = multiplexer
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
|
cfg.ProxyName: cfg,
|
||||||
|
}
|
||||||
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ func init() {
|
|||||||
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
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().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
udpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
@@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{
|
|||||||
Use: "udp",
|
Use: "udp",
|
||||||
Short: "Run frpc with a single udp proxy",
|
Short: "Run frpc with a single udp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{
|
|||||||
proxyConfs := map[string]config.ProxyConf{
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
cfg.ProxyName: cfg,
|
cfg.ProxyName: cfg,
|
||||||
}
|
}
|
||||||
err = startService(proxyConfs, nil)
|
err = startService(clientCfg, proxyConfs, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func init() {
|
|||||||
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
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().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
xtcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
@@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
Use: "xtcp",
|
Use: "xtcp",
|
||||||
Short: "Run frpc with a single xtcp proxy",
|
Short: "Run frpc with a single xtcp proxy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = startService(proxyConfs, visitorConfs)
|
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -15,13 +15,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||||
|
_ "github.com/fatedier/frp/models/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/auth"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/server"
|
"github.com/fatedier/frp/server"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -53,6 +53,7 @@ var (
|
|||||||
logFile string
|
logFile string
|
||||||
logLevel string
|
logLevel string
|
||||||
logMaxDays int64
|
logMaxDays int64
|
||||||
|
disableLogColor bool
|
||||||
token string
|
token string
|
||||||
subDomainHost string
|
subDomainHost string
|
||||||
tcpMux bool
|
tcpMux bool
|
||||||
@@ -80,6 +81,8 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||||
@@ -95,6 +98,7 @@ var rootCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cfg config.ServerCommonConf
|
||||||
var err error
|
var err error
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
var content string
|
var content string
|
||||||
@@ -102,16 +106,15 @@ var rootCmd = &cobra.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
g.GlbServerCfg.CfgFile = cfgFile
|
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||||
err = parseServerCommonCfg(CfgFileTypeIni, content)
|
|
||||||
} else {
|
} else {
|
||||||
err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runServer()
|
err = runServer(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -126,52 +129,54 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfg(fileType int, content string) (err error) {
|
func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
|
||||||
if fileType == CfgFileTypeIni {
|
if fileType == CfgFileTypeIni {
|
||||||
err = parseServerCommonCfgFromIni(content)
|
cfg, err = parseServerCommonCfgFromIni(content)
|
||||||
} else if fileType == CfgFileTypeCmd {
|
} else if fileType == CfgFileTypeCmd {
|
||||||
err = parseServerCommonCfgFromCmd()
|
cfg, err = parseServerCommonCfgFromCmd()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = g.GlbServerCfg.ServerCommonConf.Check()
|
err = cfg.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfgFromIni(content string) (err error) {
|
func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
|
||||||
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
cfg, err := config.UnmarshalServerConfFromIni(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return config.ServerCommonConf{}, err
|
||||||
}
|
}
|
||||||
g.GlbServerCfg.ServerCommonConf = *cfg
|
return cfg, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfgFromCmd() (err error) {
|
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
||||||
g.GlbServerCfg.BindAddr = bindAddr
|
cfg = config.GetDefaultServerConf()
|
||||||
g.GlbServerCfg.BindPort = bindPort
|
|
||||||
g.GlbServerCfg.BindUdpPort = bindUdpPort
|
cfg.BindAddr = bindAddr
|
||||||
g.GlbServerCfg.KcpBindPort = kcpBindPort
|
cfg.BindPort = bindPort
|
||||||
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
|
cfg.BindUdpPort = bindUdpPort
|
||||||
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
|
cfg.KcpBindPort = kcpBindPort
|
||||||
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
|
cfg.ProxyBindAddr = proxyBindAddr
|
||||||
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
|
cfg.VhostHttpPort = vhostHttpPort
|
||||||
g.GlbServerCfg.DashboardAddr = dashboardAddr
|
cfg.VhostHttpsPort = vhostHttpsPort
|
||||||
g.GlbServerCfg.DashboardPort = dashboardPort
|
cfg.VhostHttpTimeout = vhostHttpTimeout
|
||||||
g.GlbServerCfg.DashboardUser = dashboardUser
|
cfg.DashboardAddr = dashboardAddr
|
||||||
g.GlbServerCfg.DashboardPwd = dashboardPwd
|
cfg.DashboardPort = dashboardPort
|
||||||
g.GlbServerCfg.LogFile = logFile
|
cfg.DashboardUser = dashboardUser
|
||||||
g.GlbServerCfg.LogLevel = logLevel
|
cfg.DashboardPwd = dashboardPwd
|
||||||
g.GlbServerCfg.LogMaxDays = logMaxDays
|
cfg.LogFile = logFile
|
||||||
g.GlbServerCfg.Token = token
|
cfg.LogLevel = logLevel
|
||||||
g.GlbServerCfg.SubDomainHost = subDomainHost
|
cfg.LogMaxDays = logMaxDays
|
||||||
|
cfg.SubDomainHost = subDomainHost
|
||||||
|
|
||||||
|
// Only token authentication is supported in cmd mode
|
||||||
|
cfg.AuthServerConfig = auth.GetDefaultAuthServerConf()
|
||||||
|
cfg.Token = token
|
||||||
if len(allowPorts) > 0 {
|
if len(allowPorts) > 0 {
|
||||||
// e.g. 1000-2000,2001,2002,3000-4000
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||||
@@ -181,28 +186,27 @@ func parseServerCommonCfgFromCmd() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
|
cfg.AllowPorts[int(port)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
cfg.MaxPortsPerClient = maxPortsPerClient
|
||||||
|
|
||||||
if logFile == "console" {
|
if logFile == "console" {
|
||||||
g.GlbServerCfg.LogWay = "console"
|
cfg.LogWay = "console"
|
||||||
} else {
|
} else {
|
||||||
g.GlbServerCfg.LogWay = "file"
|
cfg.LogWay = "file"
|
||||||
}
|
}
|
||||||
|
cfg.DisableLogColor = disableLogColor
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func runServer() (err error) {
|
func runServer(cfg config.ServerCommonConf) (err error) {
|
||||||
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
|
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
|
||||||
g.GlbServerCfg.LogMaxDays)
|
svr, err := server.NewService(cfg)
|
||||||
svr, err := server.NewService()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("Start frps success")
|
log.Info("start frps success")
|
||||||
server.ServerService = svr
|
|
||||||
svr.Run()
|
svr.Run()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ log_level = info
|
|||||||
|
|
||||||
log_max_days = 3
|
log_max_days = 3
|
||||||
|
|
||||||
|
# disable log colors when log_file is console, default is false
|
||||||
|
disable_log_color = false
|
||||||
|
|
||||||
# for authentication
|
# for authentication
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
@@ -26,6 +29,8 @@ admin_addr = 127.0.0.1
|
|||||||
admin_port = 7400
|
admin_port = 7400
|
||||||
admin_user = admin
|
admin_user = admin
|
||||||
admin_pwd = admin
|
admin_pwd = admin
|
||||||
|
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||||
|
# assets_dir = ./static
|
||||||
|
|
||||||
# connections will be established in advance, default value is zero
|
# connections will be established in advance, default value is zero
|
||||||
pool_count = 5
|
pool_count = 5
|
||||||
@@ -59,6 +64,10 @@ tls_enable = true
|
|||||||
# heartbeat_interval = 30
|
# heartbeat_interval = 30
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
|
|
||||||
|
# additional meta info for client
|
||||||
|
meta_var1 = 123
|
||||||
|
meta_var2 = 234
|
||||||
|
|
||||||
# 'ssh' is the unique proxy name
|
# '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'
|
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||||
[ssh]
|
[ssh]
|
||||||
@@ -66,6 +75,8 @@ tls_enable = true
|
|||||||
type = tcp
|
type = tcp
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 22
|
local_port = 22
|
||||||
|
# limit bandwidth for this proxy, unit is KB and MB
|
||||||
|
bandwidth_limit = 1MB
|
||||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
# if true, message will be compressed
|
# if true, message will be compressed
|
||||||
@@ -85,6 +96,9 @@ health_check_timeout_s = 3
|
|||||||
health_check_max_failed = 3
|
health_check_max_failed = 3
|
||||||
# every 10 seconds will do a health check
|
# every 10 seconds will do a health check
|
||||||
health_check_interval_s = 10
|
health_check_interval_s = 10
|
||||||
|
# additional meta info for each proxy
|
||||||
|
meta_var1 = 123
|
||||||
|
meta_var2 = 234
|
||||||
|
|
||||||
[ssh_random]
|
[ssh_random]
|
||||||
type = tcp
|
type = tcp
|
||||||
@@ -198,6 +212,15 @@ plugin_local_addr = 127.0.0.1:80
|
|||||||
plugin_crt_path = ./server.crt
|
plugin_crt_path = ./server.crt
|
||||||
plugin_key_path = ./server.key
|
plugin_key_path = ./server.key
|
||||||
plugin_host_header_rewrite = 127.0.0.1
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
|
|
||||||
|
[plugin_http2https]
|
||||||
|
type = http
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
plugin = http2https
|
||||||
|
plugin_local_addr = 127.0.0.1:443
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
plugin_header_X-From-Where = frp
|
||||||
|
|
||||||
[secret_tcp]
|
[secret_tcp]
|
||||||
# If the type is secret tcp, remote_port is useless
|
# If the type is secret tcp, remote_port is useless
|
||||||
@@ -241,3 +264,10 @@ bind_addr = 127.0.0.1
|
|||||||
bind_port = 9001
|
bind_port = 9001
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
|
||||||
|
[tcpmuxhttpconnect]
|
||||||
|
type = tcpmux
|
||||||
|
multiplexer = httpconnect
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10701
|
||||||
|
custom_domains = tunnel1
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ vhost_https_port = 443
|
|||||||
# response header timeout(seconds) for vhost http server, default is 60s
|
# response header timeout(seconds) for vhost http server, default is 60s
|
||||||
# vhost_http_timeout = 60
|
# vhost_http_timeout = 60
|
||||||
|
|
||||||
|
# TcpMuxHttpConnectPort specifies the port that the server listens for TCP
|
||||||
|
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
|
# requests on one single port. If it's not - it will listen on this value for
|
||||||
|
# HTTP CONNECT requests. By default, this value is 0.
|
||||||
|
# tcpmux_httpconnect_port = 1337
|
||||||
|
|
||||||
# set dashboard_addr and dashboard_port to view dashboard of frps
|
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||||
# dashboard_addr's default value is same with bind_addr
|
# dashboard_addr's default value is same with bind_addr
|
||||||
# dashboard is available only if dashboard_port is set
|
# dashboard is available only if dashboard_port is set
|
||||||
@@ -33,6 +39,9 @@ dashboard_port = 7500
|
|||||||
dashboard_user = admin
|
dashboard_user = admin
|
||||||
dashboard_pwd = admin
|
dashboard_pwd = admin
|
||||||
|
|
||||||
|
# enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
|
||||||
|
enable_prometheus = true
|
||||||
|
|
||||||
# dashboard assets directory(only for debug mode)
|
# dashboard assets directory(only for debug mode)
|
||||||
# assets_dir = ./static
|
# assets_dir = ./static
|
||||||
# console or real logFile path like ./frps.log
|
# console or real logFile path like ./frps.log
|
||||||
@@ -43,9 +52,41 @@ log_level = info
|
|||||||
|
|
||||||
log_max_days = 3
|
log_max_days = 3
|
||||||
|
|
||||||
|
# disable log colors when log_file is console, default is false
|
||||||
|
disable_log_color = false
|
||||||
|
|
||||||
|
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||||
|
detailed_errors_to_client = true
|
||||||
|
|
||||||
|
# AuthenticationMethod specifies what authentication method to use authenticate frpc with frps.
|
||||||
|
# If "token" is specified - token will be read into login message.
|
||||||
|
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
authentication_method = token
|
||||||
|
|
||||||
|
# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||||
|
authenticate_heartbeats = false
|
||||||
|
|
||||||
|
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||||
|
authenticate_new_work_conns = false
|
||||||
|
|
||||||
# auth token
|
# auth token
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
|
# OidcClientId specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||||
|
# By default, this value is "".
|
||||||
|
oidc_client_id =
|
||||||
|
|
||||||
|
# OidcClientSecret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||||
|
# By default, this value is "".
|
||||||
|
oidc_client_secret =
|
||||||
|
|
||||||
|
# OidcAudience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
oidc_audience =
|
||||||
|
|
||||||
|
# OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
oidc_token_endpoint_url =
|
||||||
|
|
||||||
# heartbeat configure, it's not recommended to modify the default value
|
# heartbeat configure, it's not recommended to modify the default value
|
||||||
# the default value of heartbeat_timeout is 90
|
# the default value of heartbeat_timeout is 90
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
@@ -59,6 +100,9 @@ max_pool_count = 5
|
|||||||
# max ports can be used for each client, default value is 0 means no limit
|
# max ports can be used for each client, default value is 0 means no limit
|
||||||
max_ports_per_client = 0
|
max_ports_per_client = 0
|
||||||
|
|
||||||
|
# TlsOnly specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||||
|
tls_only = false
|
||||||
|
|
||||||
# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
|
# 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
|
# when subdomain is test, the host used by routing is test.frps.com
|
||||||
subdomain_host = frps.com
|
subdomain_host = frps.com
|
||||||
@@ -68,3 +112,13 @@ tcp_mux = true
|
|||||||
|
|
||||||
# custom 404 page for HTTP requests
|
# custom 404 page for HTTP requests
|
||||||
# custom_404_page = /path/to/404.html
|
# custom_404_page = /path/to/404.html
|
||||||
|
|
||||||
|
[plugin.user-manager]
|
||||||
|
addr = 127.0.0.1:9000
|
||||||
|
path = /handler
|
||||||
|
ops = Login
|
||||||
|
|
||||||
|
[plugin.port-manager]
|
||||||
|
addr = 127.0.0.1:9001
|
||||||
|
path = /handler
|
||||||
|
ops = NewProxy
|
||||||
|
|||||||
171
doc/server_plugin.md
Normal file
171
doc/server_plugin.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
### Manage Plugin
|
||||||
|
|
||||||
|
frp manage plugin is aim to extend frp's ability without modifing self code.
|
||||||
|
|
||||||
|
It runs as a process and listen on a port to provide RPC interface. Before frps doing some operations, frps will send RPC requests to manage plugin and do operations by it's response.
|
||||||
|
|
||||||
|
### RPC request
|
||||||
|
|
||||||
|
Support HTTP first.
|
||||||
|
|
||||||
|
When manage plugin accept the operation request, it can give three different responses.
|
||||||
|
|
||||||
|
* Reject operation and return the reason.
|
||||||
|
* Allow operation and keep original content.
|
||||||
|
* Allow operation and return modified content.
|
||||||
|
|
||||||
|
### Interface
|
||||||
|
|
||||||
|
HTTP path can be configured for each manage plugin in frps. Assume here is `/handler`.
|
||||||
|
|
||||||
|
Request
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /handler
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"op": "Login",
|
||||||
|
"content": {
|
||||||
|
... // Operation info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request Header
|
||||||
|
X-Frp-Reqid: for tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
Response
|
||||||
|
|
||||||
|
Error if not return 200 http code.
|
||||||
|
|
||||||
|
Reject opeartion
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"reject": true,
|
||||||
|
"reject_reason": "invalid user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow operation and keep original content
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"reject": false,
|
||||||
|
"unchange": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow opeartion and modify content
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"unchange": "false",
|
||||||
|
"content": {
|
||||||
|
... // Replaced content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operation
|
||||||
|
|
||||||
|
Now it supports `Login` and `NewProxy`.
|
||||||
|
|
||||||
|
#### Login
|
||||||
|
|
||||||
|
Client login operation
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"version": <string>,
|
||||||
|
"hostname": <string>,
|
||||||
|
"os": <string>,
|
||||||
|
"arch": <string>,
|
||||||
|
"user": <string>,
|
||||||
|
"timestamp": <int64>,
|
||||||
|
"privilege_key": <string>,
|
||||||
|
"run_id": <string>,
|
||||||
|
"pool_count": <int>,
|
||||||
|
"metas": map<string>string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NewProxy
|
||||||
|
|
||||||
|
Create new proxy
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
},
|
||||||
|
"proxy_name": <string>,
|
||||||
|
"proxy_type": <string>,
|
||||||
|
"use_encryption": <bool>,
|
||||||
|
"use_compression": <bool>,
|
||||||
|
"group": <string>,
|
||||||
|
"group_key": <string>,
|
||||||
|
|
||||||
|
// tcp and udp only
|
||||||
|
"remote_port": <int>,
|
||||||
|
|
||||||
|
// http and https only
|
||||||
|
"custom_domains": []<string>,
|
||||||
|
"subdomain": <string>,
|
||||||
|
"locations": <string>,
|
||||||
|
"http_user": <string>,
|
||||||
|
"http_pwd": <string>,
|
||||||
|
"host_header_rewrite": <string>,
|
||||||
|
"headers": map<string>string,
|
||||||
|
|
||||||
|
"metas": map<string>string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### manage plugin configure
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
bind_port = 7000
|
||||||
|
|
||||||
|
[plugin.user-manager]
|
||||||
|
addr = 127.0.0.1:9000
|
||||||
|
path = /handler
|
||||||
|
ops = Login
|
||||||
|
|
||||||
|
[plugin.port-manager]
|
||||||
|
addr = 127.0.0.1:9001
|
||||||
|
path = /handler
|
||||||
|
ops = NewProxy
|
||||||
|
```
|
||||||
|
|
||||||
|
addr: plugin listen on.
|
||||||
|
path: http request url path.
|
||||||
|
ops: opeartions plugin needs handle.
|
||||||
|
|
||||||
|
### meta data
|
||||||
|
|
||||||
|
Meta data will be sent to manage plugin in each RCP request.
|
||||||
|
|
||||||
|
Meta data start with `meta_`. It can be configured in `common` and each proxy.
|
||||||
|
|
||||||
|
```
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 7000
|
||||||
|
user = fake
|
||||||
|
meta_token = fake
|
||||||
|
meta_version = 1.0.0
|
||||||
|
|
||||||
|
[ssh]
|
||||||
|
type = tcp
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
|
meta_id = 123
|
||||||
|
```
|
||||||
171
doc/server_plugin_zh.md
Normal file
171
doc/server_plugin_zh.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
### 服务端管理插件
|
||||||
|
|
||||||
|
frp 管理插件的作用是在不侵入自身代码的前提下,扩展 frp 服务端的能力。
|
||||||
|
|
||||||
|
frp 管理插件会以单独进程的形式运行,并且监听在一个端口上,对外提供 RPC 接口,响应 frps 的请求。
|
||||||
|
|
||||||
|
frps 在执行某些操作前,会根据配置向管理插件发送 RPC 请求,根据管理插件的响应来执行相应的操作。
|
||||||
|
|
||||||
|
### RPC 请求
|
||||||
|
|
||||||
|
管理插件接收到操作请求后,可以给出三种回应。
|
||||||
|
|
||||||
|
* 拒绝操作,需要返回拒绝操作的原因。
|
||||||
|
* 允许操作,不需要修改操作内容。
|
||||||
|
* 允许操作,对操作请求进行修改后,返回修改后的内容。
|
||||||
|
|
||||||
|
### 接口
|
||||||
|
|
||||||
|
接口路径可以在 frps 配置中为每个插件单独配置,这里以 `/handler` 为例。
|
||||||
|
|
||||||
|
Request
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /handler
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"op": "Login",
|
||||||
|
"content": {
|
||||||
|
... // 具体的操作信息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
请求 Header
|
||||||
|
X-Frp-Reqid: 用于追踪请求
|
||||||
|
```
|
||||||
|
|
||||||
|
Response
|
||||||
|
|
||||||
|
非 200 的返回都认为是请求异常。
|
||||||
|
|
||||||
|
拒绝执行操作
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"reject": true,
|
||||||
|
"reject_reason": "invalid user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
允许且内容不需要变动
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"reject": false,
|
||||||
|
"unchange": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
允许且需要替换操作内容
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"unchange": "false",
|
||||||
|
"content": {
|
||||||
|
... // 替换后的操作信息,格式必须和请求时的一致
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 操作类型
|
||||||
|
|
||||||
|
目前插件支持管理的操作类型有 `Login` 和 `NewProxy`。
|
||||||
|
|
||||||
|
#### Login
|
||||||
|
|
||||||
|
用户登录操作信息
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"version": <string>,
|
||||||
|
"hostname": <string>,
|
||||||
|
"os": <string>,
|
||||||
|
"arch": <string>,
|
||||||
|
"user": <string>,
|
||||||
|
"timestamp": <int64>,
|
||||||
|
"privilege_key": <string>,
|
||||||
|
"run_id": <string>,
|
||||||
|
"pool_count": <int>,
|
||||||
|
"metas": map<string>string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NewProxy
|
||||||
|
|
||||||
|
创建代理的相关信息
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
},
|
||||||
|
"proxy_name": <string>,
|
||||||
|
"proxy_type": <string>,
|
||||||
|
"use_encryption": <bool>,
|
||||||
|
"use_compression": <bool>,
|
||||||
|
"group": <string>,
|
||||||
|
"group_key": <string>,
|
||||||
|
|
||||||
|
// tcp and udp only
|
||||||
|
"remote_port": <int>,
|
||||||
|
|
||||||
|
// http and https only
|
||||||
|
"custom_domains": []<string>,
|
||||||
|
"subdomain": <string>,
|
||||||
|
"locations": <string>,
|
||||||
|
"http_user": <string>,
|
||||||
|
"http_pwd": <string>,
|
||||||
|
"host_header_rewrite": <string>,
|
||||||
|
"headers": map<string>string,
|
||||||
|
|
||||||
|
"metas": map<string>string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### frps 中插件配置
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
bind_port = 7000
|
||||||
|
|
||||||
|
[plugin.user-manager]
|
||||||
|
addr = 127.0.0.1:9000
|
||||||
|
path = /handler
|
||||||
|
ops = Login
|
||||||
|
|
||||||
|
[plugin.port-manager]
|
||||||
|
addr = 127.0.0.1:9001
|
||||||
|
path = /handler
|
||||||
|
ops = NewProxy
|
||||||
|
```
|
||||||
|
|
||||||
|
addr: 插件监听的网络地址。
|
||||||
|
path: 插件监听的 HTTP 请求路径。
|
||||||
|
ops: 插件需要处理的操作列表,多个 op 以英文逗号分隔。
|
||||||
|
|
||||||
|
### 元数据
|
||||||
|
|
||||||
|
为了减少 frps 的代码修改,同时提高管理插件的扩展能力,在 frpc 的配置文件中引入自定义元数据的概念。元数据会在调用 RPC 请求时发送给插件。
|
||||||
|
|
||||||
|
元数据以 `meta_` 开头,可以配置多个,元数据分为两种,一种配置在 `common` 下,一种配置在各个 proxy 中。
|
||||||
|
|
||||||
|
```
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 7000
|
||||||
|
user = fake
|
||||||
|
meta_token = fake
|
||||||
|
meta_version = 1.0.0
|
||||||
|
|
||||||
|
[ssh]
|
||||||
|
type = tcp
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
|
meta_id = 123
|
||||||
|
```
|
||||||
32
g/g.go
32
g/g.go
@@ -1,32 +0,0 @@
|
|||||||
package g
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fatedier/frp/models/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
GlbClientCfg *ClientCfg
|
|
||||||
GlbServerCfg *ServerCfg
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
GlbClientCfg = &ClientCfg{
|
|
||||||
ClientCommonConf: *config.GetDefaultClientConf(),
|
|
||||||
}
|
|
||||||
GlbServerCfg = &ServerCfg{
|
|
||||||
ServerCommonConf: *config.GetDefaultServerConf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientCfg struct {
|
|
||||||
config.ClientCommonConf
|
|
||||||
|
|
||||||
CfgFile string
|
|
||||||
ServerUdpPort int // this is configured by login response from frps
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerCfg struct {
|
|
||||||
config.ServerCommonConf
|
|
||||||
|
|
||||||
CfgFile string
|
|
||||||
}
|
|
||||||
9
go.mod
9
go.mod
@@ -4,6 +4,7 @@ go 1.12
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||||
@@ -16,17 +17,21 @@ require (
|
|||||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||||
github.com/pkg/errors v0.8.0 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.4.1
|
||||||
github.com/rakyll/statik v0.1.1
|
github.com/rakyll/statik v0.1.1
|
||||||
github.com/rodaine/table v1.0.0
|
github.com/rodaine/table v1.0.0
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v0.0.3
|
||||||
github.com/spf13/pflag v1.0.1 // indirect
|
github.com/spf13/pflag v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.4.0
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/text v0.3.2 // indirect
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
114
go.sum
114
go.sum
@@ -1,61 +1,141 @@
|
|||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
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 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFYyOi8zA+Y8=
|
||||||
|
github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
|
||||||
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||||
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||||
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
|
||||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
|
||||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
|
||||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
151
models/auth/auth.go
Normal file
151
models/auth/auth.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
|
||||||
|
"github.com/vaughan0/go-ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
type baseConfig struct {
|
||||||
|
// AuthenticationMethod specifies what authentication method to use to
|
||||||
|
// authenticate frpc with frps. If "token" is specified - token will be
|
||||||
|
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||||
|
// token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
AuthenticationMethod string `json:"authentication_method"`
|
||||||
|
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||||
|
// heartbeats sent to frps. By default, this value is false.
|
||||||
|
AuthenticateHeartBeats bool `json:"authenticate_heartbeats"`
|
||||||
|
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||||
|
// new work connections sent to frps. By default, this value is false.
|
||||||
|
AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultBaseConf() baseConfig {
|
||||||
|
return baseConfig{
|
||||||
|
AuthenticationMethod: "token",
|
||||||
|
AuthenticateHeartBeats: false,
|
||||||
|
AuthenticateNewWorkConns: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalBaseConfFromIni(conf ini.File) baseConfig {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg := getDefaultBaseConf()
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "authentication_method"); ok {
|
||||||
|
cfg.AuthenticationMethod = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "authenticate_heartbeats"); ok && tmpStr == "true" {
|
||||||
|
cfg.AuthenticateHeartBeats = true
|
||||||
|
} else {
|
||||||
|
cfg.AuthenticateHeartBeats = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" {
|
||||||
|
cfg.AuthenticateNewWorkConns = true
|
||||||
|
} else {
|
||||||
|
cfg.AuthenticateNewWorkConns = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthClientConfig struct {
|
||||||
|
baseConfig
|
||||||
|
oidcClientConfig
|
||||||
|
tokenConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultAuthClientConf() AuthClientConfig {
|
||||||
|
return AuthClientConfig{
|
||||||
|
baseConfig: getDefaultBaseConf(),
|
||||||
|
oidcClientConfig: getDefaultOidcClientConf(),
|
||||||
|
tokenConfig: getDefaultTokenConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalAuthClientConfFromIni(conf ini.File) (cfg AuthClientConfig) {
|
||||||
|
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
|
||||||
|
cfg.oidcClientConfig = unmarshalOidcClientConfFromIni(conf)
|
||||||
|
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthServerConfig struct {
|
||||||
|
baseConfig
|
||||||
|
oidcServerConfig
|
||||||
|
tokenConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultAuthServerConf() AuthServerConfig {
|
||||||
|
return AuthServerConfig{
|
||||||
|
baseConfig: getDefaultBaseConf(),
|
||||||
|
oidcServerConfig: getDefaultOidcServerConf(),
|
||||||
|
tokenConfig: getDefaultTokenConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalAuthServerConfFromIni(conf ini.File) (cfg AuthServerConfig) {
|
||||||
|
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
|
||||||
|
cfg.oidcServerConfig = unmarshalOidcServerConfFromIni(conf)
|
||||||
|
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type Setter interface {
|
||||||
|
SetLogin(*msg.Login) error
|
||||||
|
SetPing(*msg.Ping) error
|
||||||
|
SetNewWorkConn(*msg.NewWorkConn) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthSetter(cfg AuthClientConfig) (authProvider Setter) {
|
||||||
|
switch cfg.AuthenticationMethod {
|
||||||
|
case consts.TokenAuthMethod:
|
||||||
|
authProvider = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
|
||||||
|
case consts.OidcAuthMethod:
|
||||||
|
authProvider = NewOidcAuthSetter(cfg.baseConfig, cfg.oidcClientConfig)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
|
||||||
|
}
|
||||||
|
|
||||||
|
return authProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
type Verifier interface {
|
||||||
|
VerifyLogin(*msg.Login) error
|
||||||
|
VerifyPing(*msg.Ping) error
|
||||||
|
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthVerifier(cfg AuthServerConfig) (authVerifier Verifier) {
|
||||||
|
switch cfg.AuthenticationMethod {
|
||||||
|
case consts.TokenAuthMethod:
|
||||||
|
authVerifier = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
|
||||||
|
case consts.OidcAuthMethod:
|
||||||
|
authVerifier = NewOidcAuthVerifier(cfg.baseConfig, cfg.oidcServerConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return authVerifier
|
||||||
|
}
|
||||||
255
models/auth/oidc.go
Normal file
255
models/auth/oidc.go
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc"
|
||||||
|
"github.com/vaughan0/go-ini"
|
||||||
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
type oidcClientConfig struct {
|
||||||
|
// OidcClientId specifies the client ID to use to get a token in OIDC
|
||||||
|
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||||
|
// is "".
|
||||||
|
OidcClientId string `json:"oidc_client_id"`
|
||||||
|
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||||
|
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||||
|
// is "".
|
||||||
|
OidcClientSecret string `json:"oidc_client_secret"`
|
||||||
|
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||||
|
//if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
OidcAudience string `json:"oidc_audience"`
|
||||||
|
// OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||||
|
// By default, this value is "".
|
||||||
|
OidcTokenEndpointUrl string `json:"oidc_token_endpoint_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultOidcClientConf() oidcClientConfig {
|
||||||
|
return oidcClientConfig{
|
||||||
|
OidcClientId: "",
|
||||||
|
OidcClientSecret: "",
|
||||||
|
OidcAudience: "",
|
||||||
|
OidcTokenEndpointUrl: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg := getDefaultOidcClientConf()
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok {
|
||||||
|
cfg.OidcClientId = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_client_secret"); ok {
|
||||||
|
cfg.OidcClientSecret = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_audience"); ok {
|
||||||
|
cfg.OidcAudience = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_token_endpoint_url"); ok {
|
||||||
|
cfg.OidcTokenEndpointUrl = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type oidcServerConfig struct {
|
||||||
|
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||||
|
// will be used to load public keys to verify signature and will be compared
|
||||||
|
// with the issuer claim in the OIDC token. It will be used if
|
||||||
|
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
OidcIssuer string `json:"oidc_issuer"`
|
||||||
|
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||||
|
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||||
|
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||||
|
// value is "".
|
||||||
|
OidcAudience string `json:"oidc_audience"`
|
||||||
|
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||||
|
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||||
|
// value is false.
|
||||||
|
OidcSkipExpiryCheck bool `json:"oidc_skip_expiry_check"`
|
||||||
|
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||||
|
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||||
|
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||||
|
OidcSkipIssuerCheck bool `json:"oidc_skip_issuer_check"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultOidcServerConf() oidcServerConfig {
|
||||||
|
return oidcServerConfig{
|
||||||
|
OidcIssuer: "",
|
||||||
|
OidcAudience: "",
|
||||||
|
OidcSkipExpiryCheck: false,
|
||||||
|
OidcSkipIssuerCheck: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalOidcServerConfFromIni(conf ini.File) oidcServerConfig {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg := getDefaultOidcServerConf()
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok {
|
||||||
|
cfg.OidcIssuer = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_audience"); ok {
|
||||||
|
cfg.OidcAudience = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_skip_expiry_check"); ok && tmpStr == "true" {
|
||||||
|
cfg.OidcSkipExpiryCheck = true
|
||||||
|
} else {
|
||||||
|
cfg.OidcSkipExpiryCheck = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "oidc_skip_issuer_check"); ok && tmpStr == "true" {
|
||||||
|
cfg.OidcSkipIssuerCheck = true
|
||||||
|
} else {
|
||||||
|
cfg.OidcSkipIssuerCheck = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcAuthProvider struct {
|
||||||
|
baseConfig
|
||||||
|
|
||||||
|
tokenGenerator *clientcredentials.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvider {
|
||||||
|
tokenGenerator := &clientcredentials.Config{
|
||||||
|
ClientID: cfg.OidcClientId,
|
||||||
|
ClientSecret: cfg.OidcClientSecret,
|
||||||
|
Scopes: []string{cfg.OidcAudience},
|
||||||
|
TokenURL: cfg.OidcTokenEndpointUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OidcAuthProvider{
|
||||||
|
baseConfig: baseCfg,
|
||||||
|
tokenGenerator: tokenGenerator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthProvider) generateAccessToken() (accessToken string, err error) {
|
||||||
|
tokenObj, err := auth.tokenGenerator.Token(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("couldn't generate OIDC token for login: %v", err)
|
||||||
|
}
|
||||||
|
return tokenObj.AccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
|
||||||
|
loginMsg.PrivilegeKey, err = auth.generateAccessToken()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
||||||
|
if !auth.AuthenticateHeartBeats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pingMsg.PrivilegeKey, err = auth.generateAccessToken()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||||
|
if !auth.AuthenticateNewWorkConns {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newWorkConnMsg.PrivilegeKey, err = auth.generateAccessToken()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcAuthConsumer struct {
|
||||||
|
baseConfig
|
||||||
|
|
||||||
|
verifier *oidc.IDTokenVerifier
|
||||||
|
subjectFromLogin string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthConsumer {
|
||||||
|
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
verifierConf := oidc.Config{
|
||||||
|
ClientID: cfg.OidcAudience,
|
||||||
|
SkipClientIDCheck: cfg.OidcAudience == "",
|
||||||
|
SkipExpiryCheck: cfg.OidcSkipExpiryCheck,
|
||||||
|
SkipIssuerCheck: cfg.OidcSkipIssuerCheck,
|
||||||
|
}
|
||||||
|
return &OidcAuthConsumer{
|
||||||
|
baseConfig: baseCfg,
|
||||||
|
verifier: provider.Verifier(&verifierConf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
|
||||||
|
token, err := auth.verifier.Verify(context.Background(), loginMsg.PrivilegeKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid OIDC token in login: %v", err)
|
||||||
|
}
|
||||||
|
auth.subjectFromLogin = token.Subject
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) {
|
||||||
|
token, err := auth.verifier.Verify(context.Background(), privilegeKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid OIDC token in ping: %v", err)
|
||||||
|
}
|
||||||
|
if token.Subject != auth.subjectFromLogin {
|
||||||
|
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
||||||
|
"original subject: %s, "+
|
||||||
|
"new subject: %s",
|
||||||
|
auth.subjectFromLogin, token.Subject)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
||||||
|
if !auth.AuthenticateHeartBeats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.verifyPostLoginToken(pingMsg.PrivilegeKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||||
|
if !auth.AuthenticateNewWorkConns {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.verifyPostLoginToken(newWorkConnMsg.PrivilegeKey)
|
||||||
|
}
|
||||||
120
models/auth/token.go
Normal file
120
models/auth/token.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
|
||||||
|
"github.com/vaughan0/go-ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenConfig struct {
|
||||||
|
// Token specifies the authorization token used to create keys to be sent
|
||||||
|
// to the server. The server must have a matching token for authorization
|
||||||
|
// to succeed. By default, this value is "".
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTokenConf() tokenConfig {
|
||||||
|
return tokenConfig{
|
||||||
|
Token: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalTokenConfFromIni(conf ini.File) tokenConfig {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg := getDefaultTokenConf()
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "token"); ok {
|
||||||
|
cfg.Token = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenAuthSetterVerifier struct {
|
||||||
|
baseConfig
|
||||||
|
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenAuth(baseCfg baseConfig, cfg tokenConfig) *TokenAuthSetterVerifier {
|
||||||
|
return &TokenAuthSetterVerifier{
|
||||||
|
baseConfig: baseCfg,
|
||||||
|
token: cfg.Token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) {
|
||||||
|
loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||||
|
if !auth.AuthenticateHeartBeats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pingMsg.Timestamp = time.Now().Unix()
|
||||||
|
pingMsg.PrivilegeKey = util.GetAuthKey(auth.token, pingMsg.Timestamp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||||
|
if !auth.AuthenticateHeartBeats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newWorkConnMsg.Timestamp = time.Now().Unix()
|
||||||
|
newWorkConnMsg.PrivilegeKey = util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error {
|
||||||
|
if util.GetAuthKey(auth.token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
||||||
|
return fmt.Errorf("token in login doesn't match token from configuration")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *TokenAuthSetterVerifier) VerifyPing(pingMsg *msg.Ping) error {
|
||||||
|
if !auth.AuthenticateHeartBeats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if util.GetAuthKey(auth.token, pingMsg.Timestamp) != pingMsg.PrivilegeKey {
|
||||||
|
return fmt.Errorf("token in heartbeat doesn't match token from configuration")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||||
|
if !auth.AuthenticateNewWorkConns {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) != newWorkConnMsg.PrivilegeKey {
|
||||||
|
return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -21,36 +21,106 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ini "github.com/vaughan0/go-ini"
|
ini "github.com/vaughan0/go-ini"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// client common config
|
// ClientCommonConf contains information for a client service. It is
|
||||||
|
// recommended to use GetDefaultClientConf instead of creating this object
|
||||||
|
// directly, so that all unspecified fields have reasonable default values.
|
||||||
type ClientCommonConf struct {
|
type ClientCommonConf struct {
|
||||||
ServerAddr string `json:"server_addr"`
|
auth.AuthClientConfig
|
||||||
ServerPort int `json:"server_port"`
|
// ServerAddr specifies the address of the server to connect to. By
|
||||||
HttpProxy string `json:"http_proxy"`
|
// default, this value is "0.0.0.0".
|
||||||
LogFile string `json:"log_file"`
|
ServerAddr string `json:"server_addr"`
|
||||||
LogWay string `json:"log_way"`
|
// ServerPort specifies the port to connect to the server on. By default,
|
||||||
LogLevel string `json:"log_level"`
|
// this value is 7000.
|
||||||
LogMaxDays int64 `json:"log_max_days"`
|
ServerPort int `json:"server_port"`
|
||||||
Token string `json:"token"`
|
// HttpProxy specifies a proxy address to connect to the server through. If
|
||||||
AdminAddr string `json:"admin_addr"`
|
// this value is "", the server will be connected to directly. By default,
|
||||||
AdminPort int `json:"admin_port"`
|
// this value is read from the "http_proxy" environment variable.
|
||||||
AdminUser string `json:"admin_user"`
|
HttpProxy string `json:"http_proxy"`
|
||||||
AdminPwd string `json:"admin_pwd"`
|
// LogFile specifies a file where logs will be written to. This value will
|
||||||
PoolCount int `json:"pool_count"`
|
// only be used if LogWay is set appropriately. By default, this value is
|
||||||
TcpMux bool `json:"tcp_mux"`
|
// "console".
|
||||||
User string `json:"user"`
|
LogFile string `json:"log_file"`
|
||||||
DnsServer string `json:"dns_server"`
|
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||||
LoginFailExit bool `json:"login_fail_exit"`
|
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||||
Start map[string]struct{} `json:"start"`
|
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||||
Protocol string `json:"protocol"`
|
// is "console".
|
||||||
TLSEnable bool `json:"tls_enable"`
|
LogWay string `json:"log_way"`
|
||||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||||
|
LogLevel string `json:"log_level"`
|
||||||
|
// LogMaxDays specifies the maximum number of days to store log information
|
||||||
|
// before deletion. This is only used if LogWay == "file". By default, this
|
||||||
|
// value is 0.
|
||||||
|
LogMaxDays int64 `json:"log_max_days"`
|
||||||
|
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||||
|
// true. By default, this value is false.
|
||||||
|
DisableLogColor bool `json:"disable_log_color"`
|
||||||
|
// AdminAddr specifies the address that the admin server binds to. By
|
||||||
|
// default, this value is "127.0.0.1".
|
||||||
|
AdminAddr string `json:"admin_addr"`
|
||||||
|
// AdminPort specifies the port for the admin server to listen on. If this
|
||||||
|
// value is 0, the admin server will not be started. By default, this value
|
||||||
|
// is 0.
|
||||||
|
AdminPort int `json:"admin_port"`
|
||||||
|
// AdminUser specifies the username that the admin server will use for
|
||||||
|
// login. By default, this value is "admin".
|
||||||
|
AdminUser string `json:"admin_user"`
|
||||||
|
// AdminPwd specifies the password that the admin server will use for
|
||||||
|
// login. By default, this value is "admin".
|
||||||
|
AdminPwd string `json:"admin_pwd"`
|
||||||
|
// AssetsDir specifies the local directory that the admin server will load
|
||||||
|
// resources from. If this value is "", assets will be loaded from the
|
||||||
|
// bundled executable using statik. By default, this value is "".
|
||||||
|
AssetsDir string `json:"assets_dir"`
|
||||||
|
// PoolCount specifies the number of connections the client will make to
|
||||||
|
// the server in advance. By default, this value is 0.
|
||||||
|
PoolCount int `json:"pool_count"`
|
||||||
|
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
|
||||||
|
// from a client to share a single TCP connection. If this value is true,
|
||||||
|
// the server must have TCP multiplexing enabled as well. By default, this
|
||||||
|
// value is true.
|
||||||
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
// User specifies a prefix for proxy names to distinguish them from other
|
||||||
|
// clients. If this value is not "", proxy names will automatically be
|
||||||
|
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||||
|
User string `json:"user"`
|
||||||
|
// DnsServer specifies a DNS server address for FRPC to use. If this value
|
||||||
|
// is "", the default DNS will be used. By default, this value is "".
|
||||||
|
DnsServer string `json:"dns_server"`
|
||||||
|
// LoginFailExit controls whether or not the client should exit after a
|
||||||
|
// failed login attempt. If false, the client will retry until a login
|
||||||
|
// attempt succeeds. By default, this value is true.
|
||||||
|
LoginFailExit bool `json:"login_fail_exit"`
|
||||||
|
// Start specifies a set of enabled proxies by name. If this set is empty,
|
||||||
|
// all supplied proxies are enabled. By default, this value is an empty
|
||||||
|
// set.
|
||||||
|
Start map[string]struct{} `json:"start"`
|
||||||
|
// Protocol specifies the protocol to use when interacting with the server.
|
||||||
|
// Valid values are "tcp", "kcp", and "websocket". By default, this value
|
||||||
|
// is "tcp".
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||||
|
// with the server.
|
||||||
|
TLSEnable bool `json:"tls_enable"`
|
||||||
|
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||||
|
// server, in seconds. It is not recommended to change this value. By
|
||||||
|
// default, this value is 30.
|
||||||
|
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||||
|
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||||
|
// before the connection is terminated, in seconds. It is not recommended
|
||||||
|
// to change this value. By default, this value is 90.
|
||||||
|
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||||
|
// Client meta info
|
||||||
|
Metas map[string]string `json:"metas"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultClientConf() *ClientCommonConf {
|
// GetDefaultClientConf returns a client configuration with default values.
|
||||||
return &ClientCommonConf{
|
func GetDefaultClientConf() ClientCommonConf {
|
||||||
|
return ClientCommonConf{
|
||||||
ServerAddr: "0.0.0.0",
|
ServerAddr: "0.0.0.0",
|
||||||
ServerPort: 7000,
|
ServerPort: 7000,
|
||||||
HttpProxy: os.Getenv("http_proxy"),
|
HttpProxy: os.Getenv("http_proxy"),
|
||||||
@@ -58,11 +128,12 @@ func GetDefaultClientConf() *ClientCommonConf {
|
|||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
Token: "",
|
DisableLogColor: false,
|
||||||
AdminAddr: "127.0.0.1",
|
AdminAddr: "127.0.0.1",
|
||||||
AdminPort: 0,
|
AdminPort: 0,
|
||||||
AdminUser: "",
|
AdminUser: "",
|
||||||
AdminPwd: "",
|
AdminPwd: "",
|
||||||
|
AssetsDir: "",
|
||||||
PoolCount: 1,
|
PoolCount: 1,
|
||||||
TcpMux: true,
|
TcpMux: true,
|
||||||
User: "",
|
User: "",
|
||||||
@@ -73,21 +144,20 @@ func GetDefaultClientConf() *ClientCommonConf {
|
|||||||
TLSEnable: false,
|
TLSEnable: false,
|
||||||
HeartBeatInterval: 30,
|
HeartBeatInterval: 30,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
|
Metas: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
|
func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
|
||||||
cfg = defaultCfg
|
cfg = GetDefaultClientConf()
|
||||||
if cfg == nil {
|
|
||||||
cfg = GetDefaultClientConf()
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := ini.Load(strings.NewReader(content))
|
conf, err := ini.Load(strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.AuthClientConfig = auth.UnmarshalAuthClientConfFromIni(conf)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tmpStr string
|
tmpStr string
|
||||||
ok bool
|
ok bool
|
||||||
@@ -106,6 +176,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.ServerPort = int(v)
|
cfg.ServerPort = int(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||||
|
cfg.DisableLogColor = true
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||||
cfg.HttpProxy = tmpStr
|
cfg.HttpProxy = tmpStr
|
||||||
}
|
}
|
||||||
@@ -129,10 +203,6 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "token"); ok {
|
|
||||||
cfg.Token = tmpStr
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
||||||
cfg.AdminAddr = tmpStr
|
cfg.AdminAddr = tmpStr
|
||||||
}
|
}
|
||||||
@@ -154,6 +224,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.AdminPwd = tmpStr
|
cfg.AdminPwd = tmpStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||||
|
cfg.AssetsDir = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
||||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
cfg.PoolCount = int(v)
|
cfg.PoolCount = int(v)
|
||||||
@@ -219,6 +293,11 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.HeartBeatInterval = v
|
cfg.HeartBeatInterval = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for k, v := range conf.Section("common") {
|
||||||
|
if strings.HasPrefix(k, "meta_") {
|
||||||
|
cfg.Metas[strings.TrimPrefix(k, "meta_")] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
proxyConfTypeMap = make(map[string]reflect.Type)
|
proxyConfTypeMap = make(map[string]reflect.Type)
|
||||||
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
|
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
|
||||||
|
proxyConfTypeMap[consts.TcpMuxProxy] = reflect.TypeOf(TcpMuxProxyConf{})
|
||||||
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
||||||
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
||||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||||
@@ -58,11 +59,11 @@ type ProxyConf interface {
|
|||||||
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
||||||
MarshalToMsg(pMsg *msg.NewProxy)
|
MarshalToMsg(pMsg *msg.NewProxy)
|
||||||
CheckForCli() error
|
CheckForCli() error
|
||||||
CheckForSvr() error
|
CheckForSvr(serverCfg ServerCommonConf) error
|
||||||
Compare(conf ProxyConf) bool
|
Compare(conf ProxyConf) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
|
||||||
if pMsg.ProxyType == "" {
|
if pMsg.ProxyType == "" {
|
||||||
pMsg.ProxyType = consts.TcpProxy
|
pMsg.ProxyType = consts.TcpProxy
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.UnmarshalFromMsg(pMsg)
|
cfg.UnmarshalFromMsg(pMsg)
|
||||||
err = cfg.CheckForSvr()
|
err = cfg.CheckForSvr(serverCfg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,18 +98,42 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseProxy info
|
// BaseProxyConf provides configuration info that is common to all proxy types.
|
||||||
type BaseProxyConf struct {
|
type BaseProxyConf struct {
|
||||||
|
// ProxyName is the name of this proxy.
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
|
// ProxyType specifies the type of this proxy. Valid values include "tcp",
|
||||||
|
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||||
|
// "tcp".
|
||||||
ProxyType string `json:"proxy_type"`
|
ProxyType string `json:"proxy_type"`
|
||||||
|
|
||||||
UseEncryption bool `json:"use_encryption"`
|
// UseEncryption controls whether or not communication with the server will
|
||||||
UseCompression bool `json:"use_compression"`
|
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||||
Group string `json:"group"`
|
// and client configuration. By default, this value is false.
|
||||||
GroupKey string `json:"group_key"`
|
UseEncryption bool `json:"use_encryption"`
|
||||||
|
// UseCompression controls whether or not communication with the server
|
||||||
|
// will be compressed. By default, this value is false.
|
||||||
|
UseCompression bool `json:"use_compression"`
|
||||||
|
// Group specifies which group the proxy is a part of. The server will use
|
||||||
|
// this information to load balance proxies in the same group. If the value
|
||||||
|
// is "", this proxy will not be in a group. By default, this value is "".
|
||||||
|
Group string `json:"group"`
|
||||||
|
// GroupKey specifies a group key, which should be the same among proxies
|
||||||
|
// of the same group. By default, this value is "".
|
||||||
|
GroupKey string `json:"group_key"`
|
||||||
|
|
||||||
// only used for client
|
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||||
|
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||||
|
// version will be automatically selected. By default, this value is "".
|
||||||
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||||
|
|
||||||
|
// BandwidthLimit limit the proxy bandwidth
|
||||||
|
// 0 means no limit
|
||||||
|
BandwidthLimit BandwidthQuantity `json:"bandwidth_limit"`
|
||||||
|
|
||||||
|
// meta info for each proxy
|
||||||
|
Metas map[string]string `json:"metas"`
|
||||||
|
|
||||||
LocalSvrConf
|
LocalSvrConf
|
||||||
HealthCheckConf
|
HealthCheckConf
|
||||||
}
|
}
|
||||||
@@ -124,7 +149,9 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
|||||||
cfg.UseCompression != cmp.UseCompression ||
|
cfg.UseCompression != cmp.UseCompression ||
|
||||||
cfg.Group != cmp.Group ||
|
cfg.Group != cmp.Group ||
|
||||||
cfg.GroupKey != cmp.GroupKey ||
|
cfg.GroupKey != cmp.GroupKey ||
|
||||||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
|
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion ||
|
||||||
|
!cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) ||
|
||||||
|
!reflect.DeepEqual(cfg.Metas, cmp.Metas) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||||
@@ -143,12 +170,14 @@ func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
|||||||
cfg.UseCompression = pMsg.UseCompression
|
cfg.UseCompression = pMsg.UseCompression
|
||||||
cfg.Group = pMsg.Group
|
cfg.Group = pMsg.Group
|
||||||
cfg.GroupKey = pMsg.GroupKey
|
cfg.GroupKey = pMsg.GroupKey
|
||||||
|
cfg.Metas = pMsg.Metas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
|
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
|
||||||
var (
|
var (
|
||||||
tmpStr string
|
tmpStr string
|
||||||
ok bool
|
ok bool
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
cfg.ProxyName = prefix + name
|
cfg.ProxyName = prefix + name
|
||||||
cfg.ProxyType = section["type"]
|
cfg.ProxyType = section["type"]
|
||||||
@@ -167,11 +196,15 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
|||||||
cfg.GroupKey = section["group_key"]
|
cfg.GroupKey = section["group_key"]
|
||||||
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
||||||
|
|
||||||
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
if cfg.BandwidthLimit, err = NewBandwidthQuantity(section["bandwidth_limit"]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +218,13 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
|||||||
}
|
}
|
||||||
cfg.HealthCheckUrl = s + cfg.HealthCheckUrl
|
cfg.HealthCheckUrl = s + cfg.HealthCheckUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.Metas = make(map[string]string)
|
||||||
|
for k, v := range section {
|
||||||
|
if strings.HasPrefix(k, "meta_") {
|
||||||
|
cfg.Metas[strings.TrimPrefix(k, "meta_")] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +235,7 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.UseCompression = cfg.UseCompression
|
pMsg.UseCompression = cfg.UseCompression
|
||||||
pMsg.Group = cfg.Group
|
pMsg.Group = cfg.Group
|
||||||
pMsg.GroupKey = cfg.GroupKey
|
pMsg.GroupKey = cfg.GroupKey
|
||||||
|
pMsg.Metas = cfg.Metas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||||
@@ -308,21 +349,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DomainConf) checkForSvr() (err error) {
|
func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
if err = cfg.check(); err != nil {
|
if err = cfg.check(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range cfg.CustomDomains {
|
for _, domain := range cfg.CustomDomains {
|
||||||
if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||||
if strings.Contains(domain, subDomainHost) {
|
if strings.Contains(domain, serverCfg.SubDomainHost) {
|
||||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
|
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.SubDomain != "" {
|
if cfg.SubDomain != "" {
|
||||||
if subDomainHost == "" {
|
if serverCfg.SubDomainHost == "" {
|
||||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
||||||
}
|
}
|
||||||
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
||||||
@@ -332,12 +373,20 @@ func (cfg *DomainConf) checkForSvr() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local service info
|
// LocalSvrConf configures what location the client will proxy to, or what
|
||||||
|
// plugin will be used.
|
||||||
type LocalSvrConf struct {
|
type LocalSvrConf struct {
|
||||||
LocalIp string `json:"local_ip"`
|
// LocalIp specifies the IP address or host name to proxy to.
|
||||||
LocalPort int `json:"local_port"`
|
LocalIp string `json:"local_ip"`
|
||||||
|
// LocalPort specifies the port to proxy to.
|
||||||
|
LocalPort int `json:"local_port"`
|
||||||
|
|
||||||
Plugin string `json:"plugin"`
|
// Plugin specifies what plugin should be used for proxying. If this value
|
||||||
|
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||||
|
// this value is "".
|
||||||
|
Plugin string `json:"plugin"`
|
||||||
|
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||||
|
// being used. By default, this value is an empty map.
|
||||||
PluginParams map[string]string `json:"plugin_params"`
|
PluginParams map[string]string `json:"plugin_params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,15 +448,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health check info
|
// HealthCheckConf configures health checking. This can be useful for load
|
||||||
|
// balancing purposes to detect and remove proxies to failing services.
|
||||||
type HealthCheckConf struct {
|
type HealthCheckConf struct {
|
||||||
HealthCheckType string `json:"health_check_type"` // tcp | http
|
// HealthCheckType specifies what protocol to use for health checking.
|
||||||
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||||
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
// checking will not be performed. By default, this value is "".
|
||||||
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
//
|
||||||
HealthCheckUrl string `json:"health_check_url"`
|
// If the type is "tcp", a connection will be attempted to the target
|
||||||
|
// server. If a connection cannot be established, the health check fails.
|
||||||
// local_ip + local_port
|
//
|
||||||
|
// If the type is "http", a GET request will be made to the endpoint
|
||||||
|
// specified by HealthCheckUrl. If the response is not a 200, the health
|
||||||
|
// check fails.
|
||||||
|
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||||
|
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
|
||||||
|
// check attempt to connect. If the timeout is reached, this counts as a
|
||||||
|
// health check failure. By default, this value is 3.
|
||||||
|
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||||
|
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||||
|
// proxy is stopped. By default, this value is 1.
|
||||||
|
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||||
|
// HealthCheckIntervalS specifies the time in seconds between health
|
||||||
|
// checks. By default, this value is 10.
|
||||||
|
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||||
|
// HealthCheckUrl specifies the address to send health checks to if the
|
||||||
|
// health check type is "http".
|
||||||
|
HealthCheckUrl string `json:"health_check_url"`
|
||||||
|
// HealthCheckAddr specifies the address to connect to if the health check
|
||||||
|
// type is "tcp".
|
||||||
HealthCheckAddr string `json:"-"`
|
HealthCheckAddr string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +573,85 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||||
|
|
||||||
|
// TCP Multiplexer
|
||||||
|
type TcpMuxProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
DomainConf
|
||||||
|
|
||||||
|
Multiplexer string `json:"multiplexer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpMuxProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*TcpMuxProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||||
|
cfg.Multiplexer != cmpConf.Multiplexer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.Multiplexer = pMsg.Multiplexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpMuxProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Multiplexer = section["multiplexer"]
|
||||||
|
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
|
||||||
|
return fmt.Errorf("parse conf error: proxy [%s] incorrect multiplexer [%s]", name, cfg.Multiplexer)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||||
|
pMsg.Multiplexer = cfg.Multiplexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpMuxProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.checkForCli(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
|
||||||
|
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
|
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
|
||||||
|
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Multiplexer == consts.HttpConnectTcpMultiplexer && serverCfg.TcpMuxHttpConnectPort == 0 {
|
||||||
|
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||||
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
type UdpProxyConf struct {
|
type UdpProxyConf struct {
|
||||||
@@ -552,7 +699,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
|
func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
type HttpProxyConf struct {
|
type HttpProxyConf struct {
|
||||||
@@ -657,11 +804,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
if vhostHttpPort == 0 {
|
if serverCfg.VhostHttpPort == 0 {
|
||||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||||
}
|
}
|
||||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -717,11 +864,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
|
func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
if vhostHttpsPort == 0 {
|
if serverCfg.VhostHttpsPort == 0 {
|
||||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||||
}
|
}
|
||||||
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -790,7 +937,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
|
func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,7 +1004,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
|
func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,106 +21,185 @@ import (
|
|||||||
|
|
||||||
ini "github.com/vaughan0/go-ini"
|
ini "github.com/vaughan0/go-ini"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/auth"
|
||||||
|
plugin "github.com/fatedier/frp/models/plugin/server"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// ServerCommonConf contains information for a server service. It is
|
||||||
// server global configure used for generate proxy conf used in frps
|
// recommended to use GetDefaultServerConf instead of creating this object
|
||||||
proxyBindAddr string
|
// directly, so that all unspecified fields have reasonable default values.
|
||||||
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 {
|
type ServerCommonConf struct {
|
||||||
BindAddr string `json:"bind_addr"`
|
auth.AuthServerConfig
|
||||||
BindPort int `json:"bind_port"`
|
// BindAddr specifies the address that the server binds to. By default,
|
||||||
BindUdpPort int `json:"bind_udp_port"`
|
// this value is "0.0.0.0".
|
||||||
KcpBindPort int `json:"kcp_bind_port"`
|
BindAddr string `json:"bind_addr"`
|
||||||
|
// BindPort specifies the port that the server listens on. By default, this
|
||||||
|
// value is 7000.
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
// BindUdpPort specifies the UDP port that the server listens on. If this
|
||||||
|
// value is 0, the server will not listen for UDP connections. By default,
|
||||||
|
// this value is 0
|
||||||
|
BindUdpPort int `json:"bind_udp_port"`
|
||||||
|
// BindKcpPort specifies the KCP port that the server listens on. If this
|
||||||
|
// value is 0, the server will not listen for KCP connections. By default,
|
||||||
|
// this value is 0.
|
||||||
|
KcpBindPort int `json:"kcp_bind_port"`
|
||||||
|
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||||
|
// may be the same as BindAddr. By default, this value is "0.0.0.0".
|
||||||
ProxyBindAddr string `json:"proxy_bind_addr"`
|
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||||
|
// VhostHttpPort specifies the port that the server listens for HTTP Vhost
|
||||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
// requests. If this value is 0, the server will not listen for HTTP
|
||||||
|
// requests. By default, this value is 0.
|
||||||
VhostHttpPort int `json:"vhost_http_port"`
|
VhostHttpPort int `json:"vhost_http_port"`
|
||||||
|
// VhostHttpsPort specifies the port that the server listens for HTTPS
|
||||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||||
|
// requests. By default, this value is 0.
|
||||||
VhostHttpsPort int `json:"vhost_https_port"`
|
VhostHttpsPort int `json:"vhost_https_port"`
|
||||||
|
// TcpMuxHttpConnectPort specifies the port that the server listens for TCP
|
||||||
|
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
|
// requests on one single port. If it's not - it will listen on this value for
|
||||||
|
// HTTP CONNECT requests. By default, this value is 0.
|
||||||
|
TcpMuxHttpConnectPort int `json:"tcpmux_httpconnect_port"`
|
||||||
|
// VhostHttpTimeout specifies the response header timeout for the Vhost
|
||||||
|
// HTTP server, in seconds. By default, this value is 60.
|
||||||
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||||
|
// DashboardAddr specifies the address that the dashboard binds to. By
|
||||||
|
// default, this value is "0.0.0.0".
|
||||||
DashboardAddr string `json:"dashboard_addr"`
|
DashboardAddr string `json:"dashboard_addr"`
|
||||||
|
// DashboardPort specifies the port that the dashboard listens on. If this
|
||||||
// if DashboardPort equals 0, dashboard is not available
|
// value is 0, the dashboard will not be started. By default, this value is
|
||||||
DashboardPort int `json:"dashboard_port"`
|
// 0.
|
||||||
|
DashboardPort int `json:"dashboard_port"`
|
||||||
|
// DashboardUser specifies the username that the dashboard will use for
|
||||||
|
// login. By default, this value is "admin".
|
||||||
DashboardUser string `json:"dashboard_user"`
|
DashboardUser string `json:"dashboard_user"`
|
||||||
DashboardPwd string `json:"dashboard_pwd"`
|
// DashboardUser specifies the password that the dashboard will use for
|
||||||
AssetsDir string `json:"asserts_dir"`
|
// login. By default, this value is "admin".
|
||||||
LogFile string `json:"log_file"`
|
DashboardPwd string `json:"dashboard_pwd"`
|
||||||
LogWay string `json:"log_way"` // console or file
|
// EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port}
|
||||||
LogLevel string `json:"log_level"`
|
// in /metrics api.
|
||||||
LogMaxDays int64 `json:"log_max_days"`
|
EnablePrometheus bool `json:"enable_prometheus"`
|
||||||
Token string `json:"token"`
|
// AssetsDir specifies the local directory that the dashboard will load
|
||||||
|
// resources from. If this value is "", assets will be loaded from the
|
||||||
|
// bundled executable using statik. By default, this value is "".
|
||||||
|
AssetsDir string `json:"asserts_dir"`
|
||||||
|
// LogFile specifies a file where logs will be written to. This value will
|
||||||
|
// only be used if LogWay is set appropriately. By default, this value is
|
||||||
|
// "console".
|
||||||
|
LogFile string `json:"log_file"`
|
||||||
|
// LogWay specifies the way logging is managed. Valid values are "console"
|
||||||
|
// or "file". If "console" is used, logs will be printed to stdout. If
|
||||||
|
// "file" is used, logs will be printed to LogFile. By default, this value
|
||||||
|
// is "console".
|
||||||
|
LogWay string `json:"log_way"`
|
||||||
|
// LogLevel specifies the minimum log level. Valid values are "trace",
|
||||||
|
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||||
|
LogLevel string `json:"log_level"`
|
||||||
|
// LogMaxDays specifies the maximum number of days to store log information
|
||||||
|
// before deletion. This is only used if LogWay == "file". By default, this
|
||||||
|
// value is 0.
|
||||||
|
LogMaxDays int64 `json:"log_max_days"`
|
||||||
|
// DisableLogColor disables log colors when LogWay == "console" when set to
|
||||||
|
// true. By default, this value is false.
|
||||||
|
DisableLogColor bool `json:"disable_log_color"`
|
||||||
|
// DetailedErrorsToClient defines whether to send the specific error (with
|
||||||
|
// debug info) to frpc. By default, this value is true.
|
||||||
|
DetailedErrorsToClient bool `json:"detailed_errors_to_client"`
|
||||||
|
|
||||||
|
// SubDomainHost specifies the domain that will be attached to sub-domains
|
||||||
|
// requested by the client when using Vhost proxying. For example, if this
|
||||||
|
// value is set to "frps.com" and the client requested the subdomain
|
||||||
|
// "test", the resulting URL would be "test.frps.com". By default, this
|
||||||
|
// value is "".
|
||||||
SubDomainHost string `json:"subdomain_host"`
|
SubDomainHost string `json:"subdomain_host"`
|
||||||
TcpMux bool `json:"tcp_mux"`
|
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
|
||||||
|
// from a client to share a single TCP connection. By default, this value
|
||||||
|
// is true.
|
||||||
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||||
|
// value is "", a default page will be displayed. By default, this value is
|
||||||
|
// "".
|
||||||
Custom404Page string `json:"custom_404_page"`
|
Custom404Page string `json:"custom_404_page"`
|
||||||
|
|
||||||
AllowPorts map[int]struct{}
|
// AllowPorts specifies a set of ports that clients are able to proxy to.
|
||||||
MaxPoolCount int64 `json:"max_pool_count"`
|
// If the length of this value is 0, all ports are allowed. By default,
|
||||||
|
// this value is an empty set.
|
||||||
|
AllowPorts map[int]struct{}
|
||||||
|
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||||
|
// this value is 5.
|
||||||
|
MaxPoolCount int64 `json:"max_pool_count"`
|
||||||
|
// MaxPortsPerClient specifies the maximum number of ports a single client
|
||||||
|
// may proxy to. If this value is 0, no limit will be applied. By default,
|
||||||
|
// this value is 0.
|
||||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
// TlsOnly specifies whether to only accept TLS-encrypted connections. By
|
||||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
// default, the value is false.
|
||||||
|
TlsOnly bool `json:"tls_only"`
|
||||||
|
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||||
|
// before terminating the connection. It is not recommended to change this
|
||||||
|
// value. By default, this value is 90.
|
||||||
|
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||||
|
// UserConnTimeout specifies the maximum time to wait for a work
|
||||||
|
// connection. By default, this value is 10.
|
||||||
|
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||||
|
// HTTPPlugins specify the server plugins support HTTP protocol.
|
||||||
|
HTTPPlugins map[string]plugin.HTTPPluginOptions `json:"http_plugins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultServerConf() *ServerCommonConf {
|
// GetDefaultServerConf returns a server configuration with reasonable
|
||||||
return &ServerCommonConf{
|
// defaults.
|
||||||
BindAddr: "0.0.0.0",
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
BindPort: 7000,
|
return ServerCommonConf{
|
||||||
BindUdpPort: 0,
|
BindAddr: "0.0.0.0",
|
||||||
KcpBindPort: 0,
|
BindPort: 7000,
|
||||||
ProxyBindAddr: "0.0.0.0",
|
BindUdpPort: 0,
|
||||||
VhostHttpPort: 0,
|
KcpBindPort: 0,
|
||||||
VhostHttpsPort: 0,
|
ProxyBindAddr: "0.0.0.0",
|
||||||
VhostHttpTimeout: 60,
|
VhostHttpPort: 0,
|
||||||
DashboardAddr: "0.0.0.0",
|
VhostHttpsPort: 0,
|
||||||
DashboardPort: 0,
|
TcpMuxHttpConnectPort: 0,
|
||||||
DashboardUser: "admin",
|
VhostHttpTimeout: 60,
|
||||||
DashboardPwd: "admin",
|
DashboardAddr: "0.0.0.0",
|
||||||
AssetsDir: "",
|
DashboardPort: 0,
|
||||||
LogFile: "console",
|
DashboardUser: "admin",
|
||||||
LogWay: "console",
|
DashboardPwd: "admin",
|
||||||
LogLevel: "info",
|
EnablePrometheus: false,
|
||||||
LogMaxDays: 3,
|
AssetsDir: "",
|
||||||
Token: "",
|
LogFile: "console",
|
||||||
SubDomainHost: "",
|
LogWay: "console",
|
||||||
TcpMux: true,
|
LogLevel: "info",
|
||||||
AllowPorts: make(map[int]struct{}),
|
LogMaxDays: 3,
|
||||||
MaxPoolCount: 5,
|
DisableLogColor: false,
|
||||||
MaxPortsPerClient: 0,
|
DetailedErrorsToClient: true,
|
||||||
HeartBeatTimeout: 90,
|
SubDomainHost: "",
|
||||||
UserConnTimeout: 10,
|
TcpMux: true,
|
||||||
Custom404Page: "",
|
AllowPorts: make(map[int]struct{}),
|
||||||
|
MaxPoolCount: 5,
|
||||||
|
MaxPortsPerClient: 0,
|
||||||
|
TlsOnly: false,
|
||||||
|
HeartBeatTimeout: 90,
|
||||||
|
UserConnTimeout: 10,
|
||||||
|
Custom404Page: "",
|
||||||
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
|
// UnmarshalServerConfFromIni parses the contents of a server configuration ini
|
||||||
cfg = defaultCfg
|
// file and returns the resulting server configuration.
|
||||||
if cfg == nil {
|
func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
|
||||||
cfg = GetDefaultServerConf()
|
cfg = GetDefaultServerConf()
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := ini.Load(strings.NewReader(content))
|
conf, err := ini.Load(strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("parse ini conf file error: %v", err)
|
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||||
return nil, err
|
return ServerCommonConf{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnmarshalPluginsFromIni(conf, &cfg)
|
||||||
|
|
||||||
|
cfg.AuthServerConfig = auth.UnmarshalAuthServerConfFromIni(conf)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tmpStr string
|
tmpStr string
|
||||||
ok bool
|
ok bool
|
||||||
@@ -185,6 +264,17 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
cfg.VhostHttpsPort = 0
|
cfg.VhostHttpsPort = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tcpmux_httpconnect_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid tcpmux_httpconnect_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.TcpMuxHttpConnectPort = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.TcpMuxHttpConnectPort = 0
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||||
if errRet != nil || v < 0 {
|
if errRet != nil || v < 0 {
|
||||||
@@ -220,6 +310,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
cfg.DashboardPwd = tmpStr
|
cfg.DashboardPwd = tmpStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "enable_prometheus"); ok && tmpStr == "true" {
|
||||||
|
cfg.EnablePrometheus = true
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||||
cfg.AssetsDir = tmpStr
|
cfg.AssetsDir = tmpStr
|
||||||
}
|
}
|
||||||
@@ -244,7 +338,15 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Token, _ = conf.Get("common", "token")
|
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
|
||||||
|
cfg.DisableLogColor = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "detailed_errors_to_client"); ok && tmpStr == "false" {
|
||||||
|
cfg.DetailedErrorsToClient = false
|
||||||
|
} else {
|
||||||
|
cfg.DetailedErrorsToClient = true
|
||||||
|
}
|
||||||
|
|
||||||
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||||
// e.g. 1000-2000,2001,2002,3000-4000
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
@@ -308,9 +410,33 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
cfg.HeartBeatTimeout = v
|
cfg.HeartBeatTimeout = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" {
|
||||||
|
cfg.TlsOnly = true
|
||||||
|
} else {
|
||||||
|
cfg.TlsOnly = false
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) {
|
||||||
|
for name, section := range sections {
|
||||||
|
if strings.HasPrefix(name, "plugin.") {
|
||||||
|
name = strings.TrimSpace(strings.TrimPrefix(name, "plugin."))
|
||||||
|
options := plugin.HTTPPluginOptions{
|
||||||
|
Name: name,
|
||||||
|
Addr: section["addr"],
|
||||||
|
Path: section["path"],
|
||||||
|
Ops: strings.Split(section["ops"], ","),
|
||||||
|
}
|
||||||
|
for i, _ := range options.Ops {
|
||||||
|
options.Ops[i] = strings.TrimSpace(options.Ops[i])
|
||||||
|
}
|
||||||
|
cfg.HTTPPlugins[name] = options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *ServerCommonConf) Check() (err error) {
|
func (cfg *ServerCommonConf) Check() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
112
models/config/types.go
Normal file
112
models/config/types.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MB = 1024 * 1024
|
||||||
|
KB = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
type BandwidthQuantity struct {
|
||||||
|
s string // MB or KB
|
||||||
|
|
||||||
|
i int64 // bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBandwidthQuantity(s string) (BandwidthQuantity, error) {
|
||||||
|
q := BandwidthQuantity{}
|
||||||
|
err := q.UnmarshalString(s)
|
||||||
|
if err != nil {
|
||||||
|
return q, err
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
|
||||||
|
if q == nil && u == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if q != nil && u != nil {
|
||||||
|
return q.i == u.i
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *BandwidthQuantity) String() string {
|
||||||
|
return q.s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *BandwidthQuantity) UnmarshalString(s string) error {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
base int64
|
||||||
|
f float64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if strings.HasSuffix(s, "MB") {
|
||||||
|
base = MB
|
||||||
|
fstr := strings.TrimSuffix(s, "MB")
|
||||||
|
f, err = strconv.ParseFloat(fstr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(s, "KB") {
|
||||||
|
base = KB
|
||||||
|
fstr := strings.TrimSuffix(s, "KB")
|
||||||
|
f, err = strconv.ParseFloat(fstr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("unit not support")
|
||||||
|
}
|
||||||
|
|
||||||
|
q.s = s
|
||||||
|
q.i = int64(f * float64(base))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *BandwidthQuantity) UnmarshalJSON(b []byte) error {
|
||||||
|
if len(b) == 4 && string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var str string
|
||||||
|
err := json.Unmarshal(b, &str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.UnmarshalString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte("\"" + q.s + "\""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *BandwidthQuantity) Bytes() int64 {
|
||||||
|
return q.i
|
||||||
|
}
|
||||||
40
models/config/types_test.go
Normal file
40
models/config/types_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Wrap struct {
|
||||||
|
B BandwidthQuantity `json:"b"`
|
||||||
|
Int int `json:"int"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBandwidthQuantity(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
var w Wrap
|
||||||
|
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.EqualValues(1*KB, w.B.Bytes())
|
||||||
|
|
||||||
|
buf, err := json.Marshal(&w)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(`{"b":"1KB","int":5}`, string(buf))
|
||||||
|
}
|
||||||
@@ -23,10 +23,18 @@ var (
|
|||||||
Offline string = "offline"
|
Offline string = "offline"
|
||||||
|
|
||||||
// proxy type
|
// proxy type
|
||||||
TcpProxy string = "tcp"
|
TcpProxy string = "tcp"
|
||||||
UdpProxy string = "udp"
|
UdpProxy string = "udp"
|
||||||
HttpProxy string = "http"
|
TcpMuxProxy string = "tcpmux"
|
||||||
HttpsProxy string = "https"
|
HttpProxy string = "http"
|
||||||
StcpProxy string = "stcp"
|
HttpsProxy string = "https"
|
||||||
XtcpProxy string = "xtcp"
|
StcpProxy string = "stcp"
|
||||||
|
XtcpProxy string = "xtcp"
|
||||||
|
|
||||||
|
// authentication method
|
||||||
|
TokenAuthMethod string = "token"
|
||||||
|
OidcAuthMethod string = "oidc"
|
||||||
|
|
||||||
|
// tcp multiplexer
|
||||||
|
HttpConnectTcpMultiplexer string = "httpconnect"
|
||||||
)
|
)
|
||||||
|
|||||||
93
models/metrics/aggregate/server.go
Normal file
93
models/metrics/aggregate/server.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2020 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package aggregate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/models/metrics/mem"
|
||||||
|
"github.com/fatedier/frp/models/metrics/prometheus"
|
||||||
|
"github.com/fatedier/frp/server/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableMem start to mark metrics to memory monitor system.
|
||||||
|
func EnableMem() {
|
||||||
|
sm.Add(mem.ServerMetrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnablePrometheus start to mark metrics to prometheus.
|
||||||
|
func EnablePrometheus() {
|
||||||
|
sm.Add(prometheus.ServerMetrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sm *serverMetrics = &serverMetrics{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
metrics.Register(sm)
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverMetrics struct {
|
||||||
|
ms []metrics.ServerMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) Add(sm metrics.ServerMetrics) {
|
||||||
|
m.ms = append(m.ms, sm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) NewClient() {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.NewClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseClient() {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.CloseClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.NewProxy(name, proxyType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseProxy(name string, proxyType string) {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.CloseProxy(name, proxyType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) OpenConnection(name string, proxyType string) {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.OpenConnection(name, proxyType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseConnection(name string, proxyType string) {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.CloseConnection(name, proxyType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.AddTrafficIn(name, proxyType, trafficBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
|
||||||
|
for _, v := range m.ms {
|
||||||
|
v.AddTrafficOut(name, proxyType, trafficBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
262
models/metrics/mem/server.go
Normal file
262
models/metrics/mem/server.go
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package mem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
server "github.com/fatedier/frp/server/metrics"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/metric"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sm *serverMetrics = newServerMetrics()
|
||||||
|
var ServerMetrics server.ServerMetrics
|
||||||
|
var StatsCollector Collector
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ServerMetrics = sm
|
||||||
|
StatsCollector = sm
|
||||||
|
sm.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverMetrics struct {
|
||||||
|
info *ServerStatistics
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServerMetrics() *serverMetrics {
|
||||||
|
return &serverMetrics{
|
||||||
|
info: &ServerStatistics{
|
||||||
|
TotalTrafficIn: metric.NewDateCounter(ReserveDays),
|
||||||
|
TotalTrafficOut: metric.NewDateCounter(ReserveDays),
|
||||||
|
CurConns: metric.NewCounter(),
|
||||||
|
|
||||||
|
ClientCounts: metric.NewCounter(),
|
||||||
|
ProxyTypeCounts: make(map[string]metric.Counter),
|
||||||
|
|
||||||
|
ProxyStatistics: make(map[string]*ProxyStatistics),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) run() {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(12 * time.Hour)
|
||||||
|
log.Debug("start to clear useless proxy statistics data...")
|
||||||
|
m.clearUselessInfo()
|
||||||
|
log.Debug("finish to clear useless proxy statistics data")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) clearUselessInfo() {
|
||||||
|
// To check if there are proxies that closed than 7 days and drop them.
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
for name, data := range m.info.ProxyStatistics {
|
||||||
|
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||||
|
delete(m.info.ProxyStatistics, name)
|
||||||
|
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) NewClient() {
|
||||||
|
m.info.ClientCounts.Inc(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseClient() {
|
||||||
|
m.info.ClientCounts.Dec(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
counter, ok := m.info.ProxyTypeCounts[proxyType]
|
||||||
|
if !ok {
|
||||||
|
counter = metric.NewCounter()
|
||||||
|
}
|
||||||
|
counter.Inc(1)
|
||||||
|
m.info.ProxyTypeCounts[proxyType] = counter
|
||||||
|
|
||||||
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
|
if !(ok && proxyStats.ProxyType == proxyType) {
|
||||||
|
proxyStats = &ProxyStatistics{
|
||||||
|
Name: name,
|
||||||
|
ProxyType: proxyType,
|
||||||
|
CurConns: metric.NewCounter(),
|
||||||
|
TrafficIn: metric.NewDateCounter(ReserveDays),
|
||||||
|
TrafficOut: metric.NewDateCounter(ReserveDays),
|
||||||
|
}
|
||||||
|
m.info.ProxyStatistics[name] = proxyStats
|
||||||
|
}
|
||||||
|
proxyStats.LastStartTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseProxy(name string, proxyType string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if counter, ok := m.info.ProxyTypeCounts[proxyType]; ok {
|
||||||
|
counter.Dec(1)
|
||||||
|
}
|
||||||
|
if proxyStats, ok := m.info.ProxyStatistics[name]; ok {
|
||||||
|
proxyStats.LastCloseTime = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) OpenConnection(name string, proxyType string) {
|
||||||
|
m.info.CurConns.Inc(1)
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
|
if ok {
|
||||||
|
proxyStats.CurConns.Inc(1)
|
||||||
|
m.info.ProxyStatistics[name] = proxyStats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseConnection(name string, proxyType string) {
|
||||||
|
m.info.CurConns.Dec(1)
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
|
if ok {
|
||||||
|
proxyStats.CurConns.Dec(1)
|
||||||
|
m.info.ProxyStatistics[name] = proxyStats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
|
||||||
|
m.info.TotalTrafficIn.Inc(trafficBytes)
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
|
if ok {
|
||||||
|
proxyStats.TrafficIn.Inc(trafficBytes)
|
||||||
|
m.info.ProxyStatistics[name] = proxyStats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
|
||||||
|
m.info.TotalTrafficOut.Inc(trafficBytes)
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
|
if ok {
|
||||||
|
proxyStats.TrafficOut.Inc(trafficBytes)
|
||||||
|
m.info.ProxyStatistics[name] = proxyStats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get stats data api.
|
||||||
|
|
||||||
|
func (m *serverMetrics) GetServer() *ServerStats {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
s := &ServerStats{
|
||||||
|
TotalTrafficIn: m.info.TotalTrafficIn.TodayCount(),
|
||||||
|
TotalTrafficOut: m.info.TotalTrafficOut.TodayCount(),
|
||||||
|
CurConns: m.info.CurConns.Count(),
|
||||||
|
ClientCounts: m.info.ClientCounts.Count(),
|
||||||
|
ProxyTypeCounts: make(map[string]int64),
|
||||||
|
}
|
||||||
|
for k, v := range m.info.ProxyTypeCounts {
|
||||||
|
s.ProxyTypeCounts[k] = v.Count()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) GetProxiesByType(proxyType string) []*ProxyStats {
|
||||||
|
res := make([]*ProxyStats, 0)
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
for name, proxyStats := range m.info.ProxyStatistics {
|
||||||
|
if proxyStats.ProxyType != proxyType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := &ProxyStats{
|
||||||
|
Name: name,
|
||||||
|
Type: proxyStats.ProxyType,
|
||||||
|
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||||
|
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||||
|
CurConns: proxyStats.CurConns.Count(),
|
||||||
|
}
|
||||||
|
if !proxyStats.LastStartTime.IsZero() {
|
||||||
|
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
|
||||||
|
}
|
||||||
|
if !proxyStats.LastCloseTime.IsZero() {
|
||||||
|
ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
|
||||||
|
}
|
||||||
|
res = append(res, ps)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) GetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
for name, proxyStats := range m.info.ProxyStatistics {
|
||||||
|
if proxyStats.ProxyType != proxyType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != proxyName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res = &ProxyStats{
|
||||||
|
Name: name,
|
||||||
|
Type: proxyStats.ProxyType,
|
||||||
|
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
|
||||||
|
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
|
||||||
|
CurConns: proxyStats.CurConns.Count(),
|
||||||
|
}
|
||||||
|
if !proxyStats.LastStartTime.IsZero() {
|
||||||
|
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
|
||||||
|
}
|
||||||
|
if !proxyStats.LastCloseTime.IsZero() {
|
||||||
|
res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) GetProxyTraffic(name string) (res *ProxyTrafficInfo) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
|
if ok {
|
||||||
|
res = &ProxyTrafficInfo{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
|
||||||
|
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package stats
|
package mem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
@@ -24,19 +24,6 @@ const (
|
|||||||
ReserveDays = 7
|
ReserveDays = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatsType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeNewClient StatsType = iota
|
|
||||||
TypeCloseClient
|
|
||||||
TypeNewProxy
|
|
||||||
TypeCloseProxy
|
|
||||||
TypeOpenConnection
|
|
||||||
TypeCloseConnection
|
|
||||||
TypeAddTrafficIn
|
|
||||||
TypeAddTrafficOut
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServerStats struct {
|
type ServerStats struct {
|
||||||
TotalTrafficIn int64
|
TotalTrafficIn int64
|
||||||
TotalTrafficOut int64
|
TotalTrafficOut int64
|
||||||
@@ -88,42 +75,8 @@ type ServerStatistics struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Collector interface {
|
type Collector interface {
|
||||||
Mark(statsType StatsType, payload interface{})
|
|
||||||
Run() error
|
|
||||||
GetServer() *ServerStats
|
GetServer() *ServerStats
|
||||||
GetProxiesByType(proxyType string) []*ProxyStats
|
GetProxiesByType(proxyType string) []*ProxyStats
|
||||||
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
GetProxiesByTypeAndName(proxyType string, proxyName string) *ProxyStats
|
||||||
GetProxyTraffic(name string) *ProxyTrafficInfo
|
GetProxyTraffic(name string) *ProxyTrafficInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewClientPayload struct{}
|
|
||||||
|
|
||||||
type CloseClientPayload struct{}
|
|
||||||
|
|
||||||
type NewProxyPayload struct {
|
|
||||||
Name string
|
|
||||||
ProxyType string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloseProxyPayload struct {
|
|
||||||
Name string
|
|
||||||
ProxyType string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenConnectionPayload struct {
|
|
||||||
ProxyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloseConnectionPayload struct {
|
|
||||||
ProxyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTrafficInPayload struct {
|
|
||||||
ProxyName string
|
|
||||||
TrafficBytes int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTrafficOutPayload struct {
|
|
||||||
ProxyName string
|
|
||||||
TrafficBytes int64
|
|
||||||
}
|
|
||||||
8
models/metrics/metrics.go
Normal file
8
models/metrics/metrics.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/models/metrics/aggregate"
|
||||||
|
)
|
||||||
|
|
||||||
|
var EnableMem = aggregate.EnableMem
|
||||||
|
var EnablePrometheus = aggregate.EnablePrometheus
|
||||||
95
models/metrics/prometheus/server.go
Normal file
95
models/metrics/prometheus/server.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/server/metrics"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
namespace = "frp"
|
||||||
|
serverSubsystem = "server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ServerMetrics metrics.ServerMetrics = newServerMetrics()
|
||||||
|
|
||||||
|
type serverMetrics struct {
|
||||||
|
clientCount prometheus.Gauge
|
||||||
|
proxyCount *prometheus.GaugeVec
|
||||||
|
connectionCount *prometheus.GaugeVec
|
||||||
|
trafficIn *prometheus.CounterVec
|
||||||
|
trafficOut *prometheus.CounterVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) NewClient() {
|
||||||
|
m.clientCount.Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseClient() {
|
||||||
|
m.clientCount.Dec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||||
|
m.proxyCount.WithLabelValues(proxyType).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseProxy(name string, proxyType string) {
|
||||||
|
m.proxyCount.WithLabelValues(proxyType).Dec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) OpenConnection(name string, proxyType string) {
|
||||||
|
m.connectionCount.WithLabelValues(name, proxyType).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) CloseConnection(name string, proxyType string) {
|
||||||
|
m.connectionCount.WithLabelValues(name, proxyType).Dec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {
|
||||||
|
m.trafficIn.WithLabelValues(name, proxyType).Add(float64(trafficBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {
|
||||||
|
m.trafficOut.WithLabelValues(name, proxyType).Add(float64(trafficBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServerMetrics() *serverMetrics {
|
||||||
|
m := &serverMetrics{
|
||||||
|
clientCount: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: serverSubsystem,
|
||||||
|
Name: "client_counts",
|
||||||
|
Help: "The current client counts of frps",
|
||||||
|
}),
|
||||||
|
proxyCount: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: serverSubsystem,
|
||||||
|
Name: "proxy_counts",
|
||||||
|
Help: "The current proxy counts",
|
||||||
|
}, []string{"type"}),
|
||||||
|
connectionCount: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: serverSubsystem,
|
||||||
|
Name: "connection_counts",
|
||||||
|
Help: "The current connection counts",
|
||||||
|
}, []string{"name", "type"}),
|
||||||
|
trafficIn: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: serverSubsystem,
|
||||||
|
Name: "traffic_in",
|
||||||
|
Help: "The total in traffic",
|
||||||
|
}, []string{"name", "type"}),
|
||||||
|
trafficOut: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: serverSubsystem,
|
||||||
|
Name: "traffic_out",
|
||||||
|
Help: "The total out traffic",
|
||||||
|
}, []string{"name", "type"}),
|
||||||
|
}
|
||||||
|
prometheus.MustRegister(m.clientCount)
|
||||||
|
prometheus.MustRegister(m.proxyCount)
|
||||||
|
prometheus.MustRegister(m.connectionCount)
|
||||||
|
prometheus.MustRegister(m.trafficIn)
|
||||||
|
prometheus.MustRegister(m.trafficOut)
|
||||||
|
return m
|
||||||
|
}
|
||||||
@@ -62,14 +62,15 @@ var (
|
|||||||
|
|
||||||
// When frpc start, client send this message to login to server.
|
// When frpc start, client send this message to login to server.
|
||||||
type Login struct {
|
type Login struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
Os string `json:"os"`
|
Os string `json:"os"`
|
||||||
Arch string `json:"arch"`
|
Arch string `json:"arch"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
PrivilegeKey string `json:"privilege_key"`
|
PrivilegeKey string `json:"privilege_key"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
RunId string `json:"run_id"`
|
RunId string `json:"run_id"`
|
||||||
|
Metas map[string]string `json:"metas"`
|
||||||
|
|
||||||
// Some global configures.
|
// Some global configures.
|
||||||
PoolCount int `json:"pool_count"`
|
PoolCount int `json:"pool_count"`
|
||||||
@@ -84,12 +85,13 @@ type LoginResp struct {
|
|||||||
|
|
||||||
// When frpc login success, send this message to frps for running a new proxy.
|
// When frpc login success, send this message to frps for running a new proxy.
|
||||||
type NewProxy struct {
|
type NewProxy struct {
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
ProxyType string `json:"proxy_type"`
|
ProxyType string `json:"proxy_type"`
|
||||||
UseEncryption bool `json:"use_encryption"`
|
UseEncryption bool `json:"use_encryption"`
|
||||||
UseCompression bool `json:"use_compression"`
|
UseCompression bool `json:"use_compression"`
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
GroupKey string `json:"group_key"`
|
GroupKey string `json:"group_key"`
|
||||||
|
Metas map[string]string `json:"metas"`
|
||||||
|
|
||||||
// tcp and udp only
|
// tcp and udp only
|
||||||
RemotePort int `json:"remote_port"`
|
RemotePort int `json:"remote_port"`
|
||||||
@@ -105,6 +107,9 @@ type NewProxy struct {
|
|||||||
|
|
||||||
// stcp
|
// stcp
|
||||||
Sk string `json:"sk"`
|
Sk string `json:"sk"`
|
||||||
|
|
||||||
|
// tcpmux
|
||||||
|
Multiplexer string `json:"multiplexer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewProxyResp struct {
|
type NewProxyResp struct {
|
||||||
@@ -118,7 +123,9 @@ type CloseProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NewWorkConn struct {
|
type NewWorkConn struct {
|
||||||
RunId string `json:"run_id"`
|
RunId string `json:"run_id"`
|
||||||
|
PrivilegeKey string `json:"privilege_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReqWorkConn struct {
|
type ReqWorkConn struct {
|
||||||
@@ -130,6 +137,7 @@ type StartWorkConn struct {
|
|||||||
DstAddr string `json:"dst_addr"`
|
DstAddr string `json:"dst_addr"`
|
||||||
SrcPort uint16 `json:"src_port"`
|
SrcPort uint16 `json:"src_port"`
|
||||||
DstPort uint16 `json:"dst_port"`
|
DstPort uint16 `json:"dst_port"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewVisitorConn struct {
|
type NewVisitorConn struct {
|
||||||
@@ -146,9 +154,12 @@ type NewVisitorConnResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Ping struct {
|
type Ping struct {
|
||||||
|
PrivilegeKey string `json:"privilege_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pong struct {
|
type Pong struct {
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UdpPacket struct {
|
type UdpPacket struct {
|
||||||
|
|||||||
111
models/plugin/client/http2https.go
Normal file
111
models/plugin/client/http2https.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginHTTP2HTTPS = "http2https"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTP2HTTPSPlugin struct {
|
||||||
|
hostHeaderRewrite string
|
||||||
|
localAddr string
|
||||||
|
headers map[string]string
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||||
|
localAddr := params["plugin_local_addr"]
|
||||||
|
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||||
|
headers := make(map[string]string)
|
||||||
|
for k, v := range params {
|
||||||
|
if !strings.HasPrefix(k, "plugin_header_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||||
|
headers[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if localAddr == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
p := &HTTPS2HTTPPlugin{
|
||||||
|
localAddr: localAddr,
|
||||||
|
hostHeaderRewrite: hostHeaderRewrite,
|
||||||
|
headers: headers,
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL.Scheme = "https"
|
||||||
|
req.URL.Host = p.localAddr
|
||||||
|
if p.hostHeaderRewrite != "" {
|
||||||
|
req.Host = p.hostHeaderRewrite
|
||||||
|
}
|
||||||
|
for k, v := range p.headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: rp,
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.s.Serve(listener)
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
p.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTP2HTTPSPlugin) Name() string {
|
||||||
|
return PluginHTTP2HTTPS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTP2HTTPSPlugin) Close() error {
|
||||||
|
if err := p.s.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
|
|||||||
return PluginHttpProxy
|
return PluginHttpProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
|
||||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||||
@@ -18,8 +18,10 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
@@ -35,6 +37,7 @@ type HTTPS2HTTPPlugin struct {
|
|||||||
keyPath string
|
keyPath string
|
||||||
hostHeaderRewrite string
|
hostHeaderRewrite string
|
||||||
localAddr string
|
localAddr string
|
||||||
|
headers map[string]string
|
||||||
|
|
||||||
l *Listener
|
l *Listener
|
||||||
s *http.Server
|
s *http.Server
|
||||||
@@ -45,6 +48,15 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
|||||||
keyPath := params["plugin_key_path"]
|
keyPath := params["plugin_key_path"]
|
||||||
localAddr := params["plugin_local_addr"]
|
localAddr := params["plugin_local_addr"]
|
||||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||||
|
headers := make(map[string]string)
|
||||||
|
for k, v := range params {
|
||||||
|
if !strings.HasPrefix(k, "plugin_header_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||||
|
headers[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if crtPath == "" {
|
if crtPath == "" {
|
||||||
return nil, fmt.Errorf("plugin_crt_path is required")
|
return nil, fmt.Errorf("plugin_crt_path is required")
|
||||||
@@ -63,6 +75,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
|||||||
keyPath: keyPath,
|
keyPath: keyPath,
|
||||||
localAddr: localAddr,
|
localAddr: localAddr,
|
||||||
hostHeaderRewrite: hostHeaderRewrite,
|
hostHeaderRewrite: hostHeaderRewrite,
|
||||||
|
headers: headers,
|
||||||
l: listener,
|
l: listener,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +86,9 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
|||||||
if p.hostHeaderRewrite != "" {
|
if p.hostHeaderRewrite != "" {
|
||||||
req.Host = p.hostHeaderRewrite
|
req.Host = p.hostHeaderRewrite
|
||||||
}
|
}
|
||||||
|
for k, v := range p.headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +116,7 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
p.l.PutConn(wrapConn)
|
p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
@@ -110,5 +126,8 @@ func (p *HTTPS2HTTPPlugin) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) Close() error {
|
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||||
|
if err := p.s.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -20,8 +20,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +44,9 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
|||||||
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
|
||||||
|
// extraBufToLocal will send to local connection first, then join conn with local connection
|
||||||
|
Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
sp.Server.ServeConn(wrapConn)
|
sp.Server.ServeConn(wrapConn)
|
||||||
@@ -16,6 +16,7 @@ package plugin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
@@ -72,7 +73,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
|||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
sp.l.PutConn(wrapConn)
|
sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
105
models/plugin/server/http.go
Normal file
105
models/plugin/server/http.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPPluginOptions struct {
|
||||||
|
Name string
|
||||||
|
Addr string
|
||||||
|
Path string
|
||||||
|
Ops []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPlugin struct {
|
||||||
|
options HTTPPluginOptions
|
||||||
|
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin {
|
||||||
|
return &httpPlugin{
|
||||||
|
options: options,
|
||||||
|
url: fmt.Sprintf("http://%s%s", options.Addr, options.Path),
|
||||||
|
client: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPlugin) Name() string {
|
||||||
|
return p.options.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPlugin) IsSupport(op string) bool {
|
||||||
|
for _, v := range p.options.Ops {
|
||||||
|
if v == op {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPlugin) Handle(ctx context.Context, op string, content interface{}) (*Response, interface{}, error) {
|
||||||
|
r := &Request{
|
||||||
|
Version: APIVersion,
|
||||||
|
Op: op,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
var res Response
|
||||||
|
res.Content = reflect.New(reflect.TypeOf(content)).Interface()
|
||||||
|
if err := p.do(ctx, r, &res); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &res, res.Content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error {
|
||||||
|
buf, err := json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", p.url, bytes.NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
req.Header.Set("X-Frp-Reqid", GetReqidFromContext(ctx))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("do http request error code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
buf, err = ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(buf, res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
105
models/plugin/server/manager.go
Normal file
105
models/plugin/server/manager.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
loginPlugins []Plugin
|
||||||
|
newProxyPlugins []Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() *Manager {
|
||||||
|
return &Manager{
|
||||||
|
loginPlugins: make([]Plugin, 0),
|
||||||
|
newProxyPlugins: make([]Plugin, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Register(p Plugin) {
|
||||||
|
if p.IsSupport(OpLogin) {
|
||||||
|
m.loginPlugins = append(m.loginPlugins, p)
|
||||||
|
}
|
||||||
|
if p.IsSupport(OpNewProxy) {
|
||||||
|
m.newProxyPlugins = append(m.newProxyPlugins, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
|
||||||
|
var (
|
||||||
|
res = &Response{
|
||||||
|
Reject: false,
|
||||||
|
Unchange: true,
|
||||||
|
}
|
||||||
|
retContent interface{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
reqid, _ := util.RandId()
|
||||||
|
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||||
|
ctx := xlog.NewContext(context.Background(), xl)
|
||||||
|
ctx = NewReqidContext(ctx, reqid)
|
||||||
|
|
||||||
|
for _, p := range m.loginPlugins {
|
||||||
|
res, retContent, err = p.Handle(ctx, OpLogin, *content)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("send Login request to plugin [%s] error: %v", p.Name(), err)
|
||||||
|
return nil, errors.New("send Login request to plugin error")
|
||||||
|
}
|
||||||
|
if res.Reject {
|
||||||
|
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||||
|
}
|
||||||
|
if !res.Unchange {
|
||||||
|
content = retContent.(*LoginContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
|
||||||
|
var (
|
||||||
|
res = &Response{
|
||||||
|
Reject: false,
|
||||||
|
Unchange: true,
|
||||||
|
}
|
||||||
|
retContent interface{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
reqid, _ := util.RandId()
|
||||||
|
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||||
|
ctx := xlog.NewContext(context.Background(), xl)
|
||||||
|
ctx = NewReqidContext(ctx, reqid)
|
||||||
|
|
||||||
|
for _, p := range m.newProxyPlugins {
|
||||||
|
res, retContent, err = p.Handle(ctx, OpNewProxy, *content)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("send NewProxy request to plugin [%s] error: %v", p.Name(), err)
|
||||||
|
return nil, errors.New("send NewProxy request to plugin error")
|
||||||
|
}
|
||||||
|
if res.Reject {
|
||||||
|
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||||
|
}
|
||||||
|
if !res.Unchange {
|
||||||
|
content = retContent.(*NewProxyContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,19 +12,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package errors
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PanicToError(fn func()) (err error) {
|
const (
|
||||||
defer func() {
|
APIVersion = "0.1.0"
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = fmt.Errorf("Panic error: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
fn()
|
OpLogin = "Login"
|
||||||
return
|
OpNewProxy = "NewProxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
Name() string
|
||||||
|
IsSupport(op string) bool
|
||||||
|
Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error)
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// 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
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -12,17 +12,23 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// +build !windows
|
package plugin
|
||||||
|
|
||||||
package logs
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
import "io"
|
type key int
|
||||||
|
|
||||||
type ansiColorWriter struct {
|
const (
|
||||||
w io.Writer
|
reqidKey key = 0
|
||||||
mode outputMode
|
)
|
||||||
|
|
||||||
|
func NewReqidContext(ctx context.Context, reqid string) context.Context {
|
||||||
|
return context.WithValue(ctx, reqidKey, reqid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
func GetReqidFromContext(ctx context.Context) string {
|
||||||
return cw.w.Write(p)
|
ret, _ := ctx.Value(reqidKey).(string)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
46
models/plugin/server/types.go
Normal file
46
models/plugin/server/types.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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Op string `json:"op"`
|
||||||
|
Content interface{} `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Reject bool `json:"reject"`
|
||||||
|
RejectReason string `json:"reject_reason"`
|
||||||
|
Unchange bool `json:"unchange"`
|
||||||
|
Content interface{} `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginContent struct {
|
||||||
|
msg.Login
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
Metas map[string]string `json:"metas"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewProxyContent struct {
|
||||||
|
User UserInfo `json:"user"`
|
||||||
|
msg.NewProxy
|
||||||
|
}
|
||||||
@@ -117,6 +117,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
|
|||||||
if !ok {
|
if !ok {
|
||||||
udpConn, err = net.DialUDP("udp", nil, dstAddr)
|
udpConn, err = net.DialUDP("udp", nil, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
mu.Unlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
udpConnMap[udpMsg.RemoteAddr.String()] = udpConn
|
udpConnMap[udpMsg.RemoteAddr.String()] = udpConn
|
||||||
|
|||||||
@@ -15,22 +15,26 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/auth"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/consts"
|
"github.com/fatedier/frp/models/consts"
|
||||||
frpErr "github.com/fatedier/frp/models/errors"
|
frpErr "github.com/fatedier/frp/models/errors"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
plugin "github.com/fatedier/frp/models/plugin/server"
|
||||||
"github.com/fatedier/frp/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
|
"github.com/fatedier/frp/server/metrics"
|
||||||
"github.com/fatedier/frp/server/proxy"
|
"github.com/fatedier/frp/server/proxy"
|
||||||
"github.com/fatedier/frp/server/stats"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/net"
|
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
"github.com/fatedier/golib/control/shutdown"
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
@@ -85,8 +89,11 @@ type Control struct {
|
|||||||
// proxy manager
|
// proxy manager
|
||||||
pxyManager *proxy.ProxyManager
|
pxyManager *proxy.ProxyManager
|
||||||
|
|
||||||
// stats collector to store stats info of clients and proxies
|
// plugin manager
|
||||||
statsCollector stats.Collector
|
pluginManager *plugin.Manager
|
||||||
|
|
||||||
|
// verifies authentication based on selected method
|
||||||
|
authVerifier auth.Verifier
|
||||||
|
|
||||||
// login message
|
// login message
|
||||||
loginMsg *msg.Login
|
loginMsg *msg.Login
|
||||||
@@ -129,22 +136,41 @@ type Control struct {
|
|||||||
allShutdown *shutdown.Shutdown
|
allShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
// Server configuration information
|
||||||
|
serverCfg config.ServerCommonConf
|
||||||
|
|
||||||
|
xl *xlog.Logger
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
|
func NewControl(
|
||||||
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
ctx context.Context,
|
||||||
|
rc *controller.ResourceController,
|
||||||
|
pxyManager *proxy.ProxyManager,
|
||||||
|
pluginManager *plugin.Manager,
|
||||||
|
authVerifier auth.Verifier,
|
||||||
|
ctlConn net.Conn,
|
||||||
|
loginMsg *msg.Login,
|
||||||
|
serverCfg config.ServerCommonConf,
|
||||||
|
) *Control {
|
||||||
|
|
||||||
|
poolCount := loginMsg.PoolCount
|
||||||
|
if poolCount > int(serverCfg.MaxPoolCount) {
|
||||||
|
poolCount = int(serverCfg.MaxPoolCount)
|
||||||
|
}
|
||||||
return &Control{
|
return &Control{
|
||||||
rc: rc,
|
rc: rc,
|
||||||
pxyManager: pxyManager,
|
pxyManager: pxyManager,
|
||||||
statsCollector: statsCollector,
|
pluginManager: pluginManager,
|
||||||
|
authVerifier: authVerifier,
|
||||||
conn: ctlConn,
|
conn: ctlConn,
|
||||||
loginMsg: loginMsg,
|
loginMsg: loginMsg,
|
||||||
sendCh: make(chan msg.Message, 10),
|
sendCh: make(chan msg.Message, 10),
|
||||||
readCh: make(chan msg.Message, 10),
|
readCh: make(chan msg.Message, 10),
|
||||||
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
workConnCh: make(chan net.Conn, poolCount+10),
|
||||||
proxies: make(map[string]proxy.Proxy),
|
proxies: make(map[string]proxy.Proxy),
|
||||||
poolCount: loginMsg.PoolCount,
|
poolCount: poolCount,
|
||||||
portsUsedNum: 0,
|
portsUsedNum: 0,
|
||||||
lastPing: time.Now(),
|
lastPing: time.Now(),
|
||||||
runId: loginMsg.RunId,
|
runId: loginMsg.RunId,
|
||||||
@@ -153,6 +179,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
|
|||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
managerShutdown: shutdown.New(),
|
managerShutdown: shutdown.New(),
|
||||||
allShutdown: shutdown.New(),
|
allShutdown: shutdown.New(),
|
||||||
|
serverCfg: serverCfg,
|
||||||
|
xl: xlog.FromContextSafe(ctx),
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +190,7 @@ func (ctl *Control) Start() {
|
|||||||
loginRespMsg := &msg.LoginResp{
|
loginRespMsg := &msg.LoginResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
RunId: ctl.runId,
|
RunId: ctl.runId,
|
||||||
ServerUdpPort: g.GlbServerCfg.BindUdpPort,
|
ServerUdpPort: ctl.serverCfg.BindUdpPort,
|
||||||
Error: "",
|
Error: "",
|
||||||
}
|
}
|
||||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||||
@@ -176,20 +205,22 @@ func (ctl *Control) Start() {
|
|||||||
go ctl.stoper()
|
go ctl.stoper()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) RegisterWorkConn(conn net.Conn) {
|
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.conn.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.conn.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case ctl.workConnCh <- conn:
|
case ctl.workConnCh <- conn:
|
||||||
ctl.conn.Debug("new work connection registered")
|
xl.Debug("new work connection registered")
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
ctl.conn.Debug("work connection pool is full, discarding")
|
xl.Debug("work connection pool is full, discarding")
|
||||||
conn.Close()
|
return fmt.Errorf("work connection pool is full, discarding")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,10 +229,11 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) {
|
|||||||
// and wait until it is available.
|
// and wait until it is available.
|
||||||
// return an error if wait timeout
|
// return an error if wait timeout
|
||||||
func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.conn.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.conn.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -213,14 +245,14 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
err = frpErr.ErrCtlClosed
|
err = frpErr.ErrCtlClosed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctl.conn.Debug("get work connection from pool")
|
xl.Debug("get work connection from pool")
|
||||||
default:
|
default:
|
||||||
// no work connections available in the poll, send message to frpc to get more
|
// no work connections available in the poll, send message to frpc to get more
|
||||||
err = errors.PanicToError(func() {
|
err = errors.PanicToError(func() {
|
||||||
ctl.sendCh <- &msg.ReqWorkConn{}
|
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.conn.Error("%v", err)
|
xl.Error("%v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,13 +260,13 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
case workConn, ok = <-ctl.workConnCh:
|
case workConn, ok = <-ctl.workConnCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
err = frpErr.ErrCtlClosed
|
err = frpErr.ErrCtlClosed
|
||||||
ctl.conn.Warn("no work connections avaiable, %v", err)
|
xl.Warn("no work connections avaiable, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
|
case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
|
||||||
err = fmt.Errorf("timeout trying to get work connection")
|
err = fmt.Errorf("timeout trying to get work connection")
|
||||||
ctl.conn.Warn("%v", err)
|
xl.Warn("%v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,35 +279,37 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) Replaced(newCtl *Control) {
|
func (ctl *Control) Replaced(newCtl *Control) {
|
||||||
ctl.conn.Info("Replaced by client [%s]", newCtl.runId)
|
xl := ctl.xl
|
||||||
|
xl.Info("Replaced by client [%s]", newCtl.runId)
|
||||||
ctl.runId = ""
|
ctl.runId = ""
|
||||||
ctl.allShutdown.Start()
|
ctl.allShutdown.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) writer() {
|
func (ctl *Control) writer() {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.conn.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.conn.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
defer ctl.allShutdown.Start()
|
defer ctl.allShutdown.Start()
|
||||||
defer ctl.writerShutdown.Done()
|
defer ctl.writerShutdown.Done()
|
||||||
|
|
||||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.conn.Error("crypto new writer error: %v", err)
|
xl.Error("crypto new writer error: %v", err)
|
||||||
ctl.allShutdown.Start()
|
ctl.allShutdown.Start()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if m, ok := <-ctl.sendCh; !ok {
|
if m, ok := <-ctl.sendCh; !ok {
|
||||||
ctl.conn.Info("control writer is closing")
|
xl.Info("control writer is closing")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||||
ctl.conn.Warn("write message to control connection error: %v", err)
|
xl.Warn("write message to control connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,24 +317,25 @@ func (ctl *Control) writer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) reader() {
|
func (ctl *Control) reader() {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.conn.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.conn.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
defer ctl.allShutdown.Start()
|
defer ctl.allShutdown.Start()
|
||||||
defer ctl.readerShutdown.Done()
|
defer ctl.readerShutdown.Done()
|
||||||
|
|
||||||
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
|
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
|
||||||
for {
|
for {
|
||||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
ctl.conn.Debug("control connection closed")
|
xl.Debug("control connection closed")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.conn.Warn("read error: %v", err)
|
xl.Warn("read error: %v", err)
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -311,10 +346,11 @@ func (ctl *Control) reader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) stoper() {
|
func (ctl *Control) stoper() {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.conn.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.conn.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -340,16 +376,12 @@ func (ctl *Control) stoper() {
|
|||||||
for _, pxy := range ctl.proxies {
|
for _, pxy := range ctl.proxies {
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
ctl.pxyManager.Del(pxy.GetName())
|
ctl.pxyManager.Del(pxy.GetName())
|
||||||
ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{
|
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
Name: pxy.GetName(),
|
|
||||||
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctl.allShutdown.Done()
|
ctl.allShutdown.Done()
|
||||||
ctl.conn.Info("client exit success")
|
xl.Info("client exit success")
|
||||||
|
metrics.Server.CloseClient()
|
||||||
ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// block until Control closed
|
// block until Control closed
|
||||||
@@ -358,10 +390,11 @@ func (ctl *Control) WaitClosed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) manager() {
|
func (ctl *Control) manager() {
|
||||||
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
ctl.conn.Error("panic error: %v", err)
|
xl.Error("panic error: %v", err)
|
||||||
ctl.conn.Error(string(debug.Stack()))
|
xl.Error(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -374,8 +407,8 @@ func (ctl *Control) manager() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-heartbeat.C:
|
case <-heartbeat.C:
|
||||||
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
|
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
|
||||||
ctl.conn.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case rawMsg, ok := <-ctl.readCh:
|
case rawMsg, ok := <-ctl.readCh:
|
||||||
@@ -385,29 +418,46 @@ func (ctl *Control) manager() {
|
|||||||
|
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.NewProxy:
|
case *msg.NewProxy:
|
||||||
|
content := &plugin.NewProxyContent{
|
||||||
|
User: plugin.UserInfo{
|
||||||
|
User: ctl.loginMsg.User,
|
||||||
|
Metas: ctl.loginMsg.Metas,
|
||||||
|
},
|
||||||
|
NewProxy: *m,
|
||||||
|
}
|
||||||
|
var remoteAddr string
|
||||||
|
retContent, err := ctl.pluginManager.NewProxy(content)
|
||||||
|
if err == nil {
|
||||||
|
m = &retContent.NewProxy
|
||||||
|
remoteAddr, err = ctl.RegisterProxy(m)
|
||||||
|
}
|
||||||
|
|
||||||
// register proxy in this control
|
// register proxy in this control
|
||||||
remoteAddr, err := ctl.RegisterProxy(m)
|
|
||||||
resp := &msg.NewProxyResp{
|
resp := &msg.NewProxyResp{
|
||||||
ProxyName: m.ProxyName,
|
ProxyName: m.ProxyName,
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Error = err.Error()
|
xl.Warn("new proxy [%s] error: %v", m.ProxyName, err)
|
||||||
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
|
resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", m.ProxyName), err, ctl.serverCfg.DetailedErrorsToClient)
|
||||||
} else {
|
} else {
|
||||||
resp.RemoteAddr = remoteAddr
|
resp.RemoteAddr = remoteAddr
|
||||||
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
|
xl.Info("new proxy [%s] success", m.ProxyName)
|
||||||
ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{
|
metrics.Server.NewProxy(m.ProxyName, m.ProxyType)
|
||||||
Name: m.ProxyName,
|
|
||||||
ProxyType: m.ProxyType,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
ctl.sendCh <- resp
|
ctl.sendCh <- resp
|
||||||
case *msg.CloseProxy:
|
case *msg.CloseProxy:
|
||||||
ctl.CloseProxy(m)
|
ctl.CloseProxy(m)
|
||||||
ctl.conn.Info("close proxy [%s] success", m.ProxyName)
|
xl.Info("close proxy [%s] success", m.ProxyName)
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
|
if err := ctl.authVerifier.VerifyPing(m); err != nil {
|
||||||
|
xl.Warn("received invalid ping: %v", err)
|
||||||
|
ctl.sendCh <- &msg.Pong{
|
||||||
|
Error: "invalid authentication in ping",
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
ctl.lastPing = time.Now()
|
ctl.lastPing = time.Now()
|
||||||
ctl.conn.Debug("receive heartbeat")
|
xl.Debug("receive heartbeat")
|
||||||
ctl.sendCh <- &msg.Pong{}
|
ctl.sendCh <- &msg.Pong{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,22 +467,22 @@ func (ctl *Control) manager() {
|
|||||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||||
var pxyConf config.ProxyConf
|
var pxyConf config.ProxyConf
|
||||||
// Load configures from NewProxy message and check.
|
// Load configures from NewProxy message and check.
|
||||||
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProxy will return a interface Proxy.
|
// NewProxy will return a interface Proxy.
|
||||||
// In fact it create different proxies by different proxy type, we just call run() here.
|
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||||
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
|
pxy, err := proxy.NewProxy(ctl.ctx, ctl.runId, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return remoteAddr, err
|
return remoteAddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check ports used number in each client
|
// Check ports used number in each client
|
||||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
if ctl.serverCfg.MaxPortsPerClient > 0 {
|
||||||
ctl.mu.Lock()
|
ctl.mu.Lock()
|
||||||
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
|
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
|
||||||
ctl.mu.Unlock()
|
ctl.mu.Unlock()
|
||||||
err = fmt.Errorf("exceed the max_ports_per_client")
|
err = fmt.Errorf("exceed the max_ports_per_client")
|
||||||
return
|
return
|
||||||
@@ -478,7 +528,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
if ctl.serverCfg.MaxPortsPerClient > 0 {
|
||||||
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||||
}
|
}
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
@@ -486,9 +536,6 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
|||||||
delete(ctl.proxies, closeMsg.ProxyName)
|
delete(ctl.proxies, closeMsg.ProxyName)
|
||||||
ctl.mu.Unlock()
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{
|
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
Name: pxy.GetName(),
|
|
||||||
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/fatedier/frp/models/nathole"
|
"github.com/fatedier/frp/models/nathole"
|
||||||
"github.com/fatedier/frp/server/group"
|
"github.com/fatedier/frp/server/group"
|
||||||
"github.com/fatedier/frp/server/ports"
|
"github.com/fatedier/frp/server/ports"
|
||||||
|
"github.com/fatedier/frp/utils/tcpmux"
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,4 +47,7 @@ type ResourceController struct {
|
|||||||
|
|
||||||
// Controller for nat hole connections
|
// Controller for nat hole connections
|
||||||
NatHoleController *nathole.NatHoleController
|
NatHoleController *nathole.NatHoleController
|
||||||
|
|
||||||
|
// TcpMux HTTP CONNECT multiplexer
|
||||||
|
TcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
@@ -55,7 +56,7 @@ func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListen
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
|
func (vm *VisitorManager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
|
||||||
useEncryption bool, useCompression bool) (err error) {
|
useEncryption bool, useCompression bool) (err error) {
|
||||||
|
|
||||||
vm.mu.RLock()
|
vm.mu.RLock()
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -36,9 +36,14 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
|||||||
// url router
|
// url router
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
|
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
||||||
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
|
// metrics
|
||||||
|
if svr.cfg.EnablePrometheus {
|
||||||
|
router.Handle("/metrics", promhttp.Handler())
|
||||||
|
}
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
router.HandleFunc("/api/serverinfo", svr.ApiServerInfo).Methods("GET")
|
router.HandleFunc("/api/serverinfo", svr.ApiServerInfo).Methods("GET")
|
||||||
router.HandleFunc("/api/proxy/{type}", svr.ApiProxyByType).Methods("GET")
|
router.HandleFunc("/api/proxy/{type}", svr.ApiProxyByType).Methods("GET")
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/consts"
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
"github.com/fatedier/frp/models/metrics/mem"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
|
||||||
@@ -63,19 +63,18 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
serverStats := mem.StatsCollector.GetServer()
|
||||||
serverStats := svr.statsCollector.GetServer()
|
|
||||||
svrResp := ServerInfoResp{
|
svrResp := ServerInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
BindPort: cfg.BindPort,
|
BindPort: svr.cfg.BindPort,
|
||||||
BindUdpPort: cfg.BindUdpPort,
|
BindUdpPort: svr.cfg.BindUdpPort,
|
||||||
VhostHttpPort: cfg.VhostHttpPort,
|
VhostHttpPort: svr.cfg.VhostHttpPort,
|
||||||
VhostHttpsPort: cfg.VhostHttpsPort,
|
VhostHttpsPort: svr.cfg.VhostHttpsPort,
|
||||||
KcpBindPort: cfg.KcpBindPort,
|
KcpBindPort: svr.cfg.KcpBindPort,
|
||||||
SubdomainHost: cfg.SubDomainHost,
|
SubdomainHost: svr.cfg.SubDomainHost,
|
||||||
MaxPoolCount: cfg.MaxPoolCount,
|
MaxPoolCount: svr.cfg.MaxPoolCount,
|
||||||
MaxPortsPerClient: cfg.MaxPortsPerClient,
|
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||||
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
|
||||||
|
|
||||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||||
@@ -97,6 +96,12 @@ type TcpOutConf struct {
|
|||||||
RemotePort int `json:"remote_port"`
|
RemotePort int `json:"remote_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TcpMuxOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
config.DomainConf
|
||||||
|
Multiplexer string `json:"multiplexer"`
|
||||||
|
}
|
||||||
|
|
||||||
type UdpOutConf struct {
|
type UdpOutConf struct {
|
||||||
BaseOutConf
|
BaseOutConf
|
||||||
RemotePort int `json:"remote_port"`
|
RemotePort int `json:"remote_port"`
|
||||||
@@ -126,6 +131,8 @@ func getConfByType(proxyType string) interface{} {
|
|||||||
switch proxyType {
|
switch proxyType {
|
||||||
case consts.TcpProxy:
|
case consts.TcpProxy:
|
||||||
return &TcpOutConf{}
|
return &TcpOutConf{}
|
||||||
|
case consts.TcpMuxProxy:
|
||||||
|
return &TcpMuxOutConf{}
|
||||||
case consts.UdpProxy:
|
case consts.UdpProxy:
|
||||||
return &UdpOutConf{}
|
return &UdpOutConf{}
|
||||||
case consts.HttpProxy:
|
case consts.HttpProxy:
|
||||||
@@ -180,7 +187,7 @@ func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||||
proxyStats := svr.statsCollector.GetProxiesByType(proxyType)
|
proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
|
||||||
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||||
for _, ps := range proxyStats {
|
for _, ps := range proxyStats {
|
||||||
proxyInfo := &ProxyStatsInfo{}
|
proxyInfo := &ProxyStatsInfo{}
|
||||||
@@ -250,7 +257,7 @@ func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||||
proxyInfo.Name = proxyName
|
proxyInfo.Name = proxyName
|
||||||
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||||
if ps == nil {
|
if ps == nil {
|
||||||
code = 404
|
code = 404
|
||||||
msg = "no proxy info found"
|
msg = "no proxy info found"
|
||||||
@@ -308,7 +315,7 @@ func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
trafficResp := GetProxyTrafficResp{}
|
trafficResp := GetProxyTrafficResp{}
|
||||||
trafficResp.Name = name
|
trafficResp.Name = name
|
||||||
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
|
||||||
|
|
||||||
if proxyTrafficInfo == nil {
|
if proxyTrafficInfo == nil {
|
||||||
res.Code = 404
|
res.Code = 404
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package group
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,7 +130,7 @@ func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) {
|
func (g *HTTPGroup) createConn(remoteAddr string) (net.Conn, error) {
|
||||||
var f vhost.CreateConnFunc
|
var f vhost.CreateConnFunc
|
||||||
newIndex := atomic.AddUint64(&g.index, 1)
|
newIndex := atomic.AddUint64(&g.index, 1)
|
||||||
|
|
||||||
|
|||||||
37
server/metrics/metrics.go
Normal file
37
server/metrics/metrics.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerMetrics interface {
|
||||||
|
NewClient()
|
||||||
|
CloseClient()
|
||||||
|
NewProxy(name string, proxyType string)
|
||||||
|
CloseProxy(name string, proxyType string)
|
||||||
|
OpenConnection(name string, proxyType string)
|
||||||
|
CloseConnection(name string, proxyType string)
|
||||||
|
AddTrafficIn(name string, proxyType string, trafficBytes int64)
|
||||||
|
AddTrafficOut(name string, proxyType string, trafficBytes int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Server ServerMetrics = noopServerMetrics{}
|
||||||
|
|
||||||
|
var registerMetrics sync.Once
|
||||||
|
|
||||||
|
func Register(m ServerMetrics) {
|
||||||
|
registerMetrics.Do(func() {
|
||||||
|
Server = m
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type noopServerMetrics struct{}
|
||||||
|
|
||||||
|
func (noopServerMetrics) NewClient() {}
|
||||||
|
func (noopServerMetrics) CloseClient() {}
|
||||||
|
func (noopServerMetrics) NewProxy(name string, proxyType string) {}
|
||||||
|
func (noopServerMetrics) CloseProxy(name string, proxyType string) {}
|
||||||
|
func (noopServerMetrics) OpenConnection(name string, proxyType string) {}
|
||||||
|
func (noopServerMetrics) CloseConnection(name string, proxyType string) {}
|
||||||
|
func (noopServerMetrics) AddTrafficIn(name string, proxyType string, trafficBytes int64) {}
|
||||||
|
func (noopServerMetrics) AddTrafficOut(name string, proxyType string, trafficBytes int64) {}
|
||||||
@@ -19,9 +19,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/server/stats"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
@@ -37,6 +36,7 @@ type HttpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
routeConfig := vhost.VhostRouteConfig{
|
routeConfig := vhost.VhostRouteConfig{
|
||||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||||
Headers: pxy.cfg.Headers,
|
Headers: pxy.cfg.Headers,
|
||||||
@@ -88,13 +88,13 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpPort)))
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
xl.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.cfg.SubDomain != "" {
|
if pxy.cfg.SubDomain != "" {
|
||||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
tmpDomain := routeConfig.Domain
|
tmpDomain := routeConfig.Domain
|
||||||
@@ -119,9 +119,9 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
|
||||||
|
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
xl.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remoteAddr = strings.Join(addrs, ",")
|
remoteAddr = strings.Join(addrs, ",")
|
||||||
@@ -132,10 +132,11 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
|||||||
return pxy.cfg
|
return pxy.cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
|
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
xl.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
||||||
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +148,9 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
|
|||||||
|
|
||||||
var rwc io.ReadWriteCloser = tmpConn
|
var rwc io.ReadWriteCloser = tmpConn
|
||||||
if pxy.cfg.UseEncryption {
|
if pxy.cfg.UseEncryption {
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
|
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,21 +159,16 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
|
|||||||
}
|
}
|
||||||
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||||
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||||
pxy.statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
|
metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
||||||
name := pxy.GetName()
|
name := pxy.GetName()
|
||||||
pxy.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseConnectionPayload{ProxyName: name})
|
proxyType := pxy.GetConf().GetBaseInfo().ProxyType
|
||||||
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
metrics.Server.CloseConnection(name, proxyType)
|
||||||
ProxyName: name,
|
metrics.Server.AddTrafficIn(name, proxyType, totalWrite)
|
||||||
TrafficBytes: totalWrite,
|
metrics.Server.AddTrafficOut(name, proxyType, totalRead)
|
||||||
})
|
|
||||||
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
|
||||||
ProxyName: name,
|
|
||||||
TrafficBytes: totalRead,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) Close() {
|
func (pxy *HttpProxy) Close() {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
@@ -29,6 +28,7 @@ type HttpsProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
routeConfig := &vhost.VhostRouteConfig{}
|
routeConfig := &vhost.VhostRouteConfig{}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -43,28 +43,26 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
routeConfig.Domain = domain
|
routeConfig.Domain = domain
|
||||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.AddLogPrefix(pxy.name)
|
xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.cfg.SubDomain != "" {
|
if pxy.cfg.SubDomain != "" {
|
||||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
|
||||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.AddLogPrefix(pxy.name)
|
xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||||
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
|||||||
@@ -15,58 +15,64 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
"github.com/fatedier/frp/server/stats"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetWorkConnFn func() (frpNet.Conn, error)
|
type GetWorkConnFn func() (net.Conn, error)
|
||||||
|
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
|
Context() context.Context
|
||||||
Run() (remoteAddr string, err error)
|
Run() (remoteAddr string, err error)
|
||||||
GetName() string
|
GetName() string
|
||||||
GetConf() config.ProxyConf
|
GetConf() config.ProxyConf
|
||||||
GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
|
GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
|
||||||
GetUsedPortsNum() int
|
GetUsedPortsNum() int
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
name string
|
name string
|
||||||
rc *controller.ResourceController
|
rc *controller.ResourceController
|
||||||
statsCollector stats.Collector
|
listeners []net.Listener
|
||||||
listeners []frpNet.Listener
|
usedPortsNum int
|
||||||
usedPortsNum int
|
poolCount int
|
||||||
poolCount int
|
getWorkConnFn GetWorkConnFn
|
||||||
getWorkConnFn GetWorkConnFn
|
serverCfg config.ServerCommonConf
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
log.Logger
|
xl *xlog.Logger
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) GetName() string {
|
func (pxy *BaseProxy) GetName() string {
|
||||||
return pxy.name
|
return pxy.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) Context() context.Context {
|
||||||
|
return pxy.ctx
|
||||||
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) GetUsedPortsNum() int {
|
func (pxy *BaseProxy) GetUsedPortsNum() int {
|
||||||
return pxy.usedPortsNum
|
return pxy.usedPortsNum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) Close() {
|
func (pxy *BaseProxy) Close() {
|
||||||
pxy.Info("proxy closing")
|
xl := xlog.FromContextSafe(pxy.ctx)
|
||||||
|
xl.Info("proxy closing")
|
||||||
for _, l := range pxy.listeners {
|
for _, l := range pxy.listeners {
|
||||||
l.Close()
|
l.Close()
|
||||||
}
|
}
|
||||||
@@ -74,15 +80,17 @@ func (pxy *BaseProxy) Close() {
|
|||||||
|
|
||||||
// GetWorkConnFromPool try to get a new work connections from pool
|
// GetWorkConnFromPool try to get a new work connections from pool
|
||||||
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
|
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
|
||||||
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error) {
|
||||||
|
xl := xlog.FromContextSafe(pxy.ctx)
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
for i := 0; i < pxy.poolCount+1; i++ {
|
for i := 0; i < pxy.poolCount+1; i++ {
|
||||||
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
||||||
pxy.Warn("failed to get work connection: %v", err)
|
xl.Warn("failed to get work connection: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
xl.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||||
workConn.AddLogPrefix(pxy.GetName())
|
xl.Spawn().AppendPrefix(pxy.GetName())
|
||||||
|
workConn = frpNet.NewContextConn(workConn, pxy.ctx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
srcAddr string
|
srcAddr string
|
||||||
@@ -107,9 +115,10 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
|
|||||||
SrcPort: uint16(srcPort),
|
SrcPort: uint16(srcPort),
|
||||||
DstAddr: dstAddr,
|
DstAddr: dstAddr,
|
||||||
DstPort: uint16(dstPort),
|
DstPort: uint16(dstPort),
|
||||||
|
Error: "",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
xl.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -117,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("try to get work connection failed in the end")
|
xl.Error("try to get work connection failed in the end")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -126,35 +135,38 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
|
|||||||
// startListenHandler start a goroutine handler for each listener.
|
// startListenHandler start a goroutine handler for each listener.
|
||||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
// 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.
|
// 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)) {
|
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn, config.ServerCommonConf)) {
|
||||||
|
xl := xlog.FromContextSafe(pxy.ctx)
|
||||||
for _, listener := range pxy.listeners {
|
for _, listener := range pxy.listeners {
|
||||||
go func(l frpNet.Listener) {
|
go func(l net.Listener) {
|
||||||
for {
|
for {
|
||||||
// block
|
// block
|
||||||
// if listener is closed, err returned
|
// if listener is closed, err returned
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Info("listener is closed")
|
xl.Info("listener is closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
xl.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||||
go handler(p, c, pxy.statsCollector)
|
go handler(p, c, pxy.serverCfg)
|
||||||
}
|
}
|
||||||
}(listener)
|
}(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
|
func NewProxy(ctx context.Context, runId string, rc *controller.ResourceController, poolCount int,
|
||||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
|
||||||
|
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
|
||||||
basePxy := BaseProxy{
|
basePxy := BaseProxy{
|
||||||
name: pxyConf.GetBaseInfo().ProxyName,
|
name: pxyConf.GetBaseInfo().ProxyName,
|
||||||
rc: rc,
|
rc: rc,
|
||||||
statsCollector: statsCollector,
|
listeners: make([]net.Listener, 0),
|
||||||
listeners: make([]frpNet.Listener, 0),
|
poolCount: poolCount,
|
||||||
poolCount: poolCount,
|
getWorkConnFn: getWorkConnFn,
|
||||||
getWorkConnFn: getWorkConnFn,
|
serverCfg: serverCfg,
|
||||||
Logger: log.NewPrefixLogger(runId),
|
xl: xl,
|
||||||
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
@@ -163,6 +175,11 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
|||||||
BaseProxy: &basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.TcpMuxProxyConf:
|
||||||
|
pxy = &TcpMuxProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
pxy = &HttpProxy{
|
pxy = &HttpProxy{
|
||||||
BaseProxy: &basePxy,
|
BaseProxy: &basePxy,
|
||||||
@@ -192,13 +209,13 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
|||||||
default:
|
default:
|
||||||
return pxy, fmt.Errorf("proxy type not support")
|
return pxy, fmt.Errorf("proxy type not support")
|
||||||
}
|
}
|
||||||
pxy.AddLogPrefix(pxy.GetName())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleUserTcpConnection is used for incoming tcp user connections.
|
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||||
// It can be used for tcp, http, https type.
|
// It can be used for tcp, http, https type.
|
||||||
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
|
func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, serverCfg config.ServerCommonConf) {
|
||||||
|
xl := xlog.FromContextSafe(pxy.Context())
|
||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
@@ -210,31 +227,28 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
|
|||||||
|
|
||||||
var local io.ReadWriteCloser = workConn
|
var local io.ReadWriteCloser = workConn
|
||||||
cfg := pxy.GetConf().GetBaseInfo()
|
cfg := pxy.GetConf().GetBaseInfo()
|
||||||
|
xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.UseEncryption, cfg.UseCompression)
|
||||||
if cfg.UseEncryption {
|
if cfg.UseEncryption {
|
||||||
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
|
local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.UseCompression {
|
if cfg.UseCompression {
|
||||||
local = frpIo.WithCompression(local)
|
local = frpIo.WithCompression(local)
|
||||||
}
|
}
|
||||||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
xl.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())
|
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||||
|
|
||||||
statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
|
name := pxy.GetName()
|
||||||
|
proxyType := pxy.GetConf().GetBaseInfo().ProxyType
|
||||||
|
metrics.Server.OpenConnection(name, proxyType)
|
||||||
inCount, outCount := frpIo.Join(local, userConn)
|
inCount, outCount := frpIo.Join(local, userConn)
|
||||||
statsCollector.Mark(stats.TypeCloseConnection, &stats.CloseConnectionPayload{ProxyName: pxy.GetName()})
|
metrics.Server.CloseConnection(name, proxyType)
|
||||||
statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
metrics.Server.AddTrafficIn(name, proxyType, inCount)
|
||||||
ProxyName: pxy.GetName(),
|
metrics.Server.AddTrafficOut(name, proxyType, outCount)
|
||||||
TrafficBytes: inCount,
|
xl.Debug("join connections closed")
|
||||||
})
|
|
||||||
statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
|
||||||
ProxyName: pxy.GetName(),
|
|
||||||
TrafficBytes: outCount,
|
|
||||||
})
|
|
||||||
pxy.Debug("join connections closed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyManager struct {
|
type ProxyManager struct {
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ type StcpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
|
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
listener.AddLogPrefix(pxy.name)
|
|
||||||
pxy.listeners = append(pxy.listeners, listener)
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
pxy.Info("stcp proxy custom listen success")
|
xl.Info("stcp proxy custom listen success")
|
||||||
|
|
||||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ package proxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TcpProxy struct {
|
type TcpProxy struct {
|
||||||
@@ -30,8 +29,9 @@ type TcpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
if pxy.cfg.Group != "" {
|
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)
|
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
@@ -42,10 +42,8 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
pxy.realPort = realPort
|
pxy.realPort = realPort
|
||||||
listener := frpNet.WrapLogListener(l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
listener.AddLogPrefix(pxy.name)
|
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
|
||||||
pxy.listeners = append(pxy.listeners, listener)
|
|
||||||
pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
|
|
||||||
} else {
|
} else {
|
||||||
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,14 +54,13 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
|
listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
listener.AddLogPrefix(pxy.name)
|
|
||||||
pxy.listeners = append(pxy.listeners, listener)
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.cfg.RemotePort = pxy.realPort
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
|
|||||||
95
server/proxy/tcpmux.go
Normal file
95
server/proxy/tcpmux.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TcpMuxProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.TcpMuxProxyConf
|
||||||
|
|
||||||
|
realPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) httpConnectListen(domain string, addrs []string) ([]string, error) {
|
||||||
|
routeConfig := &vhost.VhostRouteConfig{
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
l, err := pxy.rc.TcpMuxHttpConnectMuxer.Listen(pxy.ctx, routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", routeConfig.Domain)
|
||||||
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
|
return append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TcpMuxHttpConnectPort)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) httpConnectRun() (remoteAddr string, err error) {
|
||||||
|
addrs := make([]string, 0)
|
||||||
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err = pxy.httpConnectListen(domain, addrs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pxy.cfg.SubDomain != "" {
|
||||||
|
addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost, addrs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
remoteAddr = strings.Join(addrs, ",")
|
||||||
|
return remoteAddr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) Run() (remoteAddr string, err error) {
|
||||||
|
switch pxy.cfg.Multiplexer {
|
||||||
|
case consts.HttpConnectTcpMultiplexer:
|
||||||
|
remoteAddr, err = pxy.httpConnectRun()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown multiplexer [%s]", pxy.cfg.Multiplexer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
return remoteAddr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpMuxProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
if pxy.cfg.Group == "" {
|
||||||
|
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,11 +20,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/models/proto/udp"
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
"github.com/fatedier/frp/server/stats"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
@@ -55,6 +54,7 @@ type UdpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -67,7 +67,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
|
|
||||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||||
pxy.cfg.RemotePort = pxy.realPort
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
|
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
@@ -75,10 +75,10 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
udpConn, errRet := net.ListenUDP("udp", addr)
|
udpConn, errRet := net.ListenUDP("udp", addr)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
pxy.Warn("listen udp port error: %v", err)
|
xl.Warn("listen udp port error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
|
xl.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||||
|
|
||||||
pxy.udpConn = udpConn
|
pxy.udpConn = udpConn
|
||||||
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
|
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
|
||||||
@@ -92,11 +92,11 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
rawMsg msg.Message
|
rawMsg msg.Message
|
||||||
errRet error
|
errRet error
|
||||||
)
|
)
|
||||||
pxy.Trace("loop waiting message from udp workConn")
|
xl.Trace("loop waiting message from udp workConn")
|
||||||
// client will send heartbeat in workConn for keeping alive
|
// client will send heartbeat in workConn for keeping alive
|
||||||
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
|
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
|
||||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
// notify proxy to start a new work connection
|
// notify proxy to start a new work connection
|
||||||
// ignore error here, it means the proxy is closed
|
// ignore error here, it means the proxy is closed
|
||||||
@@ -108,19 +108,20 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
conn.SetReadDeadline(time.Time{})
|
conn.SetReadDeadline(time.Time{})
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
pxy.Trace("udp work conn get ping message")
|
xl.Trace("udp work conn get ping message")
|
||||||
continue
|
continue
|
||||||
case *msg.UdpPacket:
|
case *msg.UdpPacket:
|
||||||
if errRet := errors.PanicToError(func() {
|
if errRet := errors.PanicToError(func() {
|
||||||
pxy.Trace("get udp message from workConn: %s", m.Content)
|
xl.Trace("get udp message from workConn: %s", m.Content)
|
||||||
pxy.readCh <- m
|
pxy.readCh <- m
|
||||||
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
metrics.Server.AddTrafficOut(
|
||||||
ProxyName: pxy.GetName(),
|
pxy.GetName(),
|
||||||
TrafficBytes: int64(len(m.Content)),
|
pxy.GetConf().GetBaseInfo().ProxyType,
|
||||||
})
|
int64(len(m.Content)),
|
||||||
|
)
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
pxy.Info("reader goroutine for udp work connection closed")
|
xl.Info("reader goroutine for udp work connection closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,23 +135,24 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
select {
|
select {
|
||||||
case udpMsg, ok := <-pxy.sendCh:
|
case udpMsg, ok := <-pxy.sendCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
pxy.Info("sender goroutine for udp work connection closed")
|
xl.Info("sender goroutine for udp work connection closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||||
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
|
xl.Info("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
|
xl.Trace("send message to udp workConn: %s", udpMsg.Content)
|
||||||
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
metrics.Server.AddTrafficIn(
|
||||||
ProxyName: pxy.GetName(),
|
pxy.GetName(),
|
||||||
TrafficBytes: int64(len(udpMsg.Content)),
|
pxy.GetConf().GetBaseInfo().ProxyType,
|
||||||
})
|
int64(len(udpMsg.Content)),
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
pxy.Info("sender goroutine for udp work connection closed")
|
xl.Info("sender goroutine for udp work connection closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ type XtcpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
|
|
||||||
if pxy.rc.NatHoleController == nil {
|
if pxy.rc.NatHoleController == nil {
|
||||||
pxy.Error("udp port for xtcp is not specified.")
|
xl.Error("udp port for xtcp is not specified.")
|
||||||
err = fmt.Errorf("xtcp is not supported in frps")
|
err = fmt.Errorf("xtcp is not supported in frps")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,7 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
}
|
}
|
||||||
errRet = msg.WriteMsg(workConn, m)
|
errRet = msg.WriteMsg(workConn, m)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
pxy.Warn("write nat hole sid package error, %v", errRet)
|
xl.Warn("write nat hole sid package error, %v", errRet)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -61,12 +63,12 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
go func() {
|
go func() {
|
||||||
raw, errRet := msg.ReadMsg(workConn)
|
raw, errRet := msg.ReadMsg(workConn)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
pxy.Warn("read nat hole client ok package error: %v", errRet)
|
xl.Warn("read nat hole client ok package error: %v", errRet)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
||||||
pxy.Warn("read nat hole client ok package format error")
|
xl.Warn("read nat hole client ok package format error")
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@@ -29,46 +30,50 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/models/auth"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
modelmetrics "github.com/fatedier/frp/models/metrics"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/models/nathole"
|
"github.com/fatedier/frp/models/nathole"
|
||||||
|
plugin "github.com/fatedier/frp/models/plugin/server"
|
||||||
"github.com/fatedier/frp/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
"github.com/fatedier/frp/server/group"
|
"github.com/fatedier/frp/server/group"
|
||||||
|
"github.com/fatedier/frp/server/metrics"
|
||||||
"github.com/fatedier/frp/server/ports"
|
"github.com/fatedier/frp/server/ports"
|
||||||
"github.com/fatedier/frp/server/proxy"
|
"github.com/fatedier/frp/server/proxy"
|
||||||
"github.com/fatedier/frp/server/stats"
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/tcpmux"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
"github.com/fatedier/golib/net/mux"
|
"github.com/fatedier/golib/net/mux"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
connReadTimeout time.Duration = 10 * time.Second
|
connReadTimeout time.Duration = 10 * time.Second
|
||||||
|
vhostReadWriteTimeout time.Duration = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServerService *Service
|
|
||||||
|
|
||||||
// Server service
|
// Server service
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// Dispatch connections to different handlers listen on same port
|
// Dispatch connections to different handlers listen on same port
|
||||||
muxer *mux.Mux
|
muxer *mux.Mux
|
||||||
|
|
||||||
// Accept connections from client
|
// Accept connections from client
|
||||||
listener frpNet.Listener
|
listener net.Listener
|
||||||
|
|
||||||
// Accept connections using kcp
|
// Accept connections using kcp
|
||||||
kcpListener frpNet.Listener
|
kcpListener net.Listener
|
||||||
|
|
||||||
// Accept connections using websocket
|
// Accept connections using websocket
|
||||||
websocketListener frpNet.Listener
|
websocketListener net.Listener
|
||||||
|
|
||||||
// Accept frp tls connections
|
// Accept frp tls connections
|
||||||
tlsListener frpNet.Listener
|
tlsListener net.Listener
|
||||||
|
|
||||||
// Manage all controllers
|
// Manage all controllers
|
||||||
ctlManager *ControlManager
|
ctlManager *ControlManager
|
||||||
@@ -76,30 +81,43 @@ type Service struct {
|
|||||||
// Manage all proxies
|
// Manage all proxies
|
||||||
pxyManager *proxy.ProxyManager
|
pxyManager *proxy.ProxyManager
|
||||||
|
|
||||||
|
// Manage all plugins
|
||||||
|
pluginManager *plugin.Manager
|
||||||
|
|
||||||
// HTTP vhost router
|
// HTTP vhost router
|
||||||
httpVhostRouter *vhost.VhostRouters
|
httpVhostRouter *vhost.VhostRouters
|
||||||
|
|
||||||
// All resource managers and controllers
|
// All resource managers and controllers
|
||||||
rc *controller.ResourceController
|
rc *controller.ResourceController
|
||||||
|
|
||||||
// stats collector to store server and proxies stats info
|
// Verifies authentication based on selected method
|
||||||
statsCollector stats.Collector
|
authVerifier auth.Verifier
|
||||||
|
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
|
|
||||||
|
cfg config.ServerCommonConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() (svr *Service, err error) {
|
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
ctlManager: NewControlManager(),
|
ctlManager: NewControlManager(),
|
||||||
pxyManager: proxy.NewProxyManager(),
|
pxyManager: proxy.NewProxyManager(),
|
||||||
|
pluginManager: plugin.NewManager(),
|
||||||
rc: &controller.ResourceController{
|
rc: &controller.ResourceController{
|
||||||
VisitorManager: controller.NewVisitorManager(),
|
VisitorManager: controller.NewVisitorManager(),
|
||||||
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||||
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||||
},
|
},
|
||||||
httpVhostRouter: vhost.NewVhostRouters(),
|
httpVhostRouter: vhost.NewVhostRouters(),
|
||||||
|
authVerifier: auth.NewAuthVerifier(cfg.AuthServerConfig),
|
||||||
tlsConfig: generateTLSConfig(),
|
tlsConfig: generateTLSConfig(),
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init all plugins
|
||||||
|
for name, options := range cfg.HTTPPlugins {
|
||||||
|
svr.pluginManager.Register(plugin.NewHTTPPluginOptions(options))
|
||||||
|
log.Info("plugin [%s] has been registered", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init group controller
|
// Init group controller
|
||||||
@@ -108,13 +126,6 @@ func NewService() (svr *Service, err error) {
|
|||||||
// Init HTTP group controller
|
// Init HTTP group controller
|
||||||
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
||||||
|
|
||||||
// Init assets
|
|
||||||
err = assets.Load(cfg.AssetsDir)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Load assets error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init 404 not found page
|
// Init 404 not found page
|
||||||
vhost.NotFoundPagePath = cfg.Custom404Page
|
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||||
|
|
||||||
@@ -142,7 +153,7 @@ func NewService() (svr *Service, err error) {
|
|||||||
go svr.muxer.Serve()
|
go svr.muxer.Serve()
|
||||||
ln = svr.muxer.DefaultListener()
|
ln = svr.muxer.DefaultListener()
|
||||||
|
|
||||||
svr.listener = frpNet.WrapLogListener(ln)
|
svr.listener = ln
|
||||||
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
|
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
|
||||||
|
|
||||||
// Listen for accepting connections from client using kcp protocol.
|
// Listen for accepting connections from client using kcp protocol.
|
||||||
@@ -201,7 +212,7 @@ func NewService() (svr *Service, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(frpNet.WrapLogListener(l), 30*time.Second)
|
svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, vhostReadWriteTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
||||||
return
|
return
|
||||||
@@ -209,11 +220,27 @@ func NewService() (svr *Service, err error) {
|
|||||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create tcpmux httpconnect multiplexer.
|
||||||
|
if cfg.TcpMuxHttpConnectPort > 0 {
|
||||||
|
var l net.Listener
|
||||||
|
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TcpMuxHttpConnectPort))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create server listener error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.rc.TcpMuxHttpConnectMuxer, err = tcpmux.NewHttpConnectTcpMuxer(l, vhostReadWriteTimeout)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TcpMuxHttpConnectPort)
|
||||||
|
}
|
||||||
|
|
||||||
// frp tls listener
|
// frp tls listener
|
||||||
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
|
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||||
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||||
})
|
})
|
||||||
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
|
|
||||||
|
|
||||||
// Create nat hole controller.
|
// Create nat hole controller.
|
||||||
if cfg.BindUdpPort > 0 {
|
if cfg.BindUdpPort > 0 {
|
||||||
@@ -231,6 +258,13 @@ func NewService() (svr *Service, err error) {
|
|||||||
var statsEnable bool
|
var statsEnable bool
|
||||||
// Create dashboard web server.
|
// Create dashboard web server.
|
||||||
if cfg.DashboardPort > 0 {
|
if cfg.DashboardPort > 0 {
|
||||||
|
// Init dashboard assets
|
||||||
|
err = assets.Load(cfg.AssetsDir)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Load assets error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||||
@@ -239,8 +273,12 @@ func NewService() (svr *Service, err error) {
|
|||||||
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
statsEnable = true
|
statsEnable = true
|
||||||
}
|
}
|
||||||
|
if statsEnable {
|
||||||
svr.statsCollector = stats.NewInternalCollector(statsEnable)
|
modelmetrics.EnableMem()
|
||||||
|
if cfg.EnablePrometheus {
|
||||||
|
modelmetrics.EnablePrometheus()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +286,7 @@ func (svr *Service) Run() {
|
|||||||
if svr.rc.NatHoleController != nil {
|
if svr.rc.NatHoleController != nil {
|
||||||
go svr.rc.NatHoleController.Run()
|
go svr.rc.NatHoleController.Run()
|
||||||
}
|
}
|
||||||
if g.GlbServerCfg.KcpBindPort > 0 {
|
if svr.cfg.KcpBindPort > 0 {
|
||||||
go svr.HandleListener(svr.kcpListener)
|
go svr.HandleListener(svr.kcpListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +296,7 @@ func (svr *Service) Run() {
|
|||||||
svr.HandleListener(svr.listener)
|
svr.HandleListener(svr.listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) HandleListener(l frpNet.Listener) {
|
func (svr *Service) HandleListener(l net.Listener) {
|
||||||
// Listen for incoming connections from client.
|
// Listen for incoming connections from client.
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
@@ -266,10 +304,13 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
log.Warn("Listener for incoming connections from client closed")
|
log.Warn("Listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// inject xlog object into net.Conn context
|
||||||
|
xl := xlog.New()
|
||||||
|
c = frpNet.NewContextConn(c, xlog.NewContext(context.Background(), xl))
|
||||||
|
|
||||||
log.Trace("start check TLS connection...")
|
log.Trace("start check TLS connection...")
|
||||||
originConn := c
|
originConn := c
|
||||||
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, connReadTimeout)
|
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TlsOnly, connReadTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||||
originConn.Close()
|
originConn.Close()
|
||||||
@@ -278,8 +319,8 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
log.Trace("success check TLS connection")
|
log.Trace("success check TLS connection")
|
||||||
|
|
||||||
// Start a new goroutine for dealing connections.
|
// Start a new goroutine for dealing connections.
|
||||||
go func(frpConn frpNet.Conn) {
|
go func(frpConn net.Conn) {
|
||||||
dealFn := func(conn frpNet.Conn) {
|
dealFn := func(conn net.Conn) {
|
||||||
var rawMsg msg.Message
|
var rawMsg msg.Message
|
||||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||||
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
||||||
@@ -291,25 +332,36 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
|
|
||||||
switch m := rawMsg.(type) {
|
switch m := rawMsg.(type) {
|
||||||
case *msg.Login:
|
case *msg.Login:
|
||||||
err = svr.RegisterControl(conn, m)
|
// server plugin hook
|
||||||
|
content := &plugin.LoginContent{
|
||||||
|
Login: *m,
|
||||||
|
}
|
||||||
|
retContent, err := svr.pluginManager.Login(content)
|
||||||
|
if err == nil {
|
||||||
|
m = &retContent.Login
|
||||||
|
err = svr.RegisterControl(conn, m)
|
||||||
|
}
|
||||||
|
|
||||||
// If login failed, send error message there.
|
// If login failed, send error message there.
|
||||||
// Otherwise send success message in control's work goroutine.
|
// Otherwise send success message in control's work goroutine.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Warn("%v", err)
|
xl.Warn("register control error: %v", err)
|
||||||
msg.WriteMsg(conn, &msg.LoginResp{
|
msg.WriteMsg(conn, &msg.LoginResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
Error: err.Error(),
|
Error: util.GenerateResponseErrorString("register control error", err, svr.cfg.DetailedErrorsToClient),
|
||||||
})
|
})
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
case *msg.NewWorkConn:
|
case *msg.NewWorkConn:
|
||||||
svr.RegisterWorkConn(conn, m)
|
if err := svr.RegisterWorkConn(conn, m); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
case *msg.NewVisitorConn:
|
case *msg.NewVisitorConn:
|
||||||
if err = svr.RegisterVisitorConn(conn, m); err != nil {
|
if err = svr.RegisterVisitorConn(conn, m); err != nil {
|
||||||
conn.Warn("%v", err)
|
xl.Warn("register visitor conn error: %v", err)
|
||||||
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
ProxyName: m.ProxyName,
|
ProxyName: m.ProxyName,
|
||||||
Error: err.Error(),
|
Error: util.GenerateResponseErrorString("register visitor conn error", err, svr.cfg.DetailedErrorsToClient),
|
||||||
})
|
})
|
||||||
conn.Close()
|
conn.Close()
|
||||||
} else {
|
} else {
|
||||||
@@ -324,7 +376,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.GlbServerCfg.TcpMux {
|
if svr.cfg.TcpMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||||
fmuxCfg.LogOutput = ioutil.Discard
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
@@ -342,8 +394,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
session.Close()
|
session.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wrapConn := frpNet.WrapConn(stream)
|
go dealFn(stream)
|
||||||
go dealFn(wrapConn)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dealFn(frpConn)
|
dealFn(frpConn)
|
||||||
@@ -352,22 +403,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (err error) {
|
func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err error) {
|
||||||
ctlConn.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
|
|
||||||
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
|
|
||||||
|
|
||||||
// Check client version.
|
|
||||||
if ok, msg := version.Compat(loginMsg.Version); !ok {
|
|
||||||
err = fmt.Errorf("%s", msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check auth.
|
|
||||||
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
|
||||||
err = fmt.Errorf("authorization failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If client's RunId is empty, it's a new client, we just create a new controller.
|
// If client's RunId is empty, it's a new client, we just create a new controller.
|
||||||
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
|
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
|
||||||
if loginMsg.RunId == "" {
|
if loginMsg.RunId == "" {
|
||||||
@@ -377,17 +413,33 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg)
|
ctx := frpNet.NewContextFromConn(ctlConn)
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
xl.AppendPrefix(loginMsg.RunId)
|
||||||
|
ctx = xlog.NewContext(ctx, xl)
|
||||||
|
xl.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
|
||||||
|
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
|
||||||
|
|
||||||
|
// Check client version.
|
||||||
|
if ok, msg := version.Compat(loginMsg.Version); !ok {
|
||||||
|
err = fmt.Errorf("%s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check auth.
|
||||||
|
if err = svr.authVerifier.VerifyLogin(loginMsg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, svr.authVerifier, ctlConn, loginMsg, svr.cfg)
|
||||||
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
|
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
|
||||||
oldCtl.allShutdown.WaitDone()
|
oldCtl.allShutdown.WaitDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctlConn.AddLogPrefix(loginMsg.RunId)
|
|
||||||
ctl.Start()
|
ctl.Start()
|
||||||
|
|
||||||
// for statistics
|
// for statistics
|
||||||
svr.statsCollector.Mark(stats.TypeNewClient, &stats.NewClientPayload{})
|
metrics.Server.NewClient()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// block until control closed
|
// block until control closed
|
||||||
@@ -398,17 +450,25 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterWorkConn register a new work connection to control and proxies need it.
|
// RegisterWorkConn register a new work connection to control and proxies need it.
|
||||||
func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkConn) {
|
func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) error {
|
||||||
|
xl := frpNet.NewLogFromConn(workConn)
|
||||||
ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
|
ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
|
||||||
if !exist {
|
if !exist {
|
||||||
workConn.Warn("No client control found for run id [%s]", newMsg.RunId)
|
xl.Warn("No client control found for run id [%s]", newMsg.RunId)
|
||||||
return
|
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunId)
|
||||||
}
|
}
|
||||||
ctl.RegisterWorkConn(workConn)
|
// Check auth.
|
||||||
return
|
if err := svr.authVerifier.VerifyNewWorkConn(newMsg); err != nil {
|
||||||
|
xl.Warn("Invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId)
|
||||||
|
msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||||
|
Error: "invalid authentication in NewWorkConn",
|
||||||
|
})
|
||||||
|
return fmt.Errorf("invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId)
|
||||||
|
}
|
||||||
|
return ctl.RegisterWorkConn(workConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
|
func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error {
|
||||||
return svr.rc.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)
|
newMsg.UseEncryption, newMsg.UseCompression)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,277 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
72
tests/ci/auth_test.go
Normal file
72
tests/ci/auth_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package ci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/tests/config"
|
||||||
|
"github.com/fatedier/frp/tests/consts"
|
||||||
|
"github.com/fatedier/frp/tests/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FRPS_TOKEN_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
authentication_method = token
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TOKEN_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
authentication_method = token
|
||||||
|
token = 123456
|
||||||
|
protocol = tcp
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTcpWithTokenAuthentication(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TOKEN_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TOKEN_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpcCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||||
|
err = frpsProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpsProcess.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// test tcp
|
||||||
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
|
}
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
server_addr = 127.0.0.1
|
server_addr = 127.0.0.1
|
||||||
server_port = 10700
|
server_port = 10700
|
||||||
log_file = console
|
log_file = console
|
||||||
# debug, info, warn, error
|
log_level = trace
|
||||||
log_level = debug
|
|
||||||
token = 123456
|
token = 123456
|
||||||
admin_port = 10600
|
admin_port = 10600
|
||||||
admin_user = abc
|
admin_user = abc
|
||||||
@@ -127,6 +126,13 @@ custom_domains = test6.frp.com
|
|||||||
host_header_rewrite = test6.frp.com
|
host_header_rewrite = test6.frp.com
|
||||||
header_X-From-Where = frp
|
header_X-From-Where = frp
|
||||||
|
|
||||||
|
[tcpmuxhttpconnect]
|
||||||
|
type = tcpmux
|
||||||
|
multiplexer = httpconnect
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10701
|
||||||
|
custom_domains = tunnel1
|
||||||
|
|
||||||
[wildcard_http]
|
[wildcard_http]
|
||||||
type = http
|
type = http
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
bind_addr = 0.0.0.0
|
bind_addr = 0.0.0.0
|
||||||
bind_port = 10700
|
bind_port = 10700
|
||||||
vhost_http_port = 10804
|
vhost_http_port = 10804
|
||||||
log_file = console
|
tcpmux_httpconnect_port = 10806
|
||||||
log_level = debug
|
log_level = trace
|
||||||
token = 123456
|
token = 123456
|
||||||
allow_ports = 10000-20000,20002,30000-50000
|
allow_ports = 10000-20000,20002,30000-50000
|
||||||
subdomain_host = sub.com
|
subdomain_host = sub.com
|
||||||
|
|||||||
@@ -67,6 +67,20 @@ custom_domains = test2.com
|
|||||||
health_check_type = http
|
health_check_type = http
|
||||||
health_check_interval_s = 1
|
health_check_interval_s = 1
|
||||||
health_check_url = /health
|
health_check_url = /health
|
||||||
|
|
||||||
|
[http3]
|
||||||
|
type = http
|
||||||
|
local_port = 15005
|
||||||
|
custom_domains = test.balancing.com
|
||||||
|
group = test-balancing
|
||||||
|
group_key = 123
|
||||||
|
|
||||||
|
[http4]
|
||||||
|
type = http
|
||||||
|
local_port = 15006
|
||||||
|
custom_domains = test.balancing.com
|
||||||
|
group = test-balancing
|
||||||
|
group_key = 123
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestHealthCheck(t *testing.T) {
|
func TestHealthCheck(t *testing.T) {
|
||||||
@@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
defer httpSvc2.Stop()
|
defer httpSvc2.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpSvc3 := mock.NewHttpServer(15005, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("http3"))
|
||||||
|
})
|
||||||
|
err = httpSvc3.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer httpSvc3.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSvc4 := mock.NewHttpServer(15006, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("http4"))
|
||||||
|
})
|
||||||
|
err = httpSvc4.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer httpSvc4.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// ****** start frps and frpc ******
|
// ****** start frps and frpc ******
|
||||||
@@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
assert.Equal("http2", body)
|
assert.Equal("http2", body)
|
||||||
|
|
||||||
|
// ****** load balancing type http ******
|
||||||
|
result = make([]string, 0)
|
||||||
|
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(200, code)
|
||||||
|
result = append(result, body)
|
||||||
|
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(200, code)
|
||||||
|
result = append(result, body)
|
||||||
|
|
||||||
|
assert.Contains(result, "http3")
|
||||||
|
assert.Contains(result, "http4")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,6 +212,17 @@ func TestHttp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTcpMux(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
conn, err := gnet.DialTcpByProxy(fmt.Sprintf("http://%s:%d", "127.0.0.1", consts.TEST_TCP_MUX_FRP_PORT), "tunnel1")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
res, err := util.SendTcpMsgByConn(conn, consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWebSocket(t *testing.T) {
|
func TestWebSocket(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
|||||||
@@ -186,3 +186,95 @@ func TestTLSOverWebsocket(t *testing.T) {
|
|||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FRPS_TLS_ONLY_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
tls_only = true
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_ONLY_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = tcp
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_ONLY_NO_TLS_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = tcp
|
||||||
|
tls_enable = false
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20802
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTlsOnlyOverTCP(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_ONLY_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcWithTlsCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_ONLY_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpcWithTlsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||||
|
err = frpsProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpsProcess.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcessWithTls := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcWithTlsCfgPath})
|
||||||
|
err = frpcProcessWithTls.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcessWithTls.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// test tcp over tls
|
||||||
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
|
frpcProcessWithTls.Stop()
|
||||||
|
|
||||||
|
frpcWithoutTlsCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_ONLY_NO_TLS_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpcWithTlsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcProcessWithoutTls := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcWithoutTlsCfgPath})
|
||||||
|
err = frpcProcessWithoutTls.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcessWithoutTls.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// test tcp without tls
|
||||||
|
_, err = util.SendTcpMsg("127.0.0.1:20802", consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ var (
|
|||||||
TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR
|
TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR
|
||||||
TEST_HTTP_BAR_STR string = "http bar string: " + TEST_STR
|
TEST_HTTP_BAR_STR string = "http bar string: " + TEST_STR
|
||||||
|
|
||||||
|
TEST_TCP_MUX_FRP_PORT int = 10806
|
||||||
|
|
||||||
TEST_STCP_FRP_PORT int = 10805
|
TEST_STCP_FRP_PORT int = 10805
|
||||||
TEST_STCP_EC_FRP_PORT int = 10905
|
TEST_STCP_EC_FRP_PORT int = 10905
|
||||||
TEST_STCP_ECHO_STR string = "stcp type:" + TEST_STR
|
TEST_STCP_ECHO_STR string = "stcp type:" + TEST_STR
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type EchoServer struct {
|
type EchoServer struct {
|
||||||
l frpNet.Listener
|
l net.Listener
|
||||||
|
|
||||||
port int
|
port int
|
||||||
repeatedNum int
|
repeatedNum int
|
||||||
@@ -30,7 +30,7 @@ func NewEchoServer(port int, repeatedNum int, specifyStr string) *EchoServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (es *EchoServer) Start() error {
|
func (es *EchoServer) Start() error {
|
||||||
l, err := frpNet.ListenTcp("127.0.0.1", es.port)
|
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", es.port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("echo server listen error: %v\n", err)
|
fmt.Printf("echo server listen error: %v\n", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetProxyStatus(statusAddr string, user string, passwd string, name string) (status *client.ProxyStatusResp, err error) {
|
func GetProxyStatus(statusAddr string, user string, passwd string, name string) (status *client.ProxyStatusResp, err error) {
|
||||||
@@ -98,7 +97,7 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SendTcpMsg(addr string, msg string) (res string, err error) {
|
func SendTcpMsg(addr string, msg string) (res string, err error) {
|
||||||
c, err := frpNet.ConnectTcpServer(addr)
|
c, err := net.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("connect to tcp server error: %v", err)
|
err = fmt.Errorf("connect to tcp server error: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
51
utils/limit/reader.go
Normal file
51
utils/limit/reader.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package limit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(r io.Reader, limiter *rate.Limiter) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
limiter: limiter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
b := r.limiter.Burst()
|
||||||
|
if b < len(p) {
|
||||||
|
p = p[:b]
|
||||||
|
}
|
||||||
|
n, err = r.r.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.limiter.WaitN(context.Background(), n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
60
utils/limit/writer.go
Normal file
60
utils/limit/writer.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package limit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
limiter: limiter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
var nn int
|
||||||
|
b := w.limiter.Burst()
|
||||||
|
for {
|
||||||
|
end := len(p)
|
||||||
|
if end == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b < len(p) {
|
||||||
|
end = b
|
||||||
|
}
|
||||||
|
err = w.limiter.WaitN(context.Background(), end)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nn, err = w.w.Write(p[:end])
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = p[end:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -29,16 +29,20 @@ func init() {
|
|||||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
|
||||||
SetLogFile(logWay, logFile, maxdays)
|
SetLogFile(logWay, logFile, maxdays, disableLogColor)
|
||||||
SetLogLevel(logLevel)
|
SetLogLevel(logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogFile to configure log params
|
// SetLogFile to configure log params
|
||||||
// logWay: file or console
|
// logWay: file or console
|
||||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
|
||||||
if logWay == "console" {
|
if logWay == "console" {
|
||||||
Log.SetLogger("console", "")
|
params := ""
|
||||||
|
if disableLogColor {
|
||||||
|
params = fmt.Sprintf(`{"color": false}`)
|
||||||
|
}
|
||||||
|
Log.SetLogger("console", params)
|
||||||
} else {
|
} else {
|
||||||
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
||||||
Log.SetLogger("file", params)
|
Log.SetLogger("file", params)
|
||||||
@@ -87,71 +91,3 @@ func Debug(format string, v ...interface{}) {
|
|||||||
func Trace(format string, v ...interface{}) {
|
func Trace(format string, v ...interface{}) {
|
||||||
Log.Trace(format, v...)
|
Log.Trace(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger is the log interface
|
|
||||||
type Logger interface {
|
|
||||||
AddLogPrefix(string)
|
|
||||||
GetPrefixStr() string
|
|
||||||
GetAllPrefix() []string
|
|
||||||
ClearLogPrefix()
|
|
||||||
Error(string, ...interface{})
|
|
||||||
Warn(string, ...interface{})
|
|
||||||
Info(string, ...interface{})
|
|
||||||
Debug(string, ...interface{})
|
|
||||||
Trace(string, ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrefixLogger struct {
|
|
||||||
prefix string
|
|
||||||
allPrefix []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrefixLogger(prefix string) *PrefixLogger {
|
|
||||||
logger := &PrefixLogger{
|
|
||||||
allPrefix: make([]string, 0),
|
|
||||||
}
|
|
||||||
logger.AddLogPrefix(prefix)
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) AddLogPrefix(prefix string) {
|
|
||||||
if len(prefix) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pl.prefix += "[" + prefix + "] "
|
|
||||||
pl.allPrefix = append(pl.allPrefix, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) GetPrefixStr() string {
|
|
||||||
return pl.prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) GetAllPrefix() []string {
|
|
||||||
return pl.allPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) ClearLogPrefix() {
|
|
||||||
pl.prefix = ""
|
|
||||||
pl.allPrefix = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) Error(format string, v ...interface{}) {
|
|
||||||
Log.Error(pl.prefix+format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) Warn(format string, v ...interface{}) {
|
|
||||||
Log.Warn(pl.prefix+format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) Info(format string, v ...interface{}) {
|
|
||||||
Log.Info(pl.prefix+format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) Debug(format string, v ...interface{}) {
|
|
||||||
Log.Debug(pl.prefix+format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *PrefixLogger) Trace(format string, v ...interface{}) {
|
|
||||||
Log.Trace(pl.prefix+format, v...)
|
|
||||||
}
|
|
||||||
|
|||||||
34
utils/metric/metrics.go
Normal file
34
utils/metric/metrics.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2020 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package metric
|
||||||
|
|
||||||
|
// GaugeMetric represents a single numerical value that can arbitrarily go up
|
||||||
|
// and down.
|
||||||
|
type GaugeMetric interface {
|
||||||
|
Inc()
|
||||||
|
Dec()
|
||||||
|
Set(float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CounterMetric represents a single numerical value that only ever
|
||||||
|
// goes up.
|
||||||
|
type CounterMetric interface {
|
||||||
|
Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistogramMetric counts individual observations.
|
||||||
|
type HistogramMetric interface {
|
||||||
|
Observe(float64)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -23,41 +24,64 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
gnet "github.com/fatedier/golib/net"
|
||||||
kcp "github.com/fatedier/kcp-go"
|
kcp "github.com/fatedier/kcp-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Conn is the interface of connections used in frp.
|
type ContextGetter interface {
|
||||||
type Conn interface {
|
Context() context.Context
|
||||||
net.Conn
|
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WrapLogConn struct {
|
type ContextSetter interface {
|
||||||
net.Conn
|
WithContext(ctx context.Context)
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapConn(c net.Conn) Conn {
|
func NewLogFromConn(conn net.Conn) *xlog.Logger {
|
||||||
return &WrapLogConn{
|
if c, ok := conn.(ContextGetter); ok {
|
||||||
Conn: c,
|
return xlog.FromContextSafe(c.Context())
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
}
|
||||||
|
return xlog.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextFromConn(conn net.Conn) context.Context {
|
||||||
|
if c, ok := conn.(ContextGetter); ok {
|
||||||
|
return c.Context()
|
||||||
|
}
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextConn is the connection with context
|
||||||
|
type ContextConn struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextConn(c net.Conn, ctx context.Context) *ContextConn {
|
||||||
|
return &ContextConn{
|
||||||
|
Conn: c,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextConn) WithContext(ctx context.Context) {
|
||||||
|
c.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextConn) Context() context.Context {
|
||||||
|
return c.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
type WrapReadWriteCloserConn struct {
|
type WrapReadWriteCloserConn struct {
|
||||||
io.ReadWriteCloser
|
io.ReadWriteCloser
|
||||||
log.Logger
|
|
||||||
|
|
||||||
underConn net.Conn
|
underConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) Conn {
|
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) net.Conn {
|
||||||
return &WrapReadWriteCloserConn{
|
return &WrapReadWriteCloserConn{
|
||||||
ReadWriteCloser: rwc,
|
ReadWriteCloser: rwc,
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
underConn: underConn,
|
underConn: underConn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +123,6 @@ func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
|
|||||||
|
|
||||||
type CloseNotifyConn struct {
|
type CloseNotifyConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
log.Logger
|
|
||||||
|
|
||||||
// 1 means closed
|
// 1 means closed
|
||||||
closeFlag int32
|
closeFlag int32
|
||||||
@@ -108,10 +131,9 @@ type CloseNotifyConn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// closeFn will be only called once
|
// closeFn will be only called once
|
||||||
func WrapCloseNotifyConn(c net.Conn, closeFn func()) Conn {
|
func WrapCloseNotifyConn(c net.Conn, closeFn func()) net.Conn {
|
||||||
return &CloseNotifyConn{
|
return &CloseNotifyConn{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
closeFn: closeFn,
|
closeFn: closeFn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +150,7 @@ func (cc *CloseNotifyConn) Close() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StatsConn struct {
|
type StatsConn struct {
|
||||||
Conn
|
net.Conn
|
||||||
|
|
||||||
closed int64 // 1 means closed
|
closed int64 // 1 means closed
|
||||||
totalRead int64
|
totalRead int64
|
||||||
@@ -136,7 +158,7 @@ type StatsConn struct {
|
|||||||
statsFunc func(totalRead, totalWrite int64)
|
statsFunc func(totalRead, totalWrite int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapStatsConn(conn Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
|
func WrapStatsConn(conn net.Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
|
||||||
return &StatsConn{
|
return &StatsConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
statsFunc: statsFunc,
|
statsFunc: statsFunc,
|
||||||
@@ -166,10 +188,10 @@ func (statsConn *StatsConn) Close() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectServer(protocol string, addr string) (c Conn, err error) {
|
func ConnectServer(protocol string, addr string) (c net.Conn, err error) {
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
return ConnectTcpServer(addr)
|
return net.Dial("tcp", addr)
|
||||||
case "kcp":
|
case "kcp":
|
||||||
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
|
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
@@ -184,21 +206,17 @@ func ConnectServer(protocol string, addr string) (c Conn, err error) {
|
|||||||
kcpConn.SetACKNoDelay(false)
|
kcpConn.SetACKNoDelay(false)
|
||||||
kcpConn.SetReadBuffer(4194304)
|
kcpConn.SetReadBuffer(4194304)
|
||||||
kcpConn.SetWriteBuffer(4194304)
|
kcpConn.SetWriteBuffer(4194304)
|
||||||
c = WrapConn(kcpConn)
|
c = kcpConn
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn, err error) {
|
func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.Conn, err error) {
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
var conn net.Conn
|
return gnet.DialTcpByProxy(proxyURL, addr)
|
||||||
if conn, err = gnet.DialTcpByProxy(proxyUrl, addr); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return WrapConn(conn), nil
|
|
||||||
case "kcp":
|
case "kcp":
|
||||||
// http proxy is not supported for kcp
|
// http proxy is not supported for kcp
|
||||||
return ConnectServer(protocol, addr)
|
return ConnectServer(protocol, addr)
|
||||||
@@ -209,7 +227,7 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
|
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c net.Conn, err error) {
|
||||||
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
|
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -18,17 +18,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
|
|
||||||
kcp "github.com/fatedier/kcp-go"
|
kcp "github.com/fatedier/kcp-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KcpListener struct {
|
type KcpListener struct {
|
||||||
net.Addr
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
accept chan Conn
|
acceptCh chan net.Conn
|
||||||
closeFlag bool
|
closeFlag bool
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
|
func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
|
||||||
@@ -40,11 +36,9 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
|
|||||||
listener.SetWriteBuffer(4194304)
|
listener.SetWriteBuffer(4194304)
|
||||||
|
|
||||||
l = &KcpListener{
|
l = &KcpListener{
|
||||||
Addr: listener.Addr(),
|
|
||||||
listener: listener,
|
listener: listener,
|
||||||
accept: make(chan Conn),
|
acceptCh: make(chan net.Conn),
|
||||||
closeFlag: false,
|
closeFlag: false,
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -52,7 +46,7 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
|
|||||||
conn, err := listener.AcceptKCP()
|
conn, err := listener.AcceptKCP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if l.closeFlag {
|
if l.closeFlag {
|
||||||
close(l.accept)
|
close(l.acceptCh)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -64,14 +58,14 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
|
|||||||
conn.SetWindowSize(1024, 1024)
|
conn.SetWindowSize(1024, 1024)
|
||||||
conn.SetACKNoDelay(false)
|
conn.SetACKNoDelay(false)
|
||||||
|
|
||||||
l.accept <- WrapConn(conn)
|
l.acceptCh <- conn
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return l, err
|
return l, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *KcpListener) Accept() (Conn, error) {
|
func (l *KcpListener) Accept() (net.Conn, error) {
|
||||||
conn, ok := <-l.accept
|
conn, ok := <-l.acceptCh
|
||||||
if !ok {
|
if !ok {
|
||||||
return conn, fmt.Errorf("channel for kcp listener closed")
|
return conn, fmt.Errorf("channel for kcp listener closed")
|
||||||
}
|
}
|
||||||
@@ -86,6 +80,10 @@ func (l *KcpListener) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *KcpListener) Addr() net.Addr {
|
||||||
|
return l.listener.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
|
func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
|
||||||
kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
|
kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,65 +19,34 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener interface {
|
|
||||||
Accept() (Conn, error)
|
|
||||||
Close() error
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogListener struct {
|
|
||||||
l net.Listener
|
|
||||||
net.Listener
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func WrapLogListener(l net.Listener) Listener {
|
|
||||||
return &LogListener{
|
|
||||||
l: l,
|
|
||||||
Listener: l,
|
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logL *LogListener) Accept() (Conn, error) {
|
|
||||||
c, err := logL.l.Accept()
|
|
||||||
return WrapConn(c), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom listener
|
// Custom listener
|
||||||
type CustomListener struct {
|
type CustomListener struct {
|
||||||
conns chan Conn
|
acceptCh chan net.Conn
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCustomListener() *CustomListener {
|
func NewCustomListener() *CustomListener {
|
||||||
return &CustomListener{
|
return &CustomListener{
|
||||||
conns: make(chan Conn, 64),
|
acceptCh: make(chan net.Conn, 64),
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CustomListener) Accept() (Conn, error) {
|
func (l *CustomListener) Accept() (net.Conn, error) {
|
||||||
conn, ok := <-l.conns
|
conn, ok := <-l.acceptCh
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("listener closed")
|
return nil, fmt.Errorf("listener closed")
|
||||||
}
|
}
|
||||||
conn.AddLogPrefix(l.GetPrefixStr())
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CustomListener) PutConn(conn Conn) error {
|
func (l *CustomListener) PutConn(conn net.Conn) error {
|
||||||
err := errors.PanicToError(func() {
|
err := errors.PanicToError(func() {
|
||||||
select {
|
select {
|
||||||
case l.conns <- conn:
|
case l.acceptCh <- conn:
|
||||||
default:
|
default:
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
@@ -89,7 +58,7 @@ func (l *CustomListener) Close() error {
|
|||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
if !l.closed {
|
if !l.closed {
|
||||||
close(l.conns)
|
close(l.acceptCh)
|
||||||
l.closed = true
|
l.closed = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
111
utils/net/tcp.go
111
utils/net/tcp.go
@@ -1,111 +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 net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TcpListener struct {
|
|
||||||
net.Addr
|
|
||||||
listener net.Listener
|
|
||||||
accept chan Conn
|
|
||||||
closeFlag bool
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListenTcp(bindAddr string, bindPort int) (l *TcpListener, err error) {
|
|
||||||
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
|
||||||
if err != nil {
|
|
||||||
return l, err
|
|
||||||
}
|
|
||||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
|
||||||
if err != nil {
|
|
||||||
return l, err
|
|
||||||
}
|
|
||||||
|
|
||||||
l = &TcpListener{
|
|
||||||
Addr: listener.Addr(),
|
|
||||||
listener: listener,
|
|
||||||
accept: make(chan Conn),
|
|
||||||
closeFlag: false,
|
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
conn, err := listener.AcceptTCP()
|
|
||||||
if err != nil {
|
|
||||||
if l.closeFlag {
|
|
||||||
close(l.accept)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c := NewTcpConn(conn)
|
|
||||||
l.accept <- c
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return l, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait util get one new connection or listener is closed
|
|
||||||
// if listener is closed, err returned.
|
|
||||||
func (l *TcpListener) Accept() (Conn, error) {
|
|
||||||
conn, ok := <-l.accept
|
|
||||||
if !ok {
|
|
||||||
return conn, fmt.Errorf("channel for tcp listener closed")
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *TcpListener) Close() error {
|
|
||||||
if !l.closeFlag {
|
|
||||||
l.closeFlag = true
|
|
||||||
l.listener.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap for TCPConn.
|
|
||||||
type TcpConn struct {
|
|
||||||
net.Conn
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTcpConn(conn net.Conn) (c *TcpConn) {
|
|
||||||
c = &TcpConn{
|
|
||||||
Conn: conn,
|
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectTcpServer(addr string) (c Conn, err error) {
|
|
||||||
servertAddr, err := net.ResolveTCPAddr("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn, err := net.DialTCP("tcp", nil, servertAddr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c = NewTcpConn(conn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,7 @@ package net
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -26,13 +27,13 @@ var (
|
|||||||
FRP_TLS_HEAD_BYTE = 0x17
|
FRP_TLS_HEAD_BYTE = 0x17
|
||||||
)
|
)
|
||||||
|
|
||||||
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
|
||||||
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
|
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
|
||||||
out = WrapConn(tls.Client(c, tlsConfig))
|
out = tls.Client(c, tlsConfig)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
|
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, err error) {
|
||||||
sc, r := gnet.NewSharedConnSize(c, 2)
|
sc, r := gnet.NewSharedConnSize(c, 2)
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
var n int
|
var n int
|
||||||
@@ -44,9 +45,13 @@ func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
||||||
out = WrapConn(tls.Server(c, tlsConfig))
|
out = tls.Server(c, tlsConfig)
|
||||||
} else {
|
} else {
|
||||||
out = WrapConn(sc)
|
if tlsOnly {
|
||||||
|
err = fmt.Errorf("non-TLS connection received on a TlsOnly server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = sc
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,7 +31,6 @@ type UdpPacket struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeUdpConn struct {
|
type FakeUdpConn struct {
|
||||||
log.Logger
|
|
||||||
l *UdpListener
|
l *UdpListener
|
||||||
|
|
||||||
localAddr net.Addr
|
localAddr net.Addr
|
||||||
@@ -47,7 +44,6 @@ type FakeUdpConn struct {
|
|||||||
|
|
||||||
func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn {
|
func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn {
|
||||||
fc := &FakeUdpConn{
|
fc := &FakeUdpConn{
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
l: l,
|
l: l,
|
||||||
localAddr: laddr,
|
localAddr: laddr,
|
||||||
remoteAddr: raddr,
|
remoteAddr: raddr,
|
||||||
@@ -157,15 +153,13 @@ func (c *FakeUdpConn) SetWriteDeadline(t time.Time) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UdpListener struct {
|
type UdpListener struct {
|
||||||
net.Addr
|
addr net.Addr
|
||||||
accept chan Conn
|
acceptCh chan net.Conn
|
||||||
writeCh chan *UdpPacket
|
writeCh chan *UdpPacket
|
||||||
readConn net.Conn
|
readConn net.Conn
|
||||||
closeFlag bool
|
closeFlag bool
|
||||||
|
|
||||||
fakeConns map[string]*FakeUdpConn
|
fakeConns map[string]*FakeUdpConn
|
||||||
|
|
||||||
log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
|
func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
|
||||||
@@ -176,11 +170,10 @@ func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
|
|||||||
readConn, err := net.ListenUDP("udp", udpAddr)
|
readConn, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
|
||||||
l = &UdpListener{
|
l = &UdpListener{
|
||||||
Addr: udpAddr,
|
addr: udpAddr,
|
||||||
accept: make(chan Conn),
|
acceptCh: make(chan net.Conn),
|
||||||
writeCh: make(chan *UdpPacket, 1000),
|
writeCh: make(chan *UdpPacket, 1000),
|
||||||
fakeConns: make(map[string]*FakeUdpConn),
|
fakeConns: make(map[string]*FakeUdpConn),
|
||||||
Logger: log.NewPrefixLogger(""),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for reading
|
// for reading
|
||||||
@@ -189,19 +182,19 @@ func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
|
|||||||
buf := pool.GetBuf(1450)
|
buf := pool.GetBuf(1450)
|
||||||
n, remoteAddr, err := readConn.ReadFromUDP(buf)
|
n, remoteAddr, err := readConn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(l.accept)
|
close(l.acceptCh)
|
||||||
close(l.writeCh)
|
close(l.writeCh)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeConn, exist := l.fakeConns[remoteAddr.String()]
|
fakeConn, exist := l.fakeConns[remoteAddr.String()]
|
||||||
if !exist || fakeConn.IsClosed() {
|
if !exist || fakeConn.IsClosed() {
|
||||||
fakeConn = NewFakeUdpConn(l, l.Addr, remoteAddr)
|
fakeConn = NewFakeUdpConn(l, l.Addr(), remoteAddr)
|
||||||
l.fakeConns[remoteAddr.String()] = fakeConn
|
l.fakeConns[remoteAddr.String()] = fakeConn
|
||||||
}
|
}
|
||||||
fakeConn.putPacket(buf[:n])
|
fakeConn.putPacket(buf[:n])
|
||||||
|
|
||||||
l.accept <- fakeConn
|
l.acceptCh <- fakeConn
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -226,7 +219,6 @@ func (l *UdpListener) writeUdpPacket(packet *UdpPacket) (err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if errRet := recover(); errRet != nil {
|
if errRet := recover(); errRet != nil {
|
||||||
err = fmt.Errorf("udp write closed listener")
|
err = fmt.Errorf("udp write closed listener")
|
||||||
l.Info("udp write closed listener")
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
l.writeCh <- packet
|
l.writeCh <- packet
|
||||||
@@ -243,8 +235,8 @@ func (l *UdpListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *UdpListener) Accept() (Conn, error) {
|
func (l *UdpListener) Accept() (net.Conn, error) {
|
||||||
conn, ok := <-l.accept
|
conn, ok := <-l.acceptCh
|
||||||
if !ok {
|
if !ok {
|
||||||
return conn, fmt.Errorf("channel for udp listener closed")
|
return conn, fmt.Errorf("channel for udp listener closed")
|
||||||
}
|
}
|
||||||
@@ -258,3 +250,7 @@ func (l *UdpListener) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *UdpListener) Addr() net.Addr {
|
||||||
|
return l.addr
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user