Merge pull request #1494 from fatedier/dev

bump version to v0.29.1
pull/1496/head v0.29.1
fatedier 2019-11-02 21:14:50 +08:00 committed by GitHub
commit adc3adc13b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 862 additions and 880 deletions

357
README.md
View File

@ -6,53 +6,53 @@
## 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) * [Authenticating the Client](#authenticating-the-client)
* [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)
* [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) * [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) * [Plugins](#plugins)
* [Development Plan](#development-plan) * [Development Plan](#development-plan)
* [Contributing](#contributing) * [Contributing](#contributing)
* [Donation](#donation) * [Donation](#donation)
@ -62,11 +62,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 compatibility. 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 +74,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 +90,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 +109,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 +132,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 +147,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 +168,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 +191,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 +216,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 +238,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,9 +256,9 @@ 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
@ -268,17 +268,15 @@ Configure frps same as above.
plugin_header_X-From-Where = frp 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
@ -293,7 +291,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
@ -310,23 +308,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
@ -341,7 +340,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
@ -358,23 +357,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
@ -389,7 +388,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"
@ -397,12 +396,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:
@ -414,15 +412,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.
![dashboard](/doc/pic/dashboard.png) ![dashboard](/doc/pic/dashboard.png)
### 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]
@ -432,15 +430,15 @@ 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 ### Authenticating the Client
`token` in frps.ini and frpc.ini should be same. Always use the same `token` in the `[common]` section in `frps.ini` and `frpc.ini`.
### 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
@ -454,15 +452,15 @@ 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 ### Hot-Reloading frpc configuration
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features. The `admin_addr` and `admin_port` fields are required for enabling HTTP API:
```ini ```ini
# frpc.ini # frpc.ini
@ -471,17 +469,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
@ -489,19 +487,19 @@ 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.
### 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
@ -513,36 +511,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
@ -562,7 +562,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
@ -583,23 +583,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
@ -607,77 +603,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:
@ -686,21 +686,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.
@ -709,23 +707,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
@ -735,35 +733,36 @@ 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. ### 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.
@ -777,7 +776,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
@ -788,15 +787,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 ### 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**:
@ -814,7 +813,7 @@ plugin_http_passwd = abc
## Development Plan ## Development Plan
* Log http request information in frps. * Log HTTP request information in frps.
## Contributing ## Contributing
@ -822,14 +821,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

View File

@ -15,9 +15,11 @@
package client package client
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
"net"
"runtime/debug" "runtime/debug"
"sync" "sync"
"time" "time"
@ -25,8 +27,8 @@ import (
"github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/proxy"
"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"
@ -45,7 +47,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
@ -76,12 +78,19 @@ type Control struct {
mu sync.RWMutex mu sync.RWMutex
log.Logger xl *xlog.Logger
// service context
ctx context.Context
} }
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf, func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.Session,
pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control { clientCfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
serverUDPPort int) *Control {
// new xlog instance
ctl := &Control{ ctl := &Control{
runId: runId, runId: runId,
conn: conn, conn: conn,
@ -96,11 +105,12 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg
writerShutdown: shutdown.New(), writerShutdown: shutdown.New(),
msgHandlerShutdown: shutdown.New(), msgHandlerShutdown: shutdown.New(),
serverUDPPort: serverUDPPort, serverUDPPort: serverUDPPort,
Logger: log.NewPrefixLogger(""), xl: xlog.FromContextSafe(ctx),
ctx: ctx,
} }
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort) 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
} }
@ -117,6 +127,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
@ -126,31 +137,31 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
RunId: ctl.runId, RunId: ctl.runId,
} }
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() 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)
} }
} }
@ -169,15 +180,16 @@ 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) {
xl := ctl.xl
if ctl.clientCfg.TcpMux { 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 ctl.clientCfg.TLSEnable { if ctl.clientCfg.TLSEnable {
@ -188,7 +200,7 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol, conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.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
} }
} }
@ -197,10 +209,11 @@ 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()
@ -210,10 +223,10 @@ func (ctl *Control) reader() {
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
} }
@ -225,20 +238,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(ctl.clientCfg.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
} }
} }
@ -247,10 +261,11 @@ 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()
@ -266,11 +281,11 @@ 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{} ctl.sendCh <- &msg.Ping{}
case <-hbCheck.C: case <-hbCheck.C:
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.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
@ -287,7 +302,7 @@ func (ctl *Control) msgHandler() {
ctl.HandleNewProxyResp(m) ctl.HandleNewProxyResp(m)
case *msg.Pong: case *msg.Pong:
ctl.lastPong = time.Now() ctl.lastPong = time.Now()
ctl.Debug("receive heartbeat from server") xl.Debug("receive heartbeat from server")
} }
} }
} }

View File

@ -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()
} }

View File

@ -16,6 +16,7 @@ package proxy
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -29,8 +30,8 @@ import (
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/plugin" "github.com/fatedier/frp/models/plugin"
"github.com/fatedier/frp/models/proto/udp" "github.com/fatedier/frp/models/proto/udp"
"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/errors" "github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io" frpIo "github.com/fatedier/golib/io"
@ -44,17 +45,17 @@ 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, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) { func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
baseProxy := BaseProxy{ baseProxy := BaseProxy{
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
clientCfg: clientCfg, clientCfg: clientCfg,
serverUDPPort: serverUDPPort, serverUDPPort: serverUDPPort,
xl: xlog.FromContextSafe(ctx),
ctx: ctx,
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
@ -93,10 +94,12 @@ func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serve
type BaseProxy struct { type BaseProxy struct {
closed bool closed bool
mu sync.RWMutex
clientCfg config.ClientCommonConf clientCfg config.ClientCommonConf
serverUDPPort int serverUDPPort int
log.Logger
mu sync.RWMutex
xl *xlog.Logger
ctx context.Context
} }
// TCP // TCP
@ -123,8 +126,8 @@ 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, conn,
[]byte(pxy.clientCfg.Token), m) []byte(pxy.clientCfg.Token), m)
} }
@ -152,8 +155,8 @@ 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, conn,
[]byte(pxy.clientCfg.Token), m) []byte(pxy.clientCfg.Token), m)
} }
@ -181,8 +184,8 @@ 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, conn,
[]byte(pxy.clientCfg.Token), m) []byte(pxy.clientCfg.Token), m)
} }
@ -210,8 +213,8 @@ 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, conn,
[]byte(pxy.clientCfg.Token), m) []byte(pxy.clientCfg.Token), m)
} }
@ -239,12 +242,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
} }
@ -259,7 +263,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
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
} }
@ -270,28 +274,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())
/* /*
@ -301,18 +305,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()
@ -322,22 +326,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, natHoleRespMsg.VisitorAddr)
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
} }
@ -346,18 +350,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,
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) {
@ -390,7 +394,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) {
@ -419,8 +423,9 @@ 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()
@ -435,32 +440,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
} }
} }
@ -472,7 +477,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
} }
} }
@ -485,20 +490,22 @@ 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, 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
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
} }
} }
@ -541,19 +548,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 {
@ -561,6 +568,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")
} }
} }

View File

@ -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"
) )
@ -25,19 +26,17 @@ type ProxyManager struct {
// The UDP port that the server is listening on // The UDP port that the server is listening on
serverUDPPort int serverUDPPort int
logPrefix string ctx context.Context
log.Logger
} }
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *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,
clientCfg: clientCfg, clientCfg: clientCfg,
serverUDPPort: serverUDPPort, serverUDPPort: serverUDPPort,
logPrefix: logPrefix, ctx: ctx,
Logger: log.NewPrefixLogger(logPrefix),
} }
} }
@ -65,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()
@ -104,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()
@ -127,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.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort) 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)
@ -141,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)
} }
} }

View File

@ -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, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *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, clientCfg config.ClientCommonConf, ev
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, clientCfg, serverUDPPort) pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
return pw return pw
} }
@ -147,6 +149,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 +164,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
@ -180,7 +183,7 @@ func (pw *ProxyWrapper) checkWorker() {
ProxyName: pw.Name, ProxyName: pw.Name,
}, },
}) })
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed) xl.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()

View File

@ -15,9 +15,11 @@
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"
@ -30,6 +32,7 @@ import (
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/version" "github.com/fatedier/frp/utils/version"
"github.com/fatedier/frp/utils/xlog"
fmux "github.com/hashicorp/yamux" fmux "github.com/hashicorp/yamux"
) )
@ -56,18 +59,24 @@ type Service struct {
serverUDPPort int serverUDPPort int
exit uint32 // 0 means not exit exit uint32 // 0 means not exit
closedCh chan int
// service context
ctx context.Context
// call cancel to stop service
cancel context.CancelFunc
} }
// NewService creates a new client service with the given configuration.
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (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) {
ctx, cancel := context.WithCancel(context.Background())
svr = &Service{ svr = &Service{
cfg: cfg, cfg: cfg,
cfgFile: cfgFile, 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
} }
@ -79,11 +88,13 @@ 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
@ -94,7 +105,7 @@ func (svr *Service) Run() error {
} }
} else { } else {
// login success // login success
ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort) ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
ctl.Run() ctl.Run()
svr.ctlMu.Lock() svr.ctlMu.Lock()
svr.ctl = ctl svr.ctl = ctl
@ -118,12 +129,12 @@ func (svr *Service) Run() error {
} }
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.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
@ -134,10 +145,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 {
@ -148,7 +159,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.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort) ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
ctl.Run() ctl.Run()
svr.ctlMu.Lock() svr.ctlMu.Lock()
svr.ctl = ctl svr.ctl = ctl
@ -161,7 +172,8 @@ 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 svr.cfg.TLSEnable { if svr.cfg.TLSEnable {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
@ -197,7 +209,7 @@ 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() now := time.Now().Unix()
@ -225,13 +237,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
xl.ResetPrefixes()
xl.AppendPrefix(svr.runId)
svr.serverUDPPort = loginRespMsg.ServerUdpPort svr.serverUDPPort = loginRespMsg.ServerUdpPort
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort) xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
return return
} }
@ -247,5 +262,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()
} }

View File

@ -16,6 +16,7 @@ package client
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -25,9 +26,9 @@ import (
"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"
@ -38,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:
@ -63,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 mu sync.RWMutex
log.Logger ctx context.Context
} }
type StcpVisitor struct { type StcpVisitor struct {
@ -76,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
} }
@ -90,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
} }
@ -101,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
@ -121,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
} }
@ -129,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
} }
@ -144,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
} }
} }
@ -163,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
} }
@ -177,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
} }
@ -188,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 sv.ctl.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", sv.ctl.clientCfg.ServerAddr, sv.ctl.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()
@ -219,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
} }
@ -229,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()
@ -255,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()
@ -272,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")
} }

View File

@ -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 {
@ -31,25 +32,28 @@ 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
} }

View File

@ -202,7 +202,7 @@ func runServer(cfg config.ServerCommonConf) (err error) {
if err != nil { if err != nil {
return err return err
} }
log.Info("Start frps success") log.Info("start frps success")
svr.Run() svr.Run()
return return
} }

14
go.sum
View File

@ -1,14 +1,10 @@
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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/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/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/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=
@ -26,28 +22,18 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
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/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/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
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/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 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=

View File

@ -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)

View File

@ -18,6 +18,7 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strings" "strings"
@ -115,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)
} }

View File

@ -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
} }

View File

@ -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)

View File

@ -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)
} }

View File

@ -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

View File

@ -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

View File

@ -15,8 +15,10 @@
package server package server
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net"
"runtime/debug" "runtime/debug"
"sync" "sync"
"time" "time"
@ -28,8 +30,8 @@ import (
"github.com/fatedier/frp/server/controller" "github.com/fatedier/frp/server/controller"
"github.com/fatedier/frp/server/proxy" "github.com/fatedier/frp/server/proxy"
"github.com/fatedier/frp/server/stats" "github.com/fatedier/frp/server/stats"
"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"
@ -131,9 +133,12 @@ type Control struct {
// Server configuration information // Server configuration information
serverCfg config.ServerCommonConf serverCfg config.ServerCommonConf
xl *xlog.Logger
ctx context.Context
} }
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager, func NewControl(ctx context.Context, rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login, statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
serverCfg config.ServerCommonConf) *Control { serverCfg config.ServerCommonConf) *Control {
@ -161,6 +166,8 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
managerShutdown: shutdown.New(), managerShutdown: shutdown.New(),
allShutdown: shutdown.New(), allShutdown: shutdown.New(),
serverCfg: serverCfg, serverCfg: serverCfg,
xl: xlog.FromContextSafe(ctx),
ctx: ctx,
} }
} }
@ -185,18 +192,19 @@ func (ctl *Control) Start() {
} }
func (ctl *Control) RegisterWorkConn(conn net.Conn) { func (ctl *Control) RegisterWorkConn(conn net.Conn) {
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")
default: default:
ctl.conn.Debug("work connection pool is full, discarding") xl.Debug("work connection pool is full, discarding")
conn.Close() conn.Close()
} }
} }
@ -206,10 +214,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()))
} }
}() }()
@ -221,14 +230,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
} }
@ -236,13 +245,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(ctl.serverCfg.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
} }
} }
@ -255,16 +264,18 @@ 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()))
} }
}() }()
@ -273,17 +284,17 @@ func (ctl *Control) writer() {
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.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
} }
} }
@ -291,10 +302,11 @@ 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()))
} }
}() }()
@ -305,10 +317,10 @@ func (ctl *Control) reader() {
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
} }
@ -319,10 +331,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()))
} }
}() }()
@ -355,7 +368,7 @@ func (ctl *Control) stoper() {
} }
ctl.allShutdown.Done() ctl.allShutdown.Done()
ctl.conn.Info("client exit success") xl.Info("client exit success")
ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{}) ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{})
} }
@ -366,10 +379,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()))
} }
}() }()
@ -383,7 +397,7 @@ func (ctl *Control) manager() {
select { select {
case <-heartbeat.C: case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.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:
@ -400,10 +414,10 @@ func (ctl *Control) manager() {
} }
if err != nil { if err != nil {
resp.Error = err.Error() resp.Error = err.Error()
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err) xl.Warn("new proxy [%s] error: %v", m.ProxyName, err)
} 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{ ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{
Name: m.ProxyName, Name: m.ProxyName,
ProxyType: m.ProxyType, ProxyType: m.ProxyType,
@ -412,10 +426,10 @@ func (ctl *Control) manager() {
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:
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{}
} }
} }
@ -432,7 +446,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
// 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, ctl.serverCfg) pxy, err := proxy.NewProxy(ctl.ctx, ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
if err != nil { if err != nil {
return remoteAddr, err return remoteAddr, err
} }

View File

@ -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()

View File

@ -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)

View File

@ -36,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,7 +89,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
}) })
} }
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.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)
} }
} }
@ -120,7 +121,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
} }
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.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, ",")
@ -131,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
} }
@ -148,7 +150,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
if pxy.cfg.UseEncryption { if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.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
} }
} }

View File

@ -28,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() {
@ -42,26 +43,24 @@ 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, pxy.serverCfg.VhostHttpsPort)) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
} }
if pxy.cfg.SubDomain != "" { if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.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(pxy.serverCfg.VhostHttpsPort))) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
} }

View File

@ -15,6 +15,7 @@
package proxy package proxy
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -25,48 +26,54 @@ import (
"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/stats"
"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 statsCollector stats.Collector
listeners []frpNet.Listener listeners []net.Listener
usedPortsNum int usedPortsNum int
poolCount int poolCount int
getWorkConnFn GetWorkConnFn getWorkConnFn GetWorkConnFn
serverCfg config.ServerCommonConf 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 +81,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
@ -109,7 +118,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
DstPort: uint16(dstPort), DstPort: uint16(dstPort),
}) })
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,36 +135,39 @@ 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, config.ServerCommonConf)) { func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn, stats.Collector, 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, pxy.serverCfg) go handler(p, c, pxy.statsCollector, 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, statsCollector stats.Collector, poolCount int,
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (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, statsCollector: statsCollector,
listeners: make([]frpNet.Listener, 0), listeners: make([]net.Listener, 0),
poolCount: poolCount, poolCount: poolCount,
getWorkConnFn: getWorkConnFn, getWorkConnFn: getWorkConnFn,
Logger: log.NewPrefixLogger(runId),
serverCfg: serverCfg, serverCfg: serverCfg,
xl: xl,
ctx: xlog.NewContext(ctx, xl),
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
@ -193,13 +205,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, serverCfg config.ServerCommonConf) { func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, statsCollector stats.Collector, 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
@ -211,17 +223,18 @@ 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(serverCfg.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()}) statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
@ -235,7 +248,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
ProxyName: pxy.GetName(), ProxyName: pxy.GetName(),
TrafficBytes: outCount, TrafficBytes: outCount,
}) })
pxy.Debug("join connections closed") xl.Debug("join connections closed")
} }
type ProxyManager struct { type ProxyManager struct {

View File

@ -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

View File

@ -16,9 +16,9 @@ package proxy
import ( import (
"fmt" "fmt"
"net"
"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 {
@ -29,6 +29,7 @@ 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, pxy.serverCfg.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 {
@ -41,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 {
@ -55,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(pxy.serverCfg.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

View File

@ -54,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
@ -74,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)
@ -91,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
@ -107,11 +108,11 @@ 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{ pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
ProxyName: pxy.GetName(), ProxyName: pxy.GetName(),
@ -119,7 +120,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
}) })
}); 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
} }
} }
@ -133,15 +134,15 @@ 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{ pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
ProxyName: pxy.GetName(), ProxyName: pxy.GetName(),
TrafficBytes: int64(len(udpMsg.Content)), TrafficBytes: int64(len(udpMsg.Content)),
@ -149,7 +150,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
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
} }
} }

View File

@ -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
} }

View File

@ -16,6 +16,7 @@ package server
import ( import (
"bytes" "bytes"
"context"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/tls" "crypto/tls"
@ -42,6 +43,7 @@ import (
"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"
@ -57,16 +59,16 @@ type Service struct {
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
@ -135,7 +137,7 @@ func NewService(cfg config.ServerCommonConf) (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.
@ -194,7 +196,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
} }
} }
svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(frpNet.WrapLogListener(l), 30*time.Second) svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, 30*time.Second)
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
@ -203,10 +205,9 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
} }
// 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 {
@ -258,7 +259,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,6 +267,9 @@ 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
@ -278,8 +282,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 {
@ -295,7 +299,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
// 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: err.Error(),
@ -306,7 +310,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
svr.RegisterWorkConn(conn, m) svr.RegisterWorkConn(conn, m)
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: err.Error(),
@ -342,8 +346,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,8 +355,21 @@ 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]", // 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.
if loginMsg.RunId == "" {
loginMsg.RunId, err = util.RandId()
if err != nil {
return
}
}
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) ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
// Check client version. // Check client version.
@ -368,22 +384,12 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
return return
} }
// If client's RunId is empty, it's a new client, we just create a new controller. ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
// 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 == "" {
loginMsg.RunId, err = util.RandId()
if err != nil {
return
}
}
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, 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
@ -398,17 +404,18 @@ 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) {
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
} }
ctl.RegisterWorkConn(workConn) ctl.RegisterWorkConn(workConn)
return return
} }
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)
} }

View File

@ -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

View File

@ -2,8 +2,7 @@
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 log_level = trace
log_level = debug
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

View File

@ -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

View File

@ -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

View File

@ -91,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...)
}

View File

@ -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 {
return xlog.FromContextSafe(c.Context())
}
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, Conn: c,
Logger: log.NewPrefixLogger(""), 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

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -26,13 +26,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, 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 +44,9 @@ 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) out = sc
} }
return return
} }

View File

@ -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
}

View File

@ -8,8 +8,6 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/fatedier/frp/utils/log"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
@ -22,10 +20,8 @@ const (
) )
type WebsocketListener struct { type WebsocketListener struct {
net.Addr
ln net.Listener ln net.Listener
accept chan Conn acceptCh chan net.Conn
log.Logger
server *http.Server server *http.Server
httpMutex *http.ServeMux httpMutex *http.ServeMux
@ -35,9 +31,7 @@ type WebsocketListener struct {
// ln: tcp listener for websocket connections // ln: tcp listener for websocket connections
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
wl = &WebsocketListener{ wl = &WebsocketListener{
Addr: ln.Addr(), acceptCh: make(chan net.Conn),
accept: make(chan Conn),
Logger: log.NewPrefixLogger(""),
} }
muxer := http.NewServeMux() muxer := http.NewServeMux()
@ -46,7 +40,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
conn := WrapCloseNotifyConn(c, func() { conn := WrapCloseNotifyConn(c, func() {
close(notifyCh) close(notifyCh)
}) })
wl.accept <- conn wl.acceptCh <- conn
<-notifyCh <-notifyCh
})) }))
@ -68,8 +62,8 @@ func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error)
return l, nil return l, nil
} }
func (p *WebsocketListener) Accept() (Conn, error) { func (p *WebsocketListener) Accept() (net.Conn, error) {
c, ok := <-p.accept c, ok := <-p.acceptCh
if !ok { if !ok {
return nil, ErrWebsocketListenerClosed return nil, ErrWebsocketListenerClosed
} }
@ -80,8 +74,12 @@ func (p *WebsocketListener) Close() error {
return p.server.Close() return p.server.Close()
} }
func (p *WebsocketListener) Addr() net.Addr {
return p.ln.Addr()
}
// addr: domain:port // addr: domain:port
func ConnectWebsocketServer(addr string) (Conn, error) { func ConnectWebsocketServer(addr string) (net.Conn, error) {
addr = "ws://" + addr + FrpWebsocketPath addr = "ws://" + addr + FrpWebsocketPath
uri, err := url.Parse(addr) uri, err := url.Parse(addr)
if err != nil { if err != nil {
@ -101,6 +99,5 @@ func ConnectWebsocketServer(addr string) (Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c := WrapConn(conn) return conn, nil
return c, nil
} }

View File

@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.29.0" var version string = "0.29.1"
func Full() string { func Full() string {
return version return version

View File

@ -17,11 +17,10 @@ package vhost
import ( import (
"fmt" "fmt"
"io" "io"
"net"
"strings" "strings"
"time" "time"
frpNet "github.com/fatedier/frp/utils/net"
gnet "github.com/fatedier/golib/net" gnet "github.com/fatedier/golib/net"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
) )
@ -48,7 +47,7 @@ type HttpsMuxer struct {
*VhostMuxer *VhostMuxer
} }
func NewHttpsMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpsMuxer, error) { func NewHttpsMuxer(listener net.Listener, timeout time.Duration) (*HttpsMuxer, error) {
mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, nil, timeout) mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, nil, timeout)
return &HttpsMuxer{mux}, err return &HttpsMuxer{mux}, err
} }
@ -182,7 +181,7 @@ func readHandshake(rd io.Reader) (host string, err error) {
return return
} }
func GetHttpsHostname(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) { func GetHttpsHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
reqInfoMap := make(map[string]string, 0) reqInfoMap := make(map[string]string, 0)
sc, rd := gnet.NewSharedConn(c) sc, rd := gnet.NewSharedConn(c)
host, err := readHandshake(rd) host, err := readHandshake(rd)
@ -191,5 +190,5 @@ func GetHttpsHostname(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err er
} }
reqInfoMap["Host"] = host reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "https" reqInfoMap["Scheme"] = "https"
return frpNet.WrapConn(sc), reqInfoMap, nil return sc, reqInfoMap, nil
} }

View File

@ -13,22 +13,25 @@
package vhost package vhost
import ( import (
"context"
"fmt" "fmt"
"net"
"strings" "strings"
"time" "time"
"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/xlog"
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
) )
type muxFunc func(frpNet.Conn) (frpNet.Conn, map[string]string, error) type muxFunc func(net.Conn) (net.Conn, map[string]string, error)
type httpAuthFunc func(frpNet.Conn, string, string, string) (bool, error) type httpAuthFunc func(net.Conn, string, string, string) (bool, error)
type hostRewriteFunc func(frpNet.Conn, string) (frpNet.Conn, error) type hostRewriteFunc func(net.Conn, string) (net.Conn, error)
type VhostMuxer struct { type VhostMuxer struct {
listener frpNet.Listener listener net.Listener
timeout time.Duration timeout time.Duration
vhostFunc muxFunc vhostFunc muxFunc
authFunc httpAuthFunc authFunc httpAuthFunc
@ -36,7 +39,7 @@ type VhostMuxer struct {
registryRouter *VhostRouters registryRouter *VhostRouters
} }
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) { func NewVhostMuxer(listener net.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
mux = &VhostMuxer{ mux = &VhostMuxer{
listener: listener, listener: listener,
timeout: timeout, timeout: timeout,
@ -49,7 +52,7 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
return mux, nil return mux, nil
} }
type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error) type CreateConnFunc func(remoteAddr string) (net.Conn, error)
// VhostRouteConfig is the params used to match HTTP requests // VhostRouteConfig is the params used to match HTTP requests
type VhostRouteConfig struct { type VhostRouteConfig struct {
@ -65,7 +68,7 @@ type VhostRouteConfig struct {
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil // listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
// then rewrite the host header to rewriteHost // then rewrite the host header to rewriteHost
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) { func (v *VhostMuxer) Listen(ctx context.Context, cfg *VhostRouteConfig) (l *Listener, err error) {
l = &Listener{ l = &Listener{
name: cfg.Domain, name: cfg.Domain,
location: cfg.Location, location: cfg.Location,
@ -73,8 +76,8 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
userName: cfg.Username, userName: cfg.Username,
passWord: cfg.Password, passWord: cfg.Password,
mux: v, mux: v,
accept: make(chan frpNet.Conn), accept: make(chan net.Conn),
Logger: log.NewPrefixLogger(""), ctx: ctx,
} }
err = v.registryRouter.Add(cfg.Domain, cfg.Location, l) err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
if err != nil { if err != nil {
@ -123,7 +126,7 @@ func (v *VhostMuxer) run() {
} }
} }
func (v *VhostMuxer) handle(c frpNet.Conn) { func (v *VhostMuxer) handle(c net.Conn) {
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil { if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
c.Close() c.Close()
return return
@ -146,13 +149,14 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
c.Close() c.Close()
return return
} }
xl := xlog.FromContextSafe(l.ctx)
// if authFunc is exist and userName/password is set // if authFunc is exist and userName/password is set
// then verify user access // then verify user access
if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" { if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"]) bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
if bAccess == false || err != nil { if bAccess == false || err != nil {
l.Debug("check http Authorization failed") xl.Debug("check http Authorization failed")
res := noAuthResponse() res := noAuthResponse()
res.Write(c) res.Write(c)
c.Close() c.Close()
@ -166,12 +170,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
} }
c = sConn c = sConn
l.Debug("get new http request host [%s] path [%s]", name, path) xl.Debug("get new http request host [%s] path [%s]", name, path)
err = errors.PanicToError(func() { err = errors.PanicToError(func() {
l.accept <- c l.accept <- c
}) })
if err != nil { if err != nil {
l.Warn("listener is already closed, ignore this request") xl.Warn("listener is already closed, ignore this request")
} }
} }
@ -182,11 +186,12 @@ type Listener struct {
userName string userName string
passWord string passWord string
mux *VhostMuxer // for closing VhostMuxer mux *VhostMuxer // for closing VhostMuxer
accept chan frpNet.Conn accept chan net.Conn
log.Logger ctx context.Context
} }
func (l *Listener) Accept() (frpNet.Conn, error) { func (l *Listener) Accept() (net.Conn, error) {
xl := xlog.FromContextSafe(l.ctx)
conn, ok := <-l.accept conn, ok := <-l.accept
if !ok { if !ok {
return nil, fmt.Errorf("Listener closed") return nil, fmt.Errorf("Listener closed")
@ -198,17 +203,13 @@ func (l *Listener) Accept() (frpNet.Conn, error) {
if l.mux.rewriteFunc != nil { if l.mux.rewriteFunc != nil {
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost) sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
if err != nil { if err != nil {
l.Warn("host header rewrite failed: %v", err) xl.Warn("host header rewrite failed: %v", err)
return nil, fmt.Errorf("host header rewrite failed") return nil, fmt.Errorf("host header rewrite failed")
} }
l.Debug("rewrite host to [%s] success", l.rewriteHost) xl.Debug("rewrite host to [%s] success", l.rewriteHost)
conn = sConn conn = sConn
} }
return frpNet.NewContextConn(conn, l.ctx), nil
for _, prefix := range l.GetAllPrefix() {
conn.AddLogPrefix(prefix)
}
return conn, nil
} }
func (l *Listener) Close() error { func (l *Listener) Close() error {
@ -220,3 +221,7 @@ func (l *Listener) Close() error {
func (l *Listener) Name() string { func (l *Listener) Name() string {
return l.name return l.name
} }
func (l *Listener) Addr() net.Addr {
return (*net.TCPAddr)(nil)
}

42
utils/xlog/ctx.go Normal file
View File

@ -0,0 +1,42 @@
// 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 xlog
import (
"context"
)
type key int
const (
xlogKey key = 0
)
func NewContext(ctx context.Context, xl *Logger) context.Context {
return context.WithValue(ctx, xlogKey, xl)
}
func FromContext(ctx context.Context) (xl *Logger, ok bool) {
xl, ok = ctx.Value(xlogKey).(*Logger)
return
}
func FromContextSafe(ctx context.Context) *Logger {
xl, ok := ctx.Value(xlogKey).(*Logger)
if !ok {
xl = New()
}
return xl
}

73
utils/xlog/xlog.go Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package xlog
import (
"github.com/fatedier/frp/utils/log"
)
// Logger is not thread safety for operations on prefix
type Logger struct {
prefixes []string
prefixString string
}
func New() *Logger {
return &Logger{
prefixes: make([]string, 0),
}
}
func (l *Logger) ResetPrefixes() (old []string) {
old = l.prefixes
l.prefixes = make([]string, 0)
l.prefixString = ""
return
}
func (l *Logger) AppendPrefix(prefix string) *Logger {
l.prefixes = append(l.prefixes, prefix)
l.prefixString += "[" + prefix + "] "
return l
}
func (l *Logger) Spawn() *Logger {
nl := New()
for _, v := range l.prefixes {
nl.AppendPrefix(v)
}
return nl
}
func (l *Logger) Error(format string, v ...interface{}) {
log.Log.Error(l.prefixString+format, v...)
}
func (l *Logger) Warn(format string, v ...interface{}) {
log.Log.Warn(l.prefixString+format, v...)
}
func (l *Logger) Info(format string, v ...interface{}) {
log.Log.Info(l.prefixString+format, v...)
}
func (l *Logger) Debug(format string, v ...interface{}) {
log.Log.Debug(l.prefixString+format, v...)
}
func (l *Logger) Trace(format string, v ...interface{}) {
log.Log.Trace(l.prefixString+format, v...)
}