From dc6a5a29c11e3572e9f9347b096df900a1aa7583 Mon Sep 17 00:00:00 2001 From: zhouwenfeng Date: Sat, 31 Aug 2019 21:24:20 +0800 Subject: [PATCH 01/15] fix bad encryption and compression when use xtcp --- client/visitor.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/client/visitor.go b/client/visitor.go index 6eb3688c..e87310ce 100644 --- a/client/visitor.go +++ b/client/visitor.go @@ -293,18 +293,6 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { 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.KeepAliveInterval = 5 * time.Second fmuxCfg.LogOutput = ioutil.Discard @@ -320,6 +308,18 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { return } - frpIo.Join(userConn, muxConn) + var muxConnRWCloser io.ReadWriteCloser = muxConn + if sv.cfg.UseEncryption { + muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk)) + if err != nil { + sv.Error("create encryption stream error: %v", err) + return + } + } + if sv.cfg.UseCompression { + muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser) + } + + frpIo.Join(userConn, muxConnRWCloser) sv.Debug("join connections closed") } From bf0993d2a68e4f4e10465facdb9a58dc746c8abb Mon Sep 17 00:00:00 2001 From: Guy Lewin Date: Tue, 1 Oct 2019 15:58:35 -0400 Subject: [PATCH 02/15] Grammer fixes --- README.md | 90 +++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 2e3dacd9..fe46206a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name. -Now it also try to support p2p connect. +Now it also tries to support p2p connect. ## Table of Contents @@ -64,7 +64,7 @@ Now it also try to support p2p connect. ## 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 and you can try it with the latest release version. 'master' branch is for releasing stable version, 'dev' branch is for development. **We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.** @@ -172,7 +172,7 @@ However, we can expose a http or https service using frp. `./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, set remote frps's server IP as x.x.x.x, forward dns query request to Google's dns server `8.8.8.8:53`: ```ini # frpc.ini @@ -197,7 +197,7 @@ However, we can expose a http or https service using frp. ### Forward unix domain socket -Using tcp port to connect unix domain socket like docker daemon. +Use tcp port to connect to a unix domain socket (e.g. Docker daemon's socket). Configure frps same as above. @@ -222,7 +222,7 @@ Configure frps same as above. ### Expose a simple http file server -A simple way to visit files in the LAN. +A simple way to browse files in the LAN. Configure frps same as above. @@ -244,7 +244,7 @@ Configure frps same as above. 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/` by your browser, specify correct user and password, so you can see files in `/tmp/file`. ### Enable HTTPS for local HTTP service @@ -272,13 +272,13 @@ Configure frps same as above. ### Expose your service in security -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. -**stcp(secret tcp)** helps you create a proxy avoiding any one can access it. +**stcp(secret tcp)** helps you create a proxy while keeping the service secure. Configure frps same as above. -1. Start frpc, forward ssh port and `remote_port` is useless: +1. Start frpc, forward ssh port and `remote_port` are useless: ```ini # frpc.ini @@ -310,7 +310,7 @@ Configure frps same as above. bind_port = 6000 ``` -3. Connect to server in LAN by ssh assuming that username is test: +3. Connect to server in LAN using ssh assuming that username is test: `ssh -oPort=6000 test@127.0.0.1` @@ -318,7 +318,7 @@ Configure frps same as above. **xtcp** is designed for transmitting a large amount of data directly between two client. -Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work. +It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work. 1. Configure a udp port for xtcp: @@ -326,7 +326,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp* bind_udp_port = 7001 ``` -2. Start frpc, forward ssh port and `remote_port` is useless: +2. Start frpc, forward ssh port and `remote_port` are useless: ```ini # frpc.ini @@ -358,7 +358,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp* bind_port = 6000 ``` -4. Connect to server in LAN by ssh assuming that username is test: +4. Connect to server in LAN using ssh assuming that username is test: `ssh -oPort=6000 test@127.0.0.1` @@ -366,7 +366,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp* ### Configuration File -You can find features which this document not metioned from full example configuration files. +You can find features not mentioned in this document from the full example configuration files. [frps full configuration file](./conf/frps_full.ini) @@ -374,7 +374,7 @@ You can find features which this document not metioned from full example configu ### Configuration file template -Configuration file tempalte can be rendered using os environments. Template uses Go's standard format. +Configuration file template can be rendered using os environments. Template uses Go's standard format. ```ini # frpc.ini @@ -402,7 +402,7 @@ All environments has prefix `.Envs`. ### Dashboard -Check frp's status and proxies's statistics information by Dashboard. +Check frp's status and proxies' statistics information by Dashboard. Configure a port for dashboard to enable this feature: @@ -414,15 +414,15 @@ dashboard_user = admin dashboard_pwd = admin ``` -Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`. +Then visit `http://[server_addr]:7500` to see the dashboard, default username and password are both `admin`. ![dashboard](/doc/pic/dashboard.png) ### Admin UI -Admin UI help you check and manage frpc's configure. +Admin UI help 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 [common] @@ -436,11 +436,11 @@ Then visit `http://127.0.0.1:7400` to see admin UI, default username and passwor ### Authentication -`token` in frps.ini and frpc.ini should be same. +`token` in frps.ini and frpc.ini should be equal. ### Encryption and Compression -Defalut value is false, you could decide if the proxy will use encryption or compression: +Default value is false, you could decide if the proxy will use encryption or compression: ```ini # frpc.ini @@ -454,11 +454,11 @@ use_compression = true #### TLS -frp support TLS protocol between frpc and frps since v0.25.0. +frp supports TLS protocol between frpc and frps since v0.25.0. Config `tls_enable = true` in `common` section to frpc.ini to enable this feature. -For port multiplexing, frp send a first byte 0x17 to dial a TLS connection. +For port multiplexing, frp sends a first byte 0x17 to dial a TLS connection. ### Hot-Reload frpc configuration @@ -473,15 +473,15 @@ 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. -**Note that parameters in [common] section won't be modified except 'start' now.** +**Note that parameters in [common] section won't be modified except 'start'.** ### Get proxy status from client -Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file. +Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configuration file. ### Port White List -`allow_ports` in frps.ini is used for preventing abuse of ports: +`allow_ports` in frps.ini is used to prevent abuse of ports: ```ini # frps.ini @@ -493,7 +493,7 @@ allow_ports = 2000-3000,3001,3003,4000-50000 ### 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. @@ -525,7 +525,7 @@ Using kcp in frp: kcp_bind_port = 7000 ``` -2. Configure the protocol used in frpc to connect frps: +2. Configure the protocol used in frpc to connect to frps: ```ini # frpc.ini @@ -538,7 +538,7 @@ Using kcp in frp: ### Connection Pool -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 sends a message to frpc to create a new connection to the backward service when getting a user request. If a proxy's connection pool is enabled, there will be a specified number of connections pre-established. This feature is fit for a large number of short connections. @@ -585,9 +585,9 @@ group_key = 123 Proxies in same group will accept connections from port 80 randomly. -For `tcp` type, `remote_port` in one group shoud be same. +For `tcp` type, `remote_port` in the same group should be same. -For `http` type, `custom_domains, subdomain, locations` shoud be same. +For `http` type, `custom_domains, subdomain, locations` should be same. ### Health Check @@ -597,7 +597,7 @@ Add `health_check_type = {type}` to enable health check. **type** can be tcp or http. -Type tcp will dial the service port and type http will send a http rquest to service and require a 200 response. +Type tcp will dial the service port and type http will send a http request to the service and require a HTTP 200 response. Type tcp configuration: @@ -611,9 +611,9 @@ remote_port = 6000 health_check_type = tcp # dial timeout seconds health_check_timeout_s = 3 -# if continuous failed in 3 times, the proxy will be removed from frps +# if health check failed 3 times in a row, the proxy will be removed from frps health_check_max_failed = 3 -# every 10 seconds will do a health check +# health check every 10 seconds health_check_interval_s = 10 ``` @@ -664,20 +664,20 @@ host_header_rewrite = dev.yourdomain.com header_X-From-Where = frp ``` -Note that params which have prefix `header_` will be added to http request headers. +Note that parameters that have `header_` prefix will be added to http request headers. In this example, it will set header `X-From-Where: frp` to http request. ### Get Real IP #### HTTP X-Forwarded-For -Features for http proxy only. +These features are 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 the user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`. #### Proxy Protocol -frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP. +frp support Proxy Protocol to send user's real IP to local service. It support all types except UDP. Here is an example for https service: @@ -777,7 +777,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080 ### Range ports mapping -Proxy name has prefix `range:` will support mapping range ports. +Proxy name that has starts with `range:` will support mapping range ports. ```ini # frpc.ini @@ -792,9 +792,9 @@ frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`. ### Plugin -frpc only forward request to local tcp or udp port by default. +frpc only forwards request to local tcp or udp port by default. -Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `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. @@ -822,14 +822,14 @@ Interested in getting involved? We would like to help you! * Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**. * If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request. -* Sorry for my poor english and improvement for this document is welcome even some typo fix. -* If you have some wonderful ideas, send email to fatedier@gmail.com. +* Sorry for my poor English. Improvements for this document are welcome, even some typo fixes. +* If you have great ideas, send an email to fatedier@gmail.com. -**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.** +**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatedly.** ## Donation -If frp help you a lot, you can support us by: +If frp helps you a lot, you can support us by: frp QQ group: 606194980 From 5f8ed4fc609954366031886b8484daa7337f870f Mon Sep 17 00:00:00 2001 From: Guy Lewin Date: Wed, 9 Oct 2019 13:41:05 -0700 Subject: [PATCH 03/15] Fix CR --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe46206a..dbb977a6 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Now it also tries to support p2p connect. ## Status -frp is under development and you can try it with the latest release version. 'master' branch is for releasing stable version, 'dev' branch is for development. +frp is under development, you can try by using the latest release version under the 'master' branch. You can use the 'dev' branch instead for the version in development. **We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.** From 649f47c3455edb914c8a668813706e97e6770bb9 Mon Sep 17 00:00:00 2001 From: fatedier Date: Sat, 12 Oct 2019 20:13:12 +0800 Subject: [PATCH 04/15] change log method --- Makefile | 2 +- client/control.go | 73 +++++++++++------- client/health/health.go | 33 +++----- client/proxy/proxy.go | 115 +++++++++++++++------------- client/proxy/proxy_manager.go | 24 +++--- client/proxy/proxy_wrapper.go | 38 +++++---- client/service.go | 49 ++++++++---- client/visitor.go | 82 +++++++++++--------- client/visitor_manager.go | 24 +++--- cmd/frps/root.go | 2 +- go.sum | 14 ---- models/plugin/http_proxy.go | 2 +- models/plugin/https2http.go | 3 +- models/plugin/plugin.go | 6 +- models/plugin/socks5.go | 3 +- models/plugin/static_file.go | 3 +- models/plugin/unix_domain_socket.go | 4 +- server/control.go | 80 +++++++++++-------- server/controller/visitor.go | 3 +- server/group/http.go | 5 +- server/proxy/http.go | 12 +-- server/proxy/https.go | 11 ++- server/proxy/proxy.go | 65 +++++++++------- server/proxy/stcp.go | 4 +- server/proxy/tcp.go | 14 ++-- server/proxy/udp.go | 23 +++--- server/proxy/xtcp.go | 10 ++- server/service.go | 69 +++++++++-------- tests/ci/auto_test_frpc.ini | 3 +- tests/ci/auto_test_frps.ini | 3 +- tests/mock/echo_server.go | 4 +- tests/util/util.go | 3 +- utils/log/log.go | 68 ---------------- utils/net/conn.go | 80 +++++++++++-------- utils/net/kcp.go | 22 +++--- utils/net/listener.go | 49 +++--------- utils/net/tcp.go | 111 --------------------------- utils/net/tls.go | 10 +-- utils/net/udp.go | 30 ++++---- utils/net/websocket.go | 27 +++---- utils/vhost/https.go | 9 +-- utils/vhost/vhost.go | 51 ++++++------ utils/xlog/ctx.go | 42 ++++++++++ utils/xlog/xlog.go | 73 ++++++++++++++++++ 44 files changed, 670 insertions(+), 688 deletions(-) delete mode 100644 utils/net/tcp.go create mode 100644 utils/xlog/ctx.go create mode 100644 utils/xlog/xlog.go diff --git a/Makefile b/Makefile index f737d3f5..cc6ee6f0 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ file: fmt: go fmt ./... - + frps: go build -o bin/frps ./cmd/frps diff --git a/client/control.go b/client/control.go index 30992f9a..5589817f 100644 --- a/client/control.go +++ b/client/control.go @@ -15,9 +15,11 @@ package client import ( + "context" "crypto/tls" "fmt" "io" + "net" "runtime/debug" "sync" "time" @@ -25,8 +27,8 @@ import ( "github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" - "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/control/shutdown" "github.com/fatedier/golib/crypto" @@ -45,7 +47,7 @@ type Control struct { vm *VisitorManager // control connection - conn frpNet.Conn + conn net.Conn // tcp stream multiplexing, if enabled session *fmux.Session @@ -76,12 +78,19 @@ type Control struct { 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, - pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control { +func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.Session, + clientCfg config.ClientCommonConf, + pxyCfgs map[string]config.ProxyConf, + visitorCfgs map[string]config.VisitorConf, + serverUDPPort int) *Control { + // new xlog instance ctl := &Control{ runId: runId, conn: conn, @@ -96,11 +105,12 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg writerShutdown: shutdown.New(), msgHandlerShutdown: shutdown.New(), 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) return ctl } @@ -117,6 +127,7 @@ func (ctl *Control) Run() { } func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { + xl := ctl.xl workConn, err := ctl.connectServer() if err != nil { return @@ -126,31 +137,31 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { RunId: ctl.runId, } if err = msg.WriteMsg(workConn, m); err != nil { - ctl.Warn("work connection write to server error: %v", err) + xl.Warn("work connection write to server error: %v", err) workConn.Close() return } var startMsg msg.StartWorkConn if err = msg.ReadMsgInto(workConn, &startMsg); err != nil { - ctl.Error("work connection closed, %v", err) + xl.Error("work connection closed before response StartWorkConn message: %v", err) workConn.Close() return } - workConn.AddLogPrefix(startMsg.ProxyName) // dispatch this work connection to related proxy ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg) } func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) { + xl := ctl.xl // Server will return NewProxyResp message to each NewProxy message. // Start a new proxy handler if no error got err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error) if err != nil { - ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err) + xl.Warn("[%s] start error: %v", inMsg.ProxyName, err) } else { - ctl.Info("[%s] start proxy success", inMsg.ProxyName) + xl.Info("[%s] start proxy success", inMsg.ProxyName) } } @@ -169,15 +180,16 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} { } // 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 { stream, errRet := ctl.session.OpenStream() if errRet != nil { err = errRet - ctl.Warn("start new connection to server error: %v", err) + xl.Warn("start new connection to server error: %v", err) return } - conn = frpNet.WrapConn(stream) + conn = stream } else { var tlsConfig *tls.Config 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, fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig) if err != nil { - ctl.Warn("start new connection to server error: %v", err) + xl.Warn("start new connection to server error: %v", err) return } } @@ -197,10 +209,11 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { // reader read all messages from frps and send to readCh func (ctl *Control) reader() { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.Error("panic error: %v", err) - ctl.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() defer ctl.readerShutdown.Done() @@ -210,10 +223,10 @@ func (ctl *Control) reader() { for { if m, err := msg.ReadMsg(encReader); err != nil { if err == io.EOF { - ctl.Debug("read from control connection EOF") + xl.Debug("read from control connection EOF") return } else { - ctl.Warn("read error: %v", err) + xl.Warn("read error: %v", err) ctl.conn.Close() return } @@ -225,20 +238,21 @@ func (ctl *Control) reader() { // writer writes messages got from sendCh to frps func (ctl *Control) writer() { + xl := ctl.xl defer ctl.writerShutdown.Done() encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token)) if err != nil { - ctl.conn.Error("crypto new writer error: %v", err) + xl.Error("crypto new writer error: %v", err) ctl.conn.Close() return } for { if m, ok := <-ctl.sendCh; !ok { - ctl.Info("control writer is closing") + xl.Info("control writer is closing") return } else { 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 } } @@ -247,10 +261,11 @@ func (ctl *Control) writer() { // msgHandler handles all channel events and do corresponding operations. func (ctl *Control) msgHandler() { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.Error("panic error: %v", err) - ctl.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() defer ctl.msgHandlerShutdown.Done() @@ -266,11 +281,11 @@ func (ctl *Control) msgHandler() { select { case <-hbSend.C: // send heartbeat to server - ctl.Debug("send heartbeat to server") + xl.Debug("send heartbeat to server") ctl.sendCh <- &msg.Ping{} case <-hbCheck.C: if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second { - ctl.Warn("heartbeat timeout") + xl.Warn("heartbeat timeout") // let reader() stop ctl.conn.Close() return @@ -287,7 +302,7 @@ func (ctl *Control) msgHandler() { ctl.HandleNewProxyResp(m) case *msg.Pong: ctl.lastPong = time.Now() - ctl.Debug("receive heartbeat from server") + xl.Debug("receive heartbeat from server") } } } diff --git a/client/health/health.go b/client/health/health.go index 91ea707b..12771411 100644 --- a/client/health/health.go +++ b/client/health/health.go @@ -24,7 +24,7 @@ import ( "net/http" "time" - "github.com/fatedier/frp/utils/log" + "github.com/fatedier/frp/utils/xlog" ) var ( @@ -50,11 +50,11 @@ type HealthCheckMonitor struct { ctx context.Context cancel context.CancelFunc - - l log.Logger } -func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string, +func NewHealthCheckMonitor(ctx context.Context, checkType string, + intervalS int, timeoutS int, maxFailedTimes int, + addr string, url string, statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor { if intervalS <= 0 { @@ -66,7 +66,7 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai if maxFailedTimes <= 0 { maxFailedTimes = 1 } - ctx, cancel := context.WithCancel(context.Background()) + newctx, cancel := context.WithCancel(ctx) return &HealthCheckMonitor{ checkType: checkType, interval: time.Duration(intervalS) * time.Second, @@ -77,15 +77,11 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai statusOK: false, statusNormalFn: statusNormalFn, statusFailedFn: statusFailedFn, - ctx: ctx, + ctx: newctx, cancel: cancel, } } -func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) { - monitor.l = l -} - func (monitor *HealthCheckMonitor) Start() { go monitor.checkWorker() } @@ -95,6 +91,7 @@ func (monitor *HealthCheckMonitor) Stop() { } func (monitor *HealthCheckMonitor) checkWorker() { + xl := xlog.FromContextSafe(monitor.ctx) for { doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout)) err := monitor.doCheck(doCtx) @@ -109,25 +106,17 @@ func (monitor *HealthCheckMonitor) checkWorker() { } if err == nil { - if monitor.l != nil { - monitor.l.Trace("do one health check success") - } + xl.Trace("do one health check success") if !monitor.statusOK && monitor.statusNormalFn != nil { - if monitor.l != nil { - monitor.l.Info("health check status change to success") - } + xl.Info("health check status change to success") monitor.statusOK = true monitor.statusNormalFn() } } else { - if monitor.l != nil { - monitor.l.Warn("do one health check failed: %v", err) - } + xl.Warn("do one health check failed: %v", err) monitor.failedTimes++ if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil { - if monitor.l != nil { - monitor.l.Warn("health check status change to failed") - } + xl.Warn("health check status change to failed") monitor.statusOK = false monitor.statusFailedFn() } diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index f5e1b603..2da68ce8 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -16,6 +16,7 @@ package proxy import ( "bytes" + "context" "fmt" "io" "io/ioutil" @@ -29,8 +30,8 @@ import ( "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/plugin" "github.com/fatedier/frp/models/proto/udp" - "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/errors" frpIo "github.com/fatedier/golib/io" @@ -44,17 +45,17 @@ type Proxy interface { Run() error // InWorkConn accept work connections registered to server. - InWorkConn(frpNet.Conn, *msg.StartWorkConn) + InWorkConn(net.Conn, *msg.StartWorkConn) 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{ - Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), clientCfg: clientCfg, serverUDPPort: serverUDPPort, + xl: xlog.FromContextSafe(ctx), + ctx: ctx, } switch cfg := pxyConf.(type) { case *config.TcpProxyConf: @@ -93,10 +94,12 @@ func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serve type BaseProxy struct { closed bool - mu sync.RWMutex clientCfg config.ClientCommonConf serverUDPPort int - log.Logger + + mu sync.RWMutex + xl *xlog.Logger + ctx context.Context } // TCP @@ -123,8 +126,8 @@ func (pxy *TcpProxy) Close() { } } -func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, +func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, []byte(pxy.clientCfg.Token), m) } @@ -152,8 +155,8 @@ func (pxy *HttpProxy) Close() { } } -func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, +func (pxy *HttpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, []byte(pxy.clientCfg.Token), m) } @@ -181,8 +184,8 @@ func (pxy *HttpsProxy) Close() { } } -func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, +func (pxy *HttpsProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, []byte(pxy.clientCfg.Token), m) } @@ -210,8 +213,8 @@ func (pxy *StcpProxy) Close() { } } -func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, +func (pxy *StcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, []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() var natHoleSidMsg msg.NatHoleSid err := msg.ReadMsgInto(conn, &natHoleSidMsg) if err != nil { - pxy.Error("xtcp read from workConn error: %v", err) + xl.Error("xtcp read from workConn error: %v", err) return } @@ -259,7 +263,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { err = msg.WriteMsg(clientConn, natHoleClientMsg) if err != nil { - pxy.Error("send natHoleClientMsg to server error: %v", err) + xl.Error("send natHoleClientMsg to server error: %v", err) return } @@ -270,28 +274,28 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { buf := pool.GetBuf(1024) n, err := clientConn.Read(buf) if err != nil { - pxy.Error("get natHoleRespMsg error: %v", err) + xl.Error("get natHoleRespMsg error: %v", err) return } err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg) if err != nil { - pxy.Error("get natHoleRespMsg error: %v", err) + xl.Error("get natHoleRespMsg error: %v", err) return } clientConn.SetReadDeadline(time.Time{}) clientConn.Close() if natHoleRespMsg.Error != "" { - pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) + xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) return } - pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] 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 array := strings.Split(natHoleRespMsg.VisitorAddr, ":") 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()) /* @@ -301,18 +305,18 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { */ port, err := strconv.ParseInt(array[1], 10, 64) if err != nil { - pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) + xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) return } pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) - pxy.Trace("send all detect msg done") + xl.Trace("send all detect msg done") msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}) // Listen for clientConn's address and wait for visitor connection lConn, err := net.ListenUDP("udp", laddr) if err != nil { - pxy.Error("listen on visitorConn's local adress error: %v", err) + xl.Error("listen on visitorConn's local adress error: %v", err) return } defer lConn.Close() @@ -322,22 +326,22 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { var uAddr *net.UDPAddr n, uAddr, err = lConn.ReadFromUDP(sidBuf) if err != nil { - pxy.Warn("get sid from visitor error: %v", err) + xl.Warn("get sid from visitor error: %v", err) return } lConn.SetReadDeadline(time.Time{}) if string(sidBuf[:n]) != natHoleRespMsg.Sid { - pxy.Warn("incorrect sid from visitor") + xl.Warn("incorrect sid from visitor") return } 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) kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr) 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 } @@ -346,18 +350,18 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { fmuxCfg.LogOutput = ioutil.Discard sess, err := fmux.Server(kcpConn, fmuxCfg) 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 } defer sess.Close() muxConn, err := sess.Accept() if err != nil { - pxy.Error("accept for yamux connection error: %v", err) + xl.Error("accept for yamux connection error: %v", err) return } - HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, - frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m) + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, + muxConn, []byte(pxy.cfg.Sk), m) } 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 sendCh chan msg.Message - workConn frpNet.Conn + workConn net.Conn } func (pxy *UdpProxy) Run() (err error) { @@ -419,8 +423,9 @@ func (pxy *UdpProxy) Close() { } } -func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { - pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) +func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { + xl := pxy.xl + xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) // close resources releated with old workConn pxy.Close() @@ -435,32 +440,32 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { for { var udpMsg msg.UdpPacket if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil { - pxy.Warn("read from workConn for udp error: %v", errRet) + xl.Warn("read from workConn for udp error: %v", errRet) return } if errRet := errors.PanicToError(func() { - pxy.Trace("get udp package from workConn: %s", udpMsg.Content) + xl.Trace("get udp package from workConn: %s", udpMsg.Content) readCh <- &udpMsg }); errRet != nil { - pxy.Info("reader goroutine for udp work connection closed: %v", errRet) + xl.Info("reader goroutine for udp work connection closed: %v", errRet) return } } } workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) { defer func() { - pxy.Info("writer goroutine for udp work connection closed") + xl.Info("writer goroutine for udp work connection closed") }() var errRet error for rawMsg := range sendCh { switch m := rawMsg.(type) { case *msg.UdpPacket: - pxy.Trace("send udp package to workConn: %s", m.Content) + xl.Trace("send udp package to workConn: %s", m.Content) case *msg.Ping: - pxy.Trace("send ping message to udp workConn") + xl.Trace("send ping message to udp workConn") } if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil { - pxy.Error("udp work write error: %v", errRet) + xl.Error("udp work write error: %v", errRet) return } } @@ -472,7 +477,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { if errRet = errors.PanicToError(func() { sendCh <- &msg.Ping{} }); errRet != nil { - pxy.Trace("heartbeat goroutine for udp work connection closed") + xl.Trace("heartbeat goroutine for udp work connection closed") break } } @@ -485,20 +490,22 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { } // Common handler for tcp work connections. -func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin, - baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) { - +func HandleTcpWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin, + baseInfo *config.BaseProxyConf, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) { + xl := xlog.FromContextSafe(ctx) var ( remote io.ReadWriteCloser err error ) remote = workConn + xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t", + baseInfo.UseEncryption, baseInfo.UseCompression) if baseInfo.UseEncryption { remote, err = frpIo.WithEncryption(remote, encKey) if err != nil { workConn.Close() - workConn.Error("create encryption stream error: %v", err) + xl.Error("create encryption stream error: %v", err) return } } @@ -541,19 +548,19 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin. if proxyPlugin != nil { // 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) - workConn.Debug("handle by plugin finished") + xl.Debug("handle by plugin finished") return } else { localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort)) if err != nil { workConn.Close() - workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err) + xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err) return } - workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), + xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String()) if len(extraInfo) > 0 { @@ -561,6 +568,6 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin. } frpIo.Join(localConn, remote) - workConn.Debug("join connections closed") + xl.Debug("join connections closed") } } diff --git a/client/proxy/proxy_manager.go b/client/proxy/proxy_manager.go index 1d0d8786..a0afd70a 100644 --- a/client/proxy/proxy_manager.go +++ b/client/proxy/proxy_manager.go @@ -1,14 +1,15 @@ package proxy import ( + "context" "fmt" + "net" "sync" "github.com/fatedier/frp/client/event" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" - "github.com/fatedier/frp/utils/log" - frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/errors" ) @@ -25,19 +26,17 @@ type ProxyManager struct { // The UDP port that the server is listening on serverUDPPort int - logPrefix string - log.Logger + ctx context.Context } -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{ - proxies: make(map[string]*ProxyWrapper), sendCh: msgSendCh, + proxies: make(map[string]*ProxyWrapper), closed: false, clientCfg: clientCfg, serverUDPPort: serverUDPPort, - logPrefix: logPrefix, - Logger: log.NewPrefixLogger(logPrefix), + ctx: ctx, } } @@ -65,7 +64,7 @@ func (pm *ProxyManager) Close() { 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() pw, ok := pm.proxies[name] pm.mu.RUnlock() @@ -104,6 +103,7 @@ func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus { } func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) { + xl := xlog.FromContextSafe(pm.ctx) pm.mu.Lock() defer pm.mu.Unlock() @@ -127,13 +127,13 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) { } } if len(delPxyNames) > 0 { - pm.Info("proxy removed: %v", delPxyNames) + xl.Info("proxy removed: %v", delPxyNames) } addPxyNames := make([]string, 0) for name, cfg := range pxyCfgs { if _, ok := pm.proxies[name]; !ok { - pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort) + pxy := NewProxyWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort) pm.proxies[name] = pxy addPxyNames = append(addPxyNames, name) @@ -141,6 +141,6 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) { } } if len(addPxyNames) > 0 { - pm.Info("proxy added: %v", addPxyNames) + xl.Info("proxy added: %v", addPxyNames) } } diff --git a/client/proxy/proxy_wrapper.go b/client/proxy/proxy_wrapper.go index b02e5291..458fa438 100644 --- a/client/proxy/proxy_wrapper.go +++ b/client/proxy/proxy_wrapper.go @@ -1,7 +1,9 @@ package proxy import ( + "context" "fmt" + "net" "sync" "sync/atomic" "time" @@ -10,8 +12,7 @@ import ( "github.com/fatedier/frp/client/health" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" - "github.com/fatedier/frp/utils/log" - frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/errors" ) @@ -62,11 +63,13 @@ type ProxyWrapper struct { healthNotifyCh chan struct{} 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() + xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName) pw := &ProxyWrapper{ ProxyStatus: ProxyStatus{ Name: baseInfo.ProxyName, @@ -77,20 +80,19 @@ func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, ev closeCh: make(chan struct{}), healthNotifyCh: make(chan struct{}), handler: eventHandler, - Logger: log.NewPrefixLogger(logPrefix), + xl: xl, + ctx: xlog.NewContext(ctx, xl), } - pw.AddLogPrefix(pw.Name) if baseInfo.HealthCheckType != "" { pw.health = 1 // means failed - pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS, + pw.monitor = health.NewHealthCheckMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS, baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr, baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback) - pw.monitor.SetLogger(pw.Logger) - pw.Trace("enable health check monitor") + xl.Trace("enable health check monitor") } - pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort) + pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort) return pw } @@ -147,6 +149,7 @@ func (pw *ProxyWrapper) Stop() { } func (pw *ProxyWrapper) checkWorker() { + xl := pw.xl if pw.monitor != nil { // let monitor do check request first time.Sleep(500 * time.Millisecond) @@ -161,7 +164,7 @@ func (pw *ProxyWrapper) checkWorker() { (pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) || (pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) { - pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart) + xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart) pw.Status = ProxyStatusWaitStart var newProxyMsg msg.NewProxy @@ -180,7 +183,7 @@ func (pw *ProxyWrapper) checkWorker() { 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.mu.Unlock() @@ -196,6 +199,7 @@ func (pw *ProxyWrapper) checkWorker() { } func (pw *ProxyWrapper) statusNormalCallback() { + xl := pw.xl atomic.StoreUint32(&pw.health, 0) errors.PanicToError(func() { select { @@ -203,10 +207,11 @@ func (pw *ProxyWrapper) statusNormalCallback() { default: } }) - pw.Info("health check success") + xl.Info("health check success") } func (pw *ProxyWrapper) statusFailedCallback() { + xl := pw.xl atomic.StoreUint32(&pw.health, 1) errors.PanicToError(func() { select { @@ -214,15 +219,16 @@ func (pw *ProxyWrapper) statusFailedCallback() { 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() pxy := pw.pxy pw.mu.RUnlock() if pxy != nil { - workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) + xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) go pxy.InWorkConn(workConn, m) } else { workConn.Close() diff --git a/client/service.go b/client/service.go index 9f86ec7a..095df0aa 100644 --- a/client/service.go +++ b/client/service.go @@ -15,9 +15,11 @@ package client import ( + "context" "crypto/tls" "fmt" "io/ioutil" + "net" "runtime" "sync" "sync/atomic" @@ -30,6 +32,7 @@ import ( frpNet "github.com/fatedier/frp/utils/net" "github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/version" + "github.com/fatedier/frp/utils/xlog" fmux "github.com/hashicorp/yamux" ) @@ -55,19 +58,25 @@ type Service struct { // This is configured by the login response from frps serverUDPPort int - exit uint32 // 0 means not exit - closedCh chan int + exit uint32 // 0 means not exit + + // 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) { + + ctx, cancel := context.WithCancel(context.Background()) svr = &Service{ cfg: cfg, cfgFile: cfgFile, pxyCfgs: pxyCfgs, visitorCfgs: visitorCfgs, exit: 0, - closedCh: make(chan int), + ctx: xlog.NewContext(ctx, xlog.New()), + cancel: cancel, } return } @@ -79,11 +88,13 @@ func (svr *Service) GetController() *Control { } func (svr *Service) Run() error { - // first login + xl := xlog.FromContextSafe(svr.ctx) + + // login to frps for { conn, session, err := svr.login() if err != nil { - log.Warn("login to server failed: %v", err) + xl.Warn("login to server failed: %v", err) // if login_fail_exit is true, just exit this program // otherwise sleep a while and try again to connect to server @@ -94,7 +105,7 @@ func (svr *Service) Run() error { } } else { // 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() svr.ctlMu.Lock() 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) } - - <-svr.closedCh + <-svr.ctx.Done() return nil } func (svr *Service) keepControllerWorking() { + xl := xlog.FromContextSafe(svr.ctx) maxDelayTime := 20 * time.Second delayTime := time.Second @@ -134,10 +145,10 @@ func (svr *Service) keepControllerWorking() { } for { - log.Info("try to reconnect to server...") + xl.Info("try to reconnect to server...") conn, session, err := svr.login() if err != nil { - log.Warn("reconnect to server error: %v", err) + xl.Warn("reconnect to server error: %v", err) time.Sleep(delayTime) delayTime = delayTime * 2 if delayTime > maxDelayTime { @@ -148,7 +159,7 @@ func (svr *Service) keepControllerWorking() { // reconnect success, init delayTime 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() svr.ctlMu.Lock() svr.ctl = ctl @@ -161,7 +172,8 @@ func (svr *Service) keepControllerWorking() { // login creates a connection to frps and registers it self as a client // conn: control connection // session: if it's not nil, using tcp mux -func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) { +func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) { + xl := xlog.FromContextSafe(svr.ctx) var tlsConfig *tls.Config if svr.cfg.TLSEnable { tlsConfig = &tls.Config{ @@ -197,7 +209,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) err = errRet return } - conn = frpNet.WrapConn(stream) + conn = stream } now := time.Now().Unix() @@ -225,13 +237,16 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) if loginRespMsg.Error != "" { err = fmt.Errorf("%s", loginRespMsg.Error) - log.Error("%s", loginRespMsg.Error) + xl.Error("%s", loginRespMsg.Error) return } svr.runId = loginRespMsg.RunId + xl.ResetPrefixes() + xl.AppendPrefix(svr.runId) + 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 } @@ -247,5 +262,5 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs func (svr *Service) Close() { atomic.StoreUint32(&svr.exit, 1) svr.ctl.Close() - close(svr.closedCh) + svr.cancel() } diff --git a/client/visitor.go b/client/visitor.go index c1ee0040..a4900e06 100644 --- a/client/visitor.go +++ b/client/visitor.go @@ -16,6 +16,7 @@ package client import ( "bytes" + "context" "fmt" "io" "io/ioutil" @@ -25,9 +26,9 @@ import ( "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" - "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" "github.com/fatedier/frp/utils/util" + "github.com/fatedier/frp/utils/xlog" frpIo "github.com/fatedier/golib/io" "github.com/fatedier/golib/pool" @@ -38,13 +39,13 @@ import ( type Visitor interface { Run() error Close() - log.Logger } -func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) { +func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) { + xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName) baseVisitor := BaseVisitor{ - ctl: ctl, - Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName), + ctl: ctl, + ctx: xlog.NewContext(ctx, xl), } switch cfg := cfg.(type) { case *config.StcpVisitorConf: @@ -63,10 +64,11 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) { type BaseVisitor struct { ctl *Control - l frpNet.Listener + l net.Listener closed bool - mu sync.RWMutex - log.Logger + + mu sync.RWMutex + ctx context.Context } type StcpVisitor struct { @@ -76,7 +78,7 @@ type StcpVisitor struct { } 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 { return } @@ -90,10 +92,11 @@ func (sv *StcpVisitor) Close() { } func (sv *StcpVisitor) worker() { + xl := xlog.FromContextSafe(sv.ctx) for { conn, err := sv.l.Accept() if err != nil { - sv.Warn("stcp local listener closed") + xl.Warn("stcp local listener closed") return } @@ -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() - sv.Debug("get a new stcp user connection") + xl.Debug("get a new stcp user connection") visitorConn, err := sv.ctl.connectServer() if err != nil { return @@ -121,7 +125,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) { } err = msg.WriteMsg(visitorConn, newVisitorConnMsg) if err != nil { - sv.Warn("send newVisitorConnMsg to server error: %v", err) + xl.Warn("send newVisitorConnMsg to server error: %v", err) return } @@ -129,13 +133,13 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) { visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second)) err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg) if err != nil { - sv.Warn("get newVisitorConnRespMsg error: %v", err) + xl.Warn("get newVisitorConnRespMsg error: %v", err) return } visitorConn.SetReadDeadline(time.Time{}) if newVisitorConnRespMsg.Error != "" { - sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error) + xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error) return } @@ -144,7 +148,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) { if sv.cfg.UseEncryption { remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk)) if err != nil { - sv.Error("create encryption stream error: %v", err) + xl.Error("create encryption stream error: %v", err) return } } @@ -163,7 +167,7 @@ type XtcpVisitor struct { } 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 { return } @@ -177,10 +181,11 @@ func (sv *XtcpVisitor) Close() { } func (sv *XtcpVisitor) worker() { + xl := xlog.FromContextSafe(sv.ctx) for { conn, err := sv.l.Accept() if err != nil { - sv.Warn("xtcp local listener closed") + xl.Warn("xtcp local listener closed") return } @@ -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() - sv.Debug("get a new xtcp user connection") + xl.Debug("get a new xtcp user connection") if sv.ctl.serverUDPPort == 0 { - sv.Error("xtcp is not supported by server") + xl.Error("xtcp is not supported by server") return } raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort)) if err != nil { - sv.Error("resolve server UDP addr error") + xl.Error("resolve server UDP addr error") return } visitorConn, err := net.DialUDP("udp", nil, raddr) if err != nil { - sv.Warn("dial server udp addr error: %v", err) + xl.Warn("dial server udp addr error: %v", err) return } defer visitorConn.Close() @@ -219,7 +225,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { } err = msg.WriteMsg(visitorConn, natHoleVisitorMsg) if err != nil { - sv.Warn("send natHoleVisitorMsg to server error: %v", err) + xl.Warn("send natHoleVisitorMsg to server error: %v", err) return } @@ -229,24 +235,24 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { buf := pool.GetBuf(1024) n, err := visitorConn.Read(buf) if err != nil { - sv.Warn("get natHoleRespMsg error: %v", err) + xl.Warn("get natHoleRespMsg error: %v", err) return } err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg) if err != nil { - sv.Warn("get natHoleRespMsg error: %v", err) + xl.Warn("get natHoleRespMsg error: %v", err) return } visitorConn.SetReadDeadline(time.Time{}) pool.PutBuf(buf) if natHoleRespMsg.Error != "" { - sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) + xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) return } - sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], 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. visitorConn.Close() @@ -255,12 +261,12 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String()) daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr) if err != nil { - sv.Error("resolve client udp address error: %v", err) + xl.Error("resolve client udp address error: %v", err) return } lConn, err := net.DialUDP("udp", laddr, daddr) if err != nil { - sv.Error("dial client udp address error: %v", err) + xl.Error("dial client udp address error: %v", err) return } defer lConn.Close() @@ -272,23 +278,23 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { lConn.SetReadDeadline(time.Now().Add(8 * time.Second)) n, err = lConn.Read(sidBuf) if err != nil { - sv.Warn("get sid from client error: %v", err) + xl.Warn("get sid from client error: %v", err) return } lConn.SetReadDeadline(time.Time{}) if string(sidBuf[:n]) != natHoleRespMsg.Sid { - sv.Warn("incorrect sid from client") + xl.Warn("incorrect sid from client") return } 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 var remote io.ReadWriteCloser remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr) if err != nil { - sv.Error("create kcp connection from udp connection error: %v", err) + xl.Error("create kcp connection from udp connection error: %v", err) return } @@ -297,13 +303,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { fmuxCfg.LogOutput = ioutil.Discard sess, err := fmux.Client(remote, fmuxCfg) if err != nil { - sv.Error("create yamux session error: %v", err) + xl.Error("create yamux session error: %v", err) return } defer sess.Close() muxConn, err := sess.Open() if err != nil { - sv.Error("open yamux stream error: %v", err) + xl.Error("open yamux stream error: %v", err) return } @@ -311,7 +317,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { if sv.cfg.UseEncryption { muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk)) if err != nil { - sv.Error("create encryption stream error: %v", err) + xl.Error("create encryption stream error: %v", err) return } } @@ -320,5 +326,5 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { } frpIo.Join(userConn, muxConnRWCloser) - sv.Debug("join connections closed") + xl.Debug("join connections closed") } diff --git a/client/visitor_manager.go b/client/visitor_manager.go index b223d55d..3b9351e7 100644 --- a/client/visitor_manager.go +++ b/client/visitor_manager.go @@ -15,11 +15,12 @@ package client import ( + "context" "sync" "time" "github.com/fatedier/frp/models/config" - "github.com/fatedier/frp/utils/log" + "github.com/fatedier/frp/utils/xlog" ) type VisitorManager struct { @@ -30,26 +31,29 @@ type VisitorManager struct { 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{ ctl: ctl, cfgs: make(map[string]config.VisitorConf), visitors: make(map[string]Visitor), checkInterval: 10 * time.Second, + ctx: ctx, } } func (vm *VisitorManager) Run() { + xl := xlog.FromContextSafe(vm.ctx) for { time.Sleep(vm.checkInterval) vm.mu.Lock() for _, cfg := range vm.cfgs { name := cfg.GetBaseInfo().ProxyName if _, exist := vm.visitors[name]; !exist { - log.Info("try to start visitor [%s]", name) + xl.Info("try to start visitor [%s]", name) vm.startVisitor(cfg) } } @@ -59,19 +63,21 @@ func (vm *VisitorManager) Run() { // Hold lock before calling this function. func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) { + xl := xlog.FromContextSafe(vm.ctx) name := cfg.GetBaseInfo().ProxyName - visitor := NewVisitor(vm.ctl, cfg) + visitor := NewVisitor(vm.ctx, vm.ctl, cfg) err = visitor.Run() if err != nil { - visitor.Warn("start error: %v", err) + xl.Warn("start error: %v", err) } else { vm.visitors[name] = visitor - visitor.Info("start visitor success") + xl.Info("start visitor success") } return } func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) { + xl := xlog.FromContextSafe(vm.ctx) vm.mu.Lock() defer vm.mu.Unlock() @@ -97,7 +103,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) { } } if len(delNames) > 0 { - log.Info("visitor removed: %v", delNames) + xl.Info("visitor removed: %v", delNames) } addNames := make([]string, 0) @@ -109,7 +115,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) { } } if len(addNames) > 0 { - log.Info("visitor added: %v", addNames) + xl.Info("visitor added: %v", addNames) } return } diff --git a/cmd/frps/root.go b/cmd/frps/root.go index fd6cdbde..ec175fe3 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -202,7 +202,7 @@ func runServer(cfg config.ServerCommonConf) (err error) { if err != nil { return err } - log.Info("Start frps success") + log.Info("start frps success") svr.Run() return } diff --git a/go.sum b/go.sum index 5ddda4ef..b9bdf2a0 100644 --- a/go.sum +++ b/go.sum @@ -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/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/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8= github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM= github.com/fatedier/kcp-go 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/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/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= 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/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8= github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 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/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg= github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= -github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY= github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= 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/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 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/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk= github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= -github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw= github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= -github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= 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= diff --git a/models/plugin/http_proxy.go b/models/plugin/http_proxy.go index 3afa2cb8..b0ca9231 100644 --- a/models/plugin/http_proxy.go +++ b/models/plugin/http_proxy.go @@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string { 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) sc, rd := gnet.NewSharedConn(wrapConn) diff --git a/models/plugin/https2http.go b/models/plugin/https2http.go index f840ebde..65540356 100644 --- a/models/plugin/https2http.go +++ b/models/plugin/https2http.go @@ -18,6 +18,7 @@ import ( "crypto/tls" "fmt" "io" + "net" "net/http" "net/http/httputil" "strings" @@ -115,7 +116,7 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) { 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) p.l.PutConn(wrapConn) } diff --git a/models/plugin/plugin.go b/models/plugin/plugin.go index cfad5510..6850919a 100644 --- a/models/plugin/plugin.go +++ b/models/plugin/plugin.go @@ -20,8 +20,6 @@ import ( "net" "sync" - frpNet "github.com/fatedier/frp/utils/net" - "github.com/fatedier/golib/errors" ) @@ -46,7 +44,9 @@ func Create(name string, params map[string]string) (p Plugin, err error) { type Plugin interface { Name() string - Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []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 } diff --git a/models/plugin/socks5.go b/models/plugin/socks5.go index 447602a9..dc089345 100644 --- a/models/plugin/socks5.go +++ b/models/plugin/socks5.go @@ -18,6 +18,7 @@ import ( "io" "io/ioutil" "log" + "net" frpNet "github.com/fatedier/frp/utils/net" @@ -53,7 +54,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) { return } -func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) { +func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { defer conn.Close() wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) sp.Server.ServeConn(wrapConn) diff --git a/models/plugin/static_file.go b/models/plugin/static_file.go index 080ff74f..32325317 100644 --- a/models/plugin/static_file.go +++ b/models/plugin/static_file.go @@ -16,6 +16,7 @@ package plugin import ( "io" + "net" "net/http" frpNet "github.com/fatedier/frp/utils/net" @@ -72,7 +73,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) { return sp, nil } -func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) { +func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) sp.l.PutConn(wrapConn) } diff --git a/models/plugin/unix_domain_socket.go b/models/plugin/unix_domain_socket.go index 86833e25..a85ada70 100644 --- a/models/plugin/unix_domain_socket.go +++ b/models/plugin/unix_domain_socket.go @@ -19,8 +19,6 @@ import ( "io" "net" - frpNet "github.com/fatedier/frp/utils/net" - frpIo "github.com/fatedier/golib/io" ) @@ -53,7 +51,7 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) { 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) if err != nil { return diff --git a/server/control.go b/server/control.go index bb802a75..0db61987 100644 --- a/server/control.go +++ b/server/control.go @@ -15,8 +15,10 @@ package server import ( + "context" "fmt" "io" + "net" "runtime/debug" "sync" "time" @@ -28,8 +30,8 @@ import ( "github.com/fatedier/frp/server/controller" "github.com/fatedier/frp/server/proxy" "github.com/fatedier/frp/server/stats" - "github.com/fatedier/frp/utils/net" "github.com/fatedier/frp/utils/version" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/control/shutdown" "github.com/fatedier/golib/crypto" @@ -131,9 +133,12 @@ type Control struct { // Server configuration information 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, serverCfg config.ServerCommonConf) *Control { @@ -161,6 +166,8 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage managerShutdown: shutdown.New(), allShutdown: shutdown.New(), serverCfg: serverCfg, + xl: xlog.FromContextSafe(ctx), + ctx: ctx, } } @@ -185,18 +192,19 @@ func (ctl *Control) Start() { } func (ctl *Control) RegisterWorkConn(conn net.Conn) { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.conn.Error("panic error: %v", err) - ctl.conn.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() select { case ctl.workConnCh <- conn: - ctl.conn.Debug("new work connection registered") + xl.Debug("new work connection registered") default: - ctl.conn.Debug("work connection pool is full, discarding") + xl.Debug("work connection pool is full, discarding") conn.Close() } } @@ -206,10 +214,11 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) { // and wait until it is available. // return an error if wait timeout func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.conn.Error("panic error: %v", err) - ctl.conn.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() @@ -221,14 +230,14 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { err = frpErr.ErrCtlClosed return } - ctl.conn.Debug("get work connection from pool") + xl.Debug("get work connection from pool") default: // no work connections available in the poll, send message to frpc to get more err = errors.PanicToError(func() { ctl.sendCh <- &msg.ReqWorkConn{} }) if err != nil { - ctl.conn.Error("%v", err) + xl.Error("%v", err) return } @@ -236,13 +245,13 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { case workConn, ok = <-ctl.workConnCh: if !ok { err = frpErr.ErrCtlClosed - ctl.conn.Warn("no work connections avaiable, %v", err) + xl.Warn("no work connections avaiable, %v", err) return } case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second): err = fmt.Errorf("timeout trying to get work connection") - ctl.conn.Warn("%v", err) + xl.Warn("%v", err) return } } @@ -255,16 +264,18 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { } 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.allShutdown.Start() } func (ctl *Control) writer() { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.conn.Error("panic error: %v", err) - ctl.conn.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() @@ -273,17 +284,17 @@ func (ctl *Control) writer() { encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token)) if err != nil { - ctl.conn.Error("crypto new writer error: %v", err) + xl.Error("crypto new writer error: %v", err) ctl.allShutdown.Start() return } for { if m, ok := <-ctl.sendCh; !ok { - ctl.conn.Info("control writer is closing") + xl.Info("control writer is closing") return } else { 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 } } @@ -291,10 +302,11 @@ func (ctl *Control) writer() { } func (ctl *Control) reader() { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.conn.Error("panic error: %v", err) - ctl.conn.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() @@ -305,10 +317,10 @@ func (ctl *Control) reader() { for { if m, err := msg.ReadMsg(encReader); err != nil { if err == io.EOF { - ctl.conn.Debug("control connection closed") + xl.Debug("control connection closed") return } else { - ctl.conn.Warn("read error: %v", err) + xl.Warn("read error: %v", err) ctl.conn.Close() return } @@ -319,10 +331,11 @@ func (ctl *Control) reader() { } func (ctl *Control) stoper() { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.conn.Error("panic error: %v", err) - ctl.conn.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() @@ -355,7 +368,7 @@ func (ctl *Control) stoper() { } ctl.allShutdown.Done() - ctl.conn.Info("client exit success") + xl.Info("client exit success") ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{}) } @@ -366,10 +379,11 @@ func (ctl *Control) WaitClosed() { } func (ctl *Control) manager() { + xl := ctl.xl defer func() { if err := recover(); err != nil { - ctl.conn.Error("panic error: %v", err) - ctl.conn.Error(string(debug.Stack())) + xl.Error("panic error: %v", err) + xl.Error(string(debug.Stack())) } }() @@ -383,7 +397,7 @@ func (ctl *Control) manager() { select { case <-heartbeat.C: if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second { - ctl.conn.Warn("heartbeat timeout") + xl.Warn("heartbeat timeout") return } case rawMsg, ok := <-ctl.readCh: @@ -400,10 +414,10 @@ func (ctl *Control) manager() { } if err != nil { 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 { 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{ Name: m.ProxyName, ProxyType: m.ProxyType, @@ -412,10 +426,10 @@ func (ctl *Control) manager() { ctl.sendCh <- resp case *msg.CloseProxy: ctl.CloseProxy(m) - ctl.conn.Info("close proxy [%s] success", m.ProxyName) + xl.Info("close proxy [%s] success", m.ProxyName) case *msg.Ping: ctl.lastPing = time.Now() - ctl.conn.Debug("receive heartbeat") + xl.Debug("receive heartbeat") ctl.sendCh <- &msg.Pong{} } } @@ -432,7 +446,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err // NewProxy will return a interface Proxy. // In fact it create different proxies by different proxy type, we just call run() here. - pxy, err := 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 { return remoteAddr, err } diff --git a/server/controller/visitor.go b/server/controller/visitor.go index ea8a53e8..22ff6942 100644 --- a/server/controller/visitor.go +++ b/server/controller/visitor.go @@ -17,6 +17,7 @@ package controller import ( "fmt" "io" + "net" "sync" frpNet "github.com/fatedier/frp/utils/net" @@ -55,7 +56,7 @@ func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListen 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) { vm.mu.RLock() diff --git a/server/group/http.go b/server/group/http.go index 538dccf3..31609680 100644 --- a/server/group/http.go +++ b/server/group/http.go @@ -2,11 +2,10 @@ package group import ( "fmt" + "net" "sync" "sync/atomic" - frpNet "github.com/fatedier/frp/utils/net" - "github.com/fatedier/frp/utils/vhost" ) @@ -131,7 +130,7 @@ func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) { return } -func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) { +func (g *HTTPGroup) createConn(remoteAddr string) (net.Conn, error) { var f vhost.CreateConnFunc newIndex := atomic.AddUint64(&g.index, 1) diff --git a/server/proxy/http.go b/server/proxy/http.go index 5bd3139d..09fb3e44 100644 --- a/server/proxy/http.go +++ b/server/proxy/http.go @@ -36,6 +36,7 @@ type HttpProxy struct { } func (pxy *HttpProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl routeConfig := vhost.VhostRouteConfig{ RewriteHost: pxy.cfg.HostHeaderRewrite, 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))) - 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)) - 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, ",") @@ -131,10 +132,11 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf { 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) 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 } @@ -148,7 +150,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err if pxy.cfg.UseEncryption { rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token)) if err != nil { - pxy.Error("create encryption stream error: %v", err) + xl.Error("create encryption stream error: %v", err) return } } diff --git a/server/proxy/https.go b/server/proxy/https.go index 87840421..0191496f 100644 --- a/server/proxy/https.go +++ b/server/proxy/https.go @@ -28,6 +28,7 @@ type HttpsProxy struct { } func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl routeConfig := &vhost.VhostRouteConfig{} defer func() { @@ -42,26 +43,24 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { } routeConfig.Domain = domain - l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) + l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig) if errRet != nil { err = errRet return } - l.AddLogPrefix(pxy.name) - pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) + xl.Info("https proxy listen for host [%s]", routeConfig.Domain) pxy.listeners = append(pxy.listeners, l) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort)) } if pxy.cfg.SubDomain != "" { 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 { err = errRet return } - l.AddLogPrefix(pxy.name) - pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) + xl.Info("https proxy listen for host [%s]", routeConfig.Domain) pxy.listeners = append(pxy.listeners, l) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort))) } diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go index 627b0e15..84a04e61 100644 --- a/server/proxy/proxy.go +++ b/server/proxy/proxy.go @@ -15,6 +15,7 @@ package proxy import ( + "context" "fmt" "io" "net" @@ -25,48 +26,54 @@ import ( "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/server/controller" "github.com/fatedier/frp/server/stats" - "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/xlog" frpIo "github.com/fatedier/golib/io" ) -type GetWorkConnFn func() (frpNet.Conn, error) +type GetWorkConnFn func() (net.Conn, error) type Proxy interface { + Context() context.Context Run() (remoteAddr string, err error) GetName() string 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 Close() - log.Logger } type BaseProxy struct { name string rc *controller.ResourceController statsCollector stats.Collector - listeners []frpNet.Listener + listeners []net.Listener usedPortsNum int poolCount int getWorkConnFn GetWorkConnFn serverCfg config.ServerCommonConf - mu sync.RWMutex - log.Logger + mu sync.RWMutex + xl *xlog.Logger + ctx context.Context } func (pxy *BaseProxy) GetName() string { return pxy.name } +func (pxy *BaseProxy) Context() context.Context { + return pxy.ctx +} + func (pxy *BaseProxy) GetUsedPortsNum() int { return pxy.usedPortsNum } func (pxy *BaseProxy) Close() { - pxy.Info("proxy closing") + xl := xlog.FromContextSafe(pxy.ctx) + xl.Info("proxy closing") for _, l := range pxy.listeners { l.Close() } @@ -74,15 +81,17 @@ func (pxy *BaseProxy) Close() { // 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 -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 for i := 0; i < pxy.poolCount+1; i++ { 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 } - pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String()) - workConn.AddLogPrefix(pxy.GetName()) + xl.Info("get a new work connection: [%s]", workConn.RemoteAddr().String()) + xl.Spawn().AppendPrefix(pxy.GetName()) + workConn = frpNet.NewContextConn(workConn, pxy.ctx) var ( srcAddr string @@ -109,7 +118,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co DstPort: uint16(dstPort), }) 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() } else { break @@ -117,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co } 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 @@ -126,36 +135,39 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co // startListenHandler start a goroutine handler for each listener. // p: p will just be passed to handler(Proxy, frpNet.Conn). // handler: each proxy type can set different handler function to deal with connections accepted from listeners. -func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, 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 { - go func(l frpNet.Listener) { + go func(l net.Listener) { for { // block // if listener is closed, err returned c, err := l.Accept() if err != nil { - pxy.Info("listener is closed") + xl.Info("listener is closed") 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) } }(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) { + xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName) basePxy := BaseProxy{ name: pxyConf.GetBaseInfo().ProxyName, rc: rc, statsCollector: statsCollector, - listeners: make([]frpNet.Listener, 0), + listeners: make([]net.Listener, 0), poolCount: poolCount, getWorkConnFn: getWorkConnFn, - Logger: log.NewPrefixLogger(runId), serverCfg: serverCfg, + xl: xl, + ctx: xlog.NewContext(ctx, xl), } switch cfg := pxyConf.(type) { case *config.TcpProxyConf: @@ -193,13 +205,13 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st default: return pxy, fmt.Errorf("proxy type not support") } - pxy.AddLogPrefix(pxy.GetName()) return } // HandleUserTcpConnection is used for incoming tcp user connections. // It can be used for tcp, http, https type. -func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) { +func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) { + xl := xlog.FromContextSafe(pxy.Context()) defer userConn.Close() // try all connections from the pool @@ -211,17 +223,18 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta var local io.ReadWriteCloser = workConn cfg := pxy.GetConf().GetBaseInfo() + xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.UseEncryption, cfg.UseCompression) if cfg.UseEncryption { local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token)) if err != nil { - pxy.Error("create encryption stream error: %v", err) + xl.Error("create encryption stream error: %v", err) return } } if cfg.UseCompression { local = frpIo.WithCompression(local) } - pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(), + 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()) 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(), TrafficBytes: outCount, }) - pxy.Debug("join connections closed") + xl.Debug("join connections closed") } type ProxyManager struct { diff --git a/server/proxy/stcp.go b/server/proxy/stcp.go index 8cd43b20..27bfb143 100644 --- a/server/proxy/stcp.go +++ b/server/proxy/stcp.go @@ -24,14 +24,14 @@ type StcpProxy struct { } func (pxy *StcpProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) if errRet != nil { err = errRet return } - listener.AddLogPrefix(pxy.name) pxy.listeners = append(pxy.listeners, listener) - pxy.Info("stcp proxy custom listen success") + xl.Info("stcp proxy custom listen success") pxy.startListenHandler(pxy, HandleUserTcpConnection) return diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go index 388531a6..0ecfe260 100644 --- a/server/proxy/tcp.go +++ b/server/proxy/tcp.go @@ -16,9 +16,9 @@ package proxy import ( "fmt" + "net" "github.com/fatedier/frp/models/config" - frpNet "github.com/fatedier/frp/utils/net" ) type TcpProxy struct { @@ -29,6 +29,7 @@ type TcpProxy struct { } func (pxy *TcpProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl 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) if errRet != nil { @@ -41,10 +42,8 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) { } }() pxy.realPort = realPort - listener := frpNet.WrapLogListener(l) - listener.AddLogPrefix(pxy.name) - pxy.listeners = append(pxy.listeners, listener) - pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) + pxy.listeners = append(pxy.listeners, l) + xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) } else { pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) if err != nil { @@ -55,14 +54,13 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) { 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 { err = errRet return } - listener.AddLogPrefix(pxy.name) 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 diff --git a/server/proxy/udp.go b/server/proxy/udp.go index 453c7b56..397f60fb 100644 --- a/server/proxy/udp.go +++ b/server/proxy/udp.go @@ -54,6 +54,7 @@ type UdpProxy struct { } func (pxy *UdpProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) if err != nil { return @@ -74,10 +75,10 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) { udpConn, errRet := net.ListenUDP("udp", addr) if errRet != nil { err = errRet - pxy.Warn("listen udp port error: %v", err) + xl.Warn("listen udp port error: %v", err) 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.sendCh = make(chan *msg.UdpPacket, 1024) @@ -91,11 +92,11 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) { rawMsg msg.Message 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 conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second)) if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil { - pxy.Warn("read from workConn for udp error: %v", errRet) + xl.Warn("read from workConn for udp error: %v", errRet) conn.Close() // notify proxy to start a new work connection // ignore error here, it means the proxy is closed @@ -107,11 +108,11 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) { conn.SetReadDeadline(time.Time{}) switch m := rawMsg.(type) { case *msg.Ping: - pxy.Trace("udp work conn get ping message") + xl.Trace("udp work conn get ping message") continue case *msg.UdpPacket: if errRet := errors.PanicToError(func() { - pxy.Trace("get udp message from workConn: %s", m.Content) + xl.Trace("get udp message from workConn: %s", m.Content) pxy.readCh <- m pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{ ProxyName: pxy.GetName(), @@ -119,7 +120,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) { }) }); errRet != nil { conn.Close() - pxy.Info("reader goroutine for udp work connection closed") + xl.Info("reader goroutine for udp work connection closed") return } } @@ -133,15 +134,15 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) { select { case udpMsg, ok := <-pxy.sendCh: if !ok { - pxy.Info("sender goroutine for udp work connection closed") + xl.Info("sender goroutine for udp work connection closed") return } if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil { - pxy.Info("sender goroutine for udp work connection closed: %v", errRet) + xl.Info("sender goroutine for udp work connection closed: %v", errRet) conn.Close() return } 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{ ProxyName: pxy.GetName(), TrafficBytes: int64(len(udpMsg.Content)), @@ -149,7 +150,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) { continue } case <-ctx.Done(): - pxy.Info("sender goroutine for udp work connection closed") + xl.Info("sender goroutine for udp work connection closed") return } } diff --git a/server/proxy/xtcp.go b/server/proxy/xtcp.go index 925485c1..92291c06 100644 --- a/server/proxy/xtcp.go +++ b/server/proxy/xtcp.go @@ -31,8 +31,10 @@ type XtcpProxy struct { } func (pxy *XtcpProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl + 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") return } @@ -53,7 +55,7 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) { } errRet = msg.WriteMsg(workConn, m) 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() break } @@ -61,12 +63,12 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) { go func() { raw, errRet := msg.ReadMsg(workConn) 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() return } 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() return } diff --git a/server/service.go b/server/service.go index 6d561016..a4d2e9df 100644 --- a/server/service.go +++ b/server/service.go @@ -16,6 +16,7 @@ package server import ( "bytes" + "context" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -42,6 +43,7 @@ import ( "github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/vhost" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/net/mux" fmux "github.com/hashicorp/yamux" @@ -57,16 +59,16 @@ type Service struct { muxer *mux.Mux // Accept connections from client - listener frpNet.Listener + listener net.Listener // Accept connections using kcp - kcpListener frpNet.Listener + kcpListener net.Listener // Accept connections using websocket - websocketListener frpNet.Listener + websocketListener net.Listener // Accept frp tls connections - tlsListener frpNet.Listener + tlsListener net.Listener // Manage all controllers ctlManager *ControlManager @@ -135,7 +137,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) { go svr.muxer.Serve() ln = svr.muxer.DefaultListener() - svr.listener = frpNet.WrapLogListener(ln) + svr.listener = ln log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort) // 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 { err = fmt.Errorf("Create vhost httpsMuxer error, %v", err) return @@ -203,10 +205,9 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) { } // 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 }) - svr.tlsListener = frpNet.WrapLogListener(tlsListener) // Create nat hole controller. if cfg.BindUdpPort > 0 { @@ -258,7 +259,7 @@ func (svr *Service) Run() { svr.HandleListener(svr.listener) } -func (svr *Service) HandleListener(l frpNet.Listener) { +func (svr *Service) HandleListener(l net.Listener) { // Listen for incoming connections from client. for { c, err := l.Accept() @@ -266,6 +267,9 @@ func (svr *Service) HandleListener(l frpNet.Listener) { log.Warn("Listener for incoming connections from client closed") 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...") originConn := c @@ -278,8 +282,8 @@ func (svr *Service) HandleListener(l frpNet.Listener) { log.Trace("success check TLS connection") // Start a new goroutine for dealing connections. - go func(frpConn frpNet.Conn) { - dealFn := func(conn frpNet.Conn) { + go func(frpConn net.Conn) { + dealFn := func(conn net.Conn) { var rawMsg msg.Message conn.SetReadDeadline(time.Now().Add(connReadTimeout)) 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. // Otherwise send success message in control's work goroutine. if err != nil { - conn.Warn("%v", err) + xl.Warn("register control error: %v", err) msg.WriteMsg(conn, &msg.LoginResp{ Version: version.Full(), Error: err.Error(), @@ -306,7 +310,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) { svr.RegisterWorkConn(conn, m) case *msg.NewVisitorConn: 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{ ProxyName: m.ProxyName, Error: err.Error(), @@ -342,8 +346,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) { session.Close() return } - wrapConn := frpNet.WrapConn(stream) - go dealFn(wrapConn) + go dealFn(stream) } } else { 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) { - ctlConn.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]", +func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err error) { + // 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) // Check client version. @@ -368,22 +384,12 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e return } - // 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 - } - } - - ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg) + ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg) if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil { oldCtl.allShutdown.WaitDone() } - ctlConn.AddLogPrefix(loginMsg.RunId) ctl.Start() // 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. -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) 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 } ctl.RegisterWorkConn(workConn) 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, newMsg.UseEncryption, newMsg.UseCompression) } diff --git a/tests/ci/auto_test_frpc.ini b/tests/ci/auto_test_frpc.ini index 28ea5fd5..b8d90bf2 100644 --- a/tests/ci/auto_test_frpc.ini +++ b/tests/ci/auto_test_frpc.ini @@ -2,8 +2,7 @@ server_addr = 127.0.0.1 server_port = 10700 log_file = console -# debug, info, warn, error -log_level = debug +log_level = trace token = 123456 admin_port = 10600 admin_user = abc diff --git a/tests/ci/auto_test_frps.ini b/tests/ci/auto_test_frps.ini index 9300b551..8948c987 100644 --- a/tests/ci/auto_test_frps.ini +++ b/tests/ci/auto_test_frps.ini @@ -2,8 +2,7 @@ bind_addr = 0.0.0.0 bind_port = 10700 vhost_http_port = 10804 -log_file = console -log_level = debug +log_level = trace token = 123456 allow_ports = 10000-20000,20002,30000-50000 subdomain_host = sub.com diff --git a/tests/mock/echo_server.go b/tests/mock/echo_server.go index e029f505..5ae40ee9 100644 --- a/tests/mock/echo_server.go +++ b/tests/mock/echo_server.go @@ -11,7 +11,7 @@ import ( ) type EchoServer struct { - l frpNet.Listener + l net.Listener port int repeatedNum int @@ -30,7 +30,7 @@ func NewEchoServer(port int, repeatedNum int, specifyStr string) *EchoServer { } 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 { fmt.Printf("echo server listen error: %v\n", err) return err diff --git a/tests/util/util.go b/tests/util/util.go index 4a4e6ffc..163ddc26 100644 --- a/tests/util/util.go +++ b/tests/util/util.go @@ -14,7 +14,6 @@ import ( "time" "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) { @@ -98,7 +97,7 @@ func ReloadConf(reloadAddr string, user string, passwd string) 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 { err = fmt.Errorf("connect to tcp server error: %v", err) return diff --git a/utils/log/log.go b/utils/log/log.go index 1a9033bf..1ddf4cdc 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -91,71 +91,3 @@ func Debug(format string, v ...interface{}) { func Trace(format string, v ...interface{}) { Log.Trace(format, v...) } - -// Logger is the log interface -type Logger interface { - AddLogPrefix(string) - GetPrefixStr() string - GetAllPrefix() []string - ClearLogPrefix() - Error(string, ...interface{}) - Warn(string, ...interface{}) - Info(string, ...interface{}) - Debug(string, ...interface{}) - Trace(string, ...interface{}) -} - -type PrefixLogger struct { - prefix string - allPrefix []string -} - -func NewPrefixLogger(prefix string) *PrefixLogger { - logger := &PrefixLogger{ - allPrefix: make([]string, 0), - } - logger.AddLogPrefix(prefix) - return logger -} - -func (pl *PrefixLogger) AddLogPrefix(prefix string) { - if len(prefix) == 0 { - return - } - - pl.prefix += "[" + prefix + "] " - pl.allPrefix = append(pl.allPrefix, prefix) -} - -func (pl *PrefixLogger) GetPrefixStr() string { - return pl.prefix -} - -func (pl *PrefixLogger) GetAllPrefix() []string { - return pl.allPrefix -} - -func (pl *PrefixLogger) ClearLogPrefix() { - pl.prefix = "" - pl.allPrefix = make([]string, 0) -} - -func (pl *PrefixLogger) Error(format string, v ...interface{}) { - Log.Error(pl.prefix+format, v...) -} - -func (pl *PrefixLogger) Warn(format string, v ...interface{}) { - Log.Warn(pl.prefix+format, v...) -} - -func (pl *PrefixLogger) Info(format string, v ...interface{}) { - Log.Info(pl.prefix+format, v...) -} - -func (pl *PrefixLogger) Debug(format string, v ...interface{}) { - Log.Debug(pl.prefix+format, v...) -} - -func (pl *PrefixLogger) Trace(format string, v ...interface{}) { - Log.Trace(pl.prefix+format, v...) -} diff --git a/utils/net/conn.go b/utils/net/conn.go index 9f415ec8..6b1d3fa2 100644 --- a/utils/net/conn.go +++ b/utils/net/conn.go @@ -15,6 +15,7 @@ package net import ( + "context" "crypto/tls" "errors" "fmt" @@ -23,41 +24,64 @@ import ( "sync/atomic" "time" - "github.com/fatedier/frp/utils/log" - + "github.com/fatedier/frp/utils/xlog" gnet "github.com/fatedier/golib/net" kcp "github.com/fatedier/kcp-go" ) -// Conn is the interface of connections used in frp. -type Conn interface { - net.Conn - log.Logger +type ContextGetter interface { + Context() context.Context } -type WrapLogConn struct { - net.Conn - log.Logger +type ContextSetter interface { + WithContext(ctx context.Context) } -func WrapConn(c net.Conn) Conn { - return &WrapLogConn{ - Conn: c, - Logger: log.NewPrefixLogger(""), +func NewLogFromConn(conn net.Conn) *xlog.Logger { + 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, + ctx: ctx, + } +} + +func (c *ContextConn) WithContext(ctx context.Context) { + c.ctx = ctx +} + +func (c *ContextConn) Context() context.Context { + return c.ctx } type WrapReadWriteCloserConn struct { io.ReadWriteCloser - log.Logger underConn net.Conn } -func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) Conn { +func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) net.Conn { return &WrapReadWriteCloserConn{ ReadWriteCloser: rwc, - Logger: log.NewPrefixLogger(""), underConn: underConn, } } @@ -99,7 +123,6 @@ func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error { type CloseNotifyConn struct { net.Conn - log.Logger // 1 means closed closeFlag int32 @@ -108,10 +131,9 @@ type CloseNotifyConn struct { } // 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{ Conn: c, - Logger: log.NewPrefixLogger(""), closeFn: closeFn, } } @@ -128,7 +150,7 @@ func (cc *CloseNotifyConn) Close() (err error) { } type StatsConn struct { - Conn + net.Conn closed int64 // 1 means closed totalRead int64 @@ -136,7 +158,7 @@ type StatsConn struct { 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{ Conn: conn, statsFunc: statsFunc, @@ -166,10 +188,10 @@ func (statsConn *StatsConn) Close() (err error) { return } -func ConnectServer(protocol string, addr string) (c Conn, err error) { +func ConnectServer(protocol string, addr string) (c net.Conn, err error) { switch protocol { case "tcp": - return ConnectTcpServer(addr) + return net.Dial("tcp", addr) case "kcp": kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3) if errRet != nil { @@ -184,21 +206,17 @@ func ConnectServer(protocol string, addr string) (c Conn, err error) { kcpConn.SetACKNoDelay(false) kcpConn.SetReadBuffer(4194304) kcpConn.SetWriteBuffer(4194304) - c = WrapConn(kcpConn) + c = kcpConn return default: 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 { case "tcp": - var conn net.Conn - if conn, err = gnet.DialTcpByProxy(proxyUrl, addr); err != nil { - return - } - return WrapConn(conn), nil + return gnet.DialTcpByProxy(proxyURL, addr) case "kcp": // http proxy is not supported for kcp 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) if err != nil { return diff --git a/utils/net/kcp.go b/utils/net/kcp.go index 3d080fdd..39eb8987 100644 --- a/utils/net/kcp.go +++ b/utils/net/kcp.go @@ -18,17 +18,13 @@ import ( "fmt" "net" - "github.com/fatedier/frp/utils/log" - kcp "github.com/fatedier/kcp-go" ) type KcpListener struct { - net.Addr listener net.Listener - accept chan Conn + acceptCh chan net.Conn closeFlag bool - log.Logger } 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) l = &KcpListener{ - Addr: listener.Addr(), listener: listener, - accept: make(chan Conn), + acceptCh: make(chan net.Conn), closeFlag: false, - Logger: log.NewPrefixLogger(""), } go func() { @@ -52,7 +46,7 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) { conn, err := listener.AcceptKCP() if err != nil { if l.closeFlag { - close(l.accept) + close(l.acceptCh) return } continue @@ -64,14 +58,14 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) { conn.SetWindowSize(1024, 1024) conn.SetACKNoDelay(false) - l.accept <- WrapConn(conn) + l.acceptCh <- conn } }() return l, err } -func (l *KcpListener) Accept() (Conn, error) { - conn, ok := <-l.accept +func (l *KcpListener) Accept() (net.Conn, error) { + conn, ok := <-l.acceptCh if !ok { return conn, fmt.Errorf("channel for kcp listener closed") } @@ -86,6 +80,10 @@ func (l *KcpListener) Close() error { return nil } +func (l *KcpListener) Addr() net.Addr { + return l.listener.Addr() +} + func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) { kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn) if err != nil { diff --git a/utils/net/listener.go b/utils/net/listener.go index 90ea59b4..3b199c83 100644 --- a/utils/net/listener.go +++ b/utils/net/listener.go @@ -19,65 +19,34 @@ import ( "net" "sync" - "github.com/fatedier/frp/utils/log" - "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 type CustomListener struct { - conns chan Conn - closed bool - mu sync.Mutex - - log.Logger + acceptCh chan net.Conn + closed bool + mu sync.Mutex } func NewCustomListener() *CustomListener { return &CustomListener{ - conns: make(chan Conn, 64), - Logger: log.NewPrefixLogger(""), + acceptCh: make(chan net.Conn, 64), } } -func (l *CustomListener) Accept() (Conn, error) { - conn, ok := <-l.conns +func (l *CustomListener) Accept() (net.Conn, error) { + conn, ok := <-l.acceptCh if !ok { return nil, fmt.Errorf("listener closed") } - conn.AddLogPrefix(l.GetPrefixStr()) return conn, nil } -func (l *CustomListener) PutConn(conn Conn) error { +func (l *CustomListener) PutConn(conn net.Conn) error { err := errors.PanicToError(func() { select { - case l.conns <- conn: + case l.acceptCh <- conn: default: conn.Close() } @@ -89,7 +58,7 @@ func (l *CustomListener) Close() error { l.mu.Lock() defer l.mu.Unlock() if !l.closed { - close(l.conns) + close(l.acceptCh) l.closed = true } return nil diff --git a/utils/net/tcp.go b/utils/net/tcp.go deleted file mode 100644 index 5c490d90..00000000 --- a/utils/net/tcp.go +++ /dev/null @@ -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 -} diff --git a/utils/net/tls.go b/utils/net/tls.go index 4ac51d5f..b9fca317 100644 --- a/utils/net/tls.go +++ b/utils/net/tls.go @@ -26,13 +26,13 @@ var ( 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)}) - out = WrapConn(tls.Client(c, tlsConfig)) + out = tls.Client(c, tlsConfig) 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) buf := make([]byte, 1) 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 { - out = WrapConn(tls.Server(c, tlsConfig)) + out = tls.Server(c, tlsConfig) } else { - out = WrapConn(sc) + out = sc } return } diff --git a/utils/net/udp.go b/utils/net/udp.go index e748e433..28a68139 100644 --- a/utils/net/udp.go +++ b/utils/net/udp.go @@ -21,8 +21,6 @@ import ( "sync" "time" - "github.com/fatedier/frp/utils/log" - "github.com/fatedier/golib/pool" ) @@ -33,7 +31,6 @@ type UdpPacket struct { } type FakeUdpConn struct { - log.Logger l *UdpListener localAddr net.Addr @@ -47,7 +44,6 @@ type FakeUdpConn struct { func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn { fc := &FakeUdpConn{ - Logger: log.NewPrefixLogger(""), l: l, localAddr: laddr, remoteAddr: raddr, @@ -157,15 +153,13 @@ func (c *FakeUdpConn) SetWriteDeadline(t time.Time) error { } type UdpListener struct { - net.Addr - accept chan Conn + addr net.Addr + acceptCh chan net.Conn writeCh chan *UdpPacket readConn net.Conn closeFlag bool fakeConns map[string]*FakeUdpConn - - log.Logger } 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) l = &UdpListener{ - Addr: udpAddr, - accept: make(chan Conn), + addr: udpAddr, + acceptCh: make(chan net.Conn), writeCh: make(chan *UdpPacket, 1000), fakeConns: make(map[string]*FakeUdpConn), - Logger: log.NewPrefixLogger(""), } // for reading @@ -189,19 +182,19 @@ func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) { buf := pool.GetBuf(1450) n, remoteAddr, err := readConn.ReadFromUDP(buf) if err != nil { - close(l.accept) + close(l.acceptCh) close(l.writeCh) return } fakeConn, exist := l.fakeConns[remoteAddr.String()] if !exist || fakeConn.IsClosed() { - fakeConn = NewFakeUdpConn(l, l.Addr, remoteAddr) + fakeConn = NewFakeUdpConn(l, l.Addr(), remoteAddr) l.fakeConns[remoteAddr.String()] = fakeConn } fakeConn.putPacket(buf[:n]) - l.accept <- fakeConn + l.acceptCh <- fakeConn } }() @@ -226,7 +219,6 @@ func (l *UdpListener) writeUdpPacket(packet *UdpPacket) (err error) { defer func() { if errRet := recover(); errRet != nil { err = fmt.Errorf("udp write closed listener") - l.Info("udp write closed listener") } }() l.writeCh <- packet @@ -243,8 +235,8 @@ func (l *UdpListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error) return } -func (l *UdpListener) Accept() (Conn, error) { - conn, ok := <-l.accept +func (l *UdpListener) Accept() (net.Conn, error) { + conn, ok := <-l.acceptCh if !ok { return conn, fmt.Errorf("channel for udp listener closed") } @@ -258,3 +250,7 @@ func (l *UdpListener) Close() error { } return nil } + +func (l *UdpListener) Addr() net.Addr { + return l.addr +} diff --git a/utils/net/websocket.go b/utils/net/websocket.go index 99423373..36b6440c 100644 --- a/utils/net/websocket.go +++ b/utils/net/websocket.go @@ -8,8 +8,6 @@ import ( "net/url" "time" - "github.com/fatedier/frp/utils/log" - "golang.org/x/net/websocket" ) @@ -22,10 +20,8 @@ const ( ) type WebsocketListener struct { - net.Addr - ln net.Listener - accept chan Conn - log.Logger + ln net.Listener + acceptCh chan net.Conn server *http.Server httpMutex *http.ServeMux @@ -35,9 +31,7 @@ type WebsocketListener struct { // ln: tcp listener for websocket connections func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { wl = &WebsocketListener{ - Addr: ln.Addr(), - accept: make(chan Conn), - Logger: log.NewPrefixLogger(""), + acceptCh: make(chan net.Conn), } muxer := http.NewServeMux() @@ -46,7 +40,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { conn := WrapCloseNotifyConn(c, func() { close(notifyCh) }) - wl.accept <- conn + wl.acceptCh <- conn <-notifyCh })) @@ -68,8 +62,8 @@ func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) return l, nil } -func (p *WebsocketListener) Accept() (Conn, error) { - c, ok := <-p.accept +func (p *WebsocketListener) Accept() (net.Conn, error) { + c, ok := <-p.acceptCh if !ok { return nil, ErrWebsocketListenerClosed } @@ -80,8 +74,12 @@ func (p *WebsocketListener) Close() error { return p.server.Close() } +func (p *WebsocketListener) Addr() net.Addr { + return p.ln.Addr() +} + // addr: domain:port -func ConnectWebsocketServer(addr string) (Conn, error) { +func ConnectWebsocketServer(addr string) (net.Conn, error) { addr = "ws://" + addr + FrpWebsocketPath uri, err := url.Parse(addr) if err != nil { @@ -101,6 +99,5 @@ func ConnectWebsocketServer(addr string) (Conn, error) { if err != nil { return nil, err } - c := WrapConn(conn) - return c, nil + return conn, nil } diff --git a/utils/vhost/https.go b/utils/vhost/https.go index 12fc8d0c..53177019 100644 --- a/utils/vhost/https.go +++ b/utils/vhost/https.go @@ -17,11 +17,10 @@ package vhost import ( "fmt" "io" + "net" "strings" "time" - frpNet "github.com/fatedier/frp/utils/net" - gnet "github.com/fatedier/golib/net" "github.com/fatedier/golib/pool" ) @@ -48,7 +47,7 @@ type HttpsMuxer struct { *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) return &HttpsMuxer{mux}, err } @@ -182,7 +181,7 @@ func readHandshake(rd io.Reader) (host string, err error) { 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) sc, rd := gnet.NewSharedConn(c) host, err := readHandshake(rd) @@ -191,5 +190,5 @@ func GetHttpsHostname(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err er } reqInfoMap["Host"] = host reqInfoMap["Scheme"] = "https" - return frpNet.WrapConn(sc), reqInfoMap, nil + return sc, reqInfoMap, nil } diff --git a/utils/vhost/vhost.go b/utils/vhost/vhost.go index d3e54fa1..57f82394 100644 --- a/utils/vhost/vhost.go +++ b/utils/vhost/vhost.go @@ -13,22 +13,25 @@ package vhost import ( + "context" "fmt" + "net" "strings" "time" "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/xlog" "github.com/fatedier/golib/errors" ) -type muxFunc func(frpNet.Conn) (frpNet.Conn, map[string]string, error) -type httpAuthFunc func(frpNet.Conn, string, string, string) (bool, error) -type hostRewriteFunc func(frpNet.Conn, string) (frpNet.Conn, error) +type muxFunc func(net.Conn) (net.Conn, map[string]string, error) +type httpAuthFunc func(net.Conn, string, string, string) (bool, error) +type hostRewriteFunc func(net.Conn, string) (net.Conn, error) type VhostMuxer struct { - listener frpNet.Listener + listener net.Listener timeout time.Duration vhostFunc muxFunc authFunc httpAuthFunc @@ -36,7 +39,7 @@ type VhostMuxer struct { 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{ listener: listener, timeout: timeout, @@ -49,7 +52,7 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut 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 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 // 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{ name: cfg.Domain, location: cfg.Location, @@ -73,8 +76,8 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) { userName: cfg.Username, passWord: cfg.Password, mux: v, - accept: make(chan frpNet.Conn), - Logger: log.NewPrefixLogger(""), + accept: make(chan net.Conn), + ctx: ctx, } err = v.registryRouter.Add(cfg.Domain, cfg.Location, l) 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 { c.Close() return @@ -146,13 +149,14 @@ func (v *VhostMuxer) handle(c frpNet.Conn) { c.Close() return } + xl := xlog.FromContextSafe(l.ctx) // if authFunc is exist and userName/password is set // then verify user access if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" { bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"]) if bAccess == false || err != nil { - l.Debug("check http Authorization failed") + xl.Debug("check http Authorization failed") res := noAuthResponse() res.Write(c) c.Close() @@ -166,12 +170,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) { } 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() { l.accept <- c }) 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 passWord string mux *VhostMuxer // for closing VhostMuxer - accept chan frpNet.Conn - log.Logger + accept chan net.Conn + 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 if !ok { return nil, fmt.Errorf("Listener closed") @@ -198,17 +203,13 @@ func (l *Listener) Accept() (frpNet.Conn, error) { if l.mux.rewriteFunc != nil { sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost) 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") } - l.Debug("rewrite host to [%s] success", l.rewriteHost) + xl.Debug("rewrite host to [%s] success", l.rewriteHost) conn = sConn } - - for _, prefix := range l.GetAllPrefix() { - conn.AddLogPrefix(prefix) - } - return conn, nil + return frpNet.NewContextConn(conn, l.ctx), nil } func (l *Listener) Close() error { @@ -220,3 +221,7 @@ func (l *Listener) Close() error { func (l *Listener) Name() string { return l.name } + +func (l *Listener) Addr() net.Addr { + return (*net.TCPAddr)(nil) +} diff --git a/utils/xlog/ctx.go b/utils/xlog/ctx.go new file mode 100644 index 00000000..1d3619be --- /dev/null +++ b/utils/xlog/ctx.go @@ -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 +} diff --git a/utils/xlog/xlog.go b/utils/xlog/xlog.go new file mode 100644 index 00000000..a1c48672 --- /dev/null +++ b/utils/xlog/xlog.go @@ -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...) +} From dc0fd60d3058025a6f0fde9f52216d6eca45c6ed Mon Sep 17 00:00:00 2001 From: Weisi Dai Date: Sun, 13 Oct 2019 14:46:08 -0700 Subject: [PATCH 05/15] frp: Fix typos in English Readme. --- README.md | 339 +++++++++++++++++++++++++++--------------------------- 1 file changed, 169 insertions(+), 170 deletions(-) diff --git a/README.md b/README.md index dbb977a6..ab9d8356 100644 --- a/README.md +++ b/README.md @@ -6,53 +6,53 @@ ## 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 tries to support p2p connect. +frp also has a P2P connect mode. ## Table of Contents -* [Status](#status) +* [Development Status](#development-status) * [Architecture](#architecture) * [Example Usage](#example-usage) * [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh) * [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains) * [Forward DNS query request](#forward-dns-query-request) - * [Forward unix domain socket](#forward-unix-domain-socket) - * [Expose a simple http file server](#expose-a-simple-http-file-server) + * [Forward Unix domain socket](#forward-unix-domain-socket) + * [Expose a simple HTTP file server](#expose-a-simple-http-file-server) * [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service) - * [Expose your service in security](#expose-your-service-in-security) + * [Expose your service privately](#expose-your-service-privately) * [P2P Mode](#p2p-mode) * [Features](#features) - * [Configuration File](#configuration-file) - * [Configuration file template](#configuration-file-template) + * [Configuration Files](#configuration-files) + * [Using Environment Variables](#using-environment-variables) * [Dashboard](#dashboard) * [Admin UI](#admin-ui) - * [Authentication](#authentication) + * [Authenticating the Client](#authenticating-the-client) * [Encryption and Compression](#encryption-and-compression) * [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) - * [Port White List](#port-white-list) + * [Only allowing certain ports on the server](#only-allowing-certain-ports-on-the-server) * [Port Reuse](#port-reuse) * [TCP Stream Multiplexing](#tcp-stream-multiplexing) * [Support KCP Protocol](#support-kcp-protocol) - * [Connection Pool](#connection-pool) + * [Connection Pooling](#connection-pooling) * [Load balancing](#load-balancing) - * [Health Check](#health-check) - * [Rewriting the Host Header](#rewriting-the-host-header) - * [Set Headers In HTTP Request](#set-headers-in-http-request) + * [Service Health Check](#service-health-check) + * [Rewriting the HTTP Host Header](#rewriting-the-http-host-header) + * [Setting other HTTP Headers](#setting-other-http-headers) * [Get Real IP](#get-real-ip) * [HTTP X-Forwarded-For](#http-x-forwarded-for) * [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) * [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) - * [Plugin](#plugin) + * [Plugins](#plugins) * [Development Plan](#development-plan) * [Contributing](#contributing) * [Donation](#donation) @@ -62,11 +62,11 @@ Now it also tries to support p2p connect. -## Status +## Development Status -frp is under development, you can try by using the latest release version under the 'master' branch. You can use the 'dev' branch instead for the version in development. +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 @@ -74,15 +74,15 @@ frp is under development, you can try by using the latest release version under ## Example Usage -Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch. +Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your operating system and architecture. -Put **frps** and **frps.ini** to your server with public IP. +Put `frps` and `frps.ini` onto your server A with public IP. -Put **frpc** and **frpc.ini** to your server in LAN. +Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected from public Internet). ### Access your computer in LAN by SSH -1. Modify frps.ini: +1. Modify `frps.ini` on server A: ```ini # frps.ini @@ -90,11 +90,11 @@ Put **frpc** and **frpc.ini** to your server in LAN. bind_port = 7000 ``` -2. Start frps: +2. Start `frps` on server A: `./frps -c ./frps.ini` -3. Modify frpc.ini, `server_addr` is your frps's server IP: +3. On server B, modify `frpc.ini` to put in your `frps` server public IP as `server_addr` field: ```ini # frpc.ini @@ -109,21 +109,21 @@ Put **frpc** and **frpc.ini** to your server in LAN. remote_port = 6000 ``` -4. Start frpc: +4. Start `frpc` on server B: `./frpc -c ./frpc.ini` -5. Connect to server in LAN by ssh assuming that username is test: +5. From another machine, SSH to server B like this (assuming that username is `test`): `ssh -oPort=6000 test@x.x.x.x` ### Visit your web service in LAN by custom domains -Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip. +Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local IP. -However, we can expose a http or https service using frp. +However, we can expose an HTTP(S) service using frp. -1. Modify frps.ini, configure http port 8080: +1. Modify `frps.ini`, set the vhost HTTP port to 8080: ```ini # frps.ini @@ -132,11 +132,11 @@ However, we can expose a http or https service using frp. vhost_http_port = 8080 ``` -2. Start frps: +2. Start `frps`: `./frps -c ./frps.ini` -3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service: +3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. The `local_port` is the port of your web service: ```ini # frpc.ini @@ -147,20 +147,20 @@ However, we can expose a http or https service using frp. [web] type = http local_port = 80 - custom_domains = www.yourdomain.com + custom_domains = www.example.com ``` -4. Start frpc: +4. Start `frpc`: `./frpc -c ./frpc.ini` -5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain. +5. Resolve A record of `www.example.com` to the public IP of the remote frps server or CNAME record to your origin domain. -6. Now visit your local web service using url `http://www.yourdomain.com:8080`. +6. Now visit your local web service using url `http://www.example.com:8080`. ### Forward DNS query request -1. Modify frps.ini: +1. Modify `frps.ini`: ```ini # frps.ini @@ -168,11 +168,11 @@ However, we can expose a http or https service using frp. bind_port = 7000 ``` -2. Start frps: +2. Start `frps`: `./frps -c ./frps.ini` -3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to Google's dns server `8.8.8.8:53`: +3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server, forward DNS query request to Google Public DNS server `8.8.8.8:53`: ```ini # frpc.ini @@ -191,17 +191,17 @@ However, we can expose a http or https service using frp. `./frpc -c ./frpc.ini` -5. Send dns query request by dig: +5. Test DNS resolution using `dig` command: `dig @x.x.x.x -p 6000 www.google.com` -### Forward unix domain socket +### Forward Unix domain socket -Use tcp port to connect to a unix domain socket (e.g. Docker daemon's socket). +Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP. -Configure frps same as above. +Configure `frps` same as above. -1. Start frpc with configurations: +1. Start `frpc` with configuration: ```ini # frpc.ini @@ -216,17 +216,17 @@ Configure frps same as above. plugin_unix_path = /var/run/docker.sock ``` -2. Get docker version by curl command: +2. Test: Get Docker version using `curl`: `curl http://x.x.x.x:6000/version` -### Expose a simple http file server +### Expose a simple HTTP file server -A simple way to browse files in the LAN. +Browser your files stored in the LAN, from public Internet. -Configure frps same as above. +Configure `frps` same as above. -1. Start frpc with configurations: +1. Start `frpc` with configuration: ```ini # frpc.ini @@ -238,17 +238,17 @@ Configure frps same as above. type = tcp remote_port = 6000 plugin = static_file - plugin_local_path = /tmp/file + plugin_local_path = /tmp/files plugin_strip_prefix = static plugin_http_user = abc plugin_http_passwd = abc ``` -2. Visit `http://x.x.x.x:6000/static/` by your browser, specify 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 -1. Start frpc with configurations: +1. Start `frpc` with configuration: ```ini # frpc.ini @@ -256,9 +256,9 @@ Configure frps same as above. server_addr = x.x.x.x server_port = 7000 - [test_htts2http] + [test_https2http] type = https - custom_domains = test.yourdomain.com + custom_domains = test.example.com plugin = https2http plugin_local_addr = 127.0.0.1:80 @@ -268,17 +268,15 @@ Configure frps same as above. 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 -Some services will be at risk if exposed directly to the public network. +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 while keeping the service secure. +Configure `frps` same as above. -Configure frps same as above. - -1. Start frpc, forward ssh port and `remote_port` are useless: +1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `sk` field for the preshared key, and that the `remote_port` field is removed here: ```ini # frpc.ini @@ -293,7 +291,7 @@ Configure frps same as above. local_port = 22 ``` -2. Start another frpc in which you want to connect this ssh server: +2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`sk` field): ```ini # frpc.ini @@ -310,23 +308,24 @@ Configure frps same as above. bind_port = 6000 ``` -3. Connect to server in LAN using ssh assuming that username is test: +3. On machine C, connect to SSH on machine B, using this command: - `ssh -oPort=6000 test@127.0.0.1` + `ssh -oPort=6000 127.0.0.1` ### P2P Mode -**xtcp** is designed for transmitting a large amount of data directly between two client. +**xtcp** is designed for transmitting large amounts of data directly between clients. A frps server is still needed, as P2P here only refers the actual data transmission. -It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work. +Note it can't penetrate all types of NAT devices. You might want to fallback to **stcp** if **xtcp** doesn't work. -1. Configure a udp port for xtcp: +1. In `frps.ini` configure a UDP port for xtcp: ```ini + # frps.ini bind_udp_port = 7001 ``` -2. Start frpc, forward ssh port and `remote_port` are useless: +2. Start `frpc` on machine B, expose the SSH port. Note that `remote_port` field is removed: ```ini # frpc.ini @@ -341,7 +340,7 @@ It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** do local_port = 22 ``` -3. Start another frpc in which you want to connect this ssh server: +3. Start another `frpc` (typically on another machine C) with the config to connect to SSH using P2P mode: ```ini # frpc.ini @@ -358,23 +357,23 @@ It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** do bind_port = 6000 ``` -4. Connect to server in LAN using ssh assuming that username is test: +4. On machine C, connect to SSH on machine B, using this command: - `ssh -oPort=6000 test@127.0.0.1` + `ssh -oPort=6000 127.0.0.1` ## Features -### Configuration File +### Configuration Files -You can find features not mentioned in this document from the 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 template can be rendered using os environments. Template uses Go's standard format. +Environment variables can be referenced in the configuration file, using Go's standard format: ```ini # frpc.ini @@ -389,7 +388,7 @@ local_port = 22 remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }} ``` -Start frpc program: +With the config above, variables can be passed into `frpc` program like this: ``` export FRP_SERVER_ADDR="x.x.x.x" @@ -397,8 +396,7 @@ export FRP_SSH_REMOTE_PORT="6000" ./frpc -c ./frpc.ini ``` -frpc will auto render configuration file template using os environments. -All environments has prefix `.Envs`. +`frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`. ### Dashboard @@ -414,13 +412,13 @@ dashboard_user = admin dashboard_pwd = admin ``` -Then visit `http://[server_addr]:7500` to see the 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) ### Admin UI -Admin UI help you check and manage frpc's configuration. +The Admin UI helps you check and manage frpc's configuration. Configure an address for admin UI to enable this feature: @@ -432,15 +430,15 @@ admin_user = admin admin_pwd = admin ``` -Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`. +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 equal. +Always use the same `token` in the `[common]` section in `frps.ini` and `frpc.ini`. ### Encryption and Compression -Default value is false, you could decide if the proxy will use encryption or compression: +The features are off by default. You can turn on encryption and/or compression: ```ini # frpc.ini @@ -454,15 +452,15 @@ use_compression = true #### TLS -frp supports 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 sends 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 # frpc.ini @@ -471,17 +469,17 @@ admin_addr = 127.0.0.1 admin_port = 7400 ``` -Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies. +Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let `frpc` create or update or delete proxies. **Note that parameters in [common] section won't be modified except 'start'.** ### 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 configuration 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 to prevent abuse of ports: +`allow_ports` in `frps.ini` is used to avoid abuse of ports: ```ini # frps.ini @@ -489,7 +487,7 @@ Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set ad allow_ports = 2000-3000,3001,3003,4000-50000 ``` -`allow_ports` consists of a specific port or a range of ports divided by `,`. +`allow_ports` consists of specific ports or port ranges (lowest port number, dash `-`, highest port number), separated by comma `,`. ### Port Reuse @@ -499,9 +497,9 @@ We would like to try to allow multiple proxies bind a same remote port with diff ### TCP Stream Multiplexing -frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection. +frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection. -You can disable this feature by modify frps.ini and frpc.ini: +You can disable this feature by modify `frps.ini` and `frpc.ini`: ```ini # frps.ini and frpc.ini, must be same @@ -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. -Using kcp in frp: +KCP mode uses UDP as the underlying transport. Using KCP in frp: -1. Enable kcp protocol in frps: +1. Enable KCP in frps: ```ini # frps.ini [common] bind_port = 7000 - # kcp needs to bind a udp port, it can be same with 'bind_port' + # Specify a UDP port for KCP. kcp_bind_port = 7000 ``` -2. Configure the protocol used in frpc to connect to frps: + The `kcp_bind_port` number can be the same number as `bind_port`, since `bind_port` field specifies a TCP port. + +2. Configure `frpc.ini` to use KCP to connect to frps: ```ini # frpc.ini [common] server_addr = x.x.x.x - # specify the 'kcp_bind_port' in frps + # Same as the 'kcp_bind_port' in frps.ini server_port = 7000 protocol = kcp ``` -### Connection Pool +### Connection Pooling -By default, frps sends a message to frpc to create a new connection to the backward service when getting a user request. If a proxy's connection pool is enabled, there will be a specified number of connections pre-established. +By default, frps creates a new frpc connection to the backend service upon a user request. With connection pooling, frps keeps a certain number of pre-established connections, reducing the time needed to establish a connection. -This feature is fit for a large number of short connections. +This feature is suitable for a large number of short connections. -1. Configure the limit of pool count each proxy can use in frps.ini: +1. Configure the limit of pool count each proxy can use in `frps.ini`: ```ini # frps.ini @@ -562,7 +562,7 @@ This feature is fit for a large number of short connections. 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 # frpc.ini @@ -583,23 +583,19 @@ group_key = 123 `group_key` is used for authentication. -Proxies in same group will accept connections from port 80 randomly. +Connections to port 80 will be dispatched to proxies in the same group randomly. -For `tcp` type, `remote_port` in the same group should be same. +For type `tcp`, `remote_port` in the same group should be the same. -For `http` type, `custom_domains, subdomain, locations` should 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. -Add `health_check_type = {type}` to enable health check. +Add `health_check_type = tcp` or `health_check_type = http` to enable health check. -**type** can be tcp or http. - -Type tcp will dial the service port and type http will send a http request to the service and require a HTTP 200 response. - -Type tcp configuration: +With health check type **tcp**, the service port will be pinged (TCPing): ```ini # frpc.ini @@ -607,77 +603,81 @@ Type tcp configuration: type = tcp local_port = 22 remote_port = 6000 -# enable tcp health check +# Enable TCP health check health_check_type = tcp -# dial timeout seconds +# TCPing timeout seconds health_check_timeout_s = 3 -# if health check failed 3 times in a row, 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 every 10 seconds +# A health check every 10 seconds health_check_interval_s = 10 ``` -Type http configuration: +With health check type **http**, an HTTP request will be sent to the service and an HTTP 2xx OK response is expected: + ```ini # frpc.ini [web] type = http local_ip = 127.0.0.1 local_port = 80 -custom_domains = test.yourdomain.com -# enable http health check +custom_domains = test.example.com +# Enable HTTP health check health_check_type = http -# frpc will send a GET http request '/status' to local http service -# http service is alive when it return 2xx http response code +# frpc will send a GET request to '/status' +# and expect an HTTP 2xx OK response health_check_url = /status -health_check_interval_s = 10 -health_check_max_failed = 3 health_check_timeout_s = 3 +health_check_max_failed = 3 +health_check_interval_s = 10 ``` -### Rewriting the Host Header +### Rewriting the HTTP Host Header -When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests. +By default frp does not modify the tunneled HTTP requests at all as it's a byte-for-byte copy. + +However, speaking of web servers and HTTP requests, your web server might rely on the `Host` HTTP header to determine the website to be accessed. frp can rewrite the `Host` header when forwarding the HTTP requests, with the `host_header_rewrite` field: ```ini # frpc.ini [web] type = http local_port = 80 -custom_domains = test.yourdomain.com -host_header_rewrite = dev.yourdomain.com +custom_domains = test.example.com +host_header_rewrite = dev.example.com ``` -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 # frpc.ini [web] type = http local_port = 80 -custom_domains = test.yourdomain.com -host_header_rewrite = dev.yourdomain.com +custom_domains = test.example.com +host_header_rewrite = dev.example.com header_X-From-Where = frp ``` -Note that parameters that have `header_` prefix will be added to http request headers. -In this example, it will set header `X-From-Where: frp` to http request. +Note that parameter(s) prefixed with `header_` will be added to HTTP request headers. + +In this example, it will set header `X-From-Where: frp` in the HTTP request. ### Get Real IP #### HTTP X-Forwarded-For -These features are for http proxy only. +This feature is for http proxy only. -You can get the 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 -frp support Proxy Protocol to send user's real IP to local service. It support all types except 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: @@ -686,21 +686,19 @@ Here is an example for https service: [web] type = https 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 ``` -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. - -### Password protecting your web service +### Require HTTP Basic auth (password) for web services Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password. -This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file. +This enforces HTTP Basic Auth on all requests with the username and password specified in frpc's configure file. It can only be enabled when proxy type is http. @@ -709,23 +707,23 @@ It can only be enabled when proxy type is http. [web] type = http local_port = 80 -custom_domains = test.yourdomain.com +custom_domains = test.example.com http_user = abc http_pwd = abc ``` -Visit `http://test.yourdomain.com` and now you need to input username and password. +Visit `http://test.example.com` in the browser and now you are prompted to enter the username and password. ### Custom subdomain names -It is convenient to use `subdomain` configure for http、https type when many people use one frps server together. +It is convenient to use `subdomain` configure for http and https types when many people share one frps server. ```ini # frps.ini subdomain_host = frps.com ``` -Resolve `*.frps.com` to the frps server's IP. +Resolve `*.frps.com` to the frps server's IP. This is usually called a Wildcard DNS record. ```ini # frpc.ini @@ -735,35 +733,36 @@ local_port = 80 subdomain = test ``` -Now you can visit your web service by host `test.frps.com`. +Now you can visit your web service on `test.frps.com`. Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`. ### URL routing -frp support forward http requests to different backward web services by url routing. +frp supports forwarding HTTP requests to different backend web services by url routing. -`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order. +`locations` specifies the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order. ```ini # frpc.ini [web01] type = http local_port = 80 -custom_domains = web.yourdomain.com +custom_domains = web.example.com locations = / [web02] type = http local_port = 81 -custom_domains = web.yourdomain.com +custom_domains = web.example.com locations = /news,/about ``` -Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**. -### Connect frps by HTTP PROXY +HTTP requests with URL prefix `/news` or `/about` will be forwarded to **web02** and other requests to **web01**. -frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file. +### 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. @@ -777,7 +776,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080 ### Range ports mapping -Proxy name that has starts with `range:` will support mapping range ports. +Proxy with names that start with `range:` will support mapping range ports. ```ini # frpc.ini @@ -788,15 +787,15 @@ local_port = 6000-6006,6007 remote_port = 6000-6006,6007 ``` -frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`. +frpc will generate 8 proxies like `test_tcp_0`, `test_tcp_1`, ..., `test_tcp_7`. -### Plugin +### Plugins -frpc only forwards request to local tcp or udp port by default. +frpc only forwards requests to local TCP or UDP ports by default. 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**: @@ -814,7 +813,7 @@ plugin_http_passwd = abc ## Development Plan -* Log http request information in frps. +* Log HTTP request information in frps. ## Contributing From 8eb945ee9b14c1e4b513d401ddf21a8ce922651e Mon Sep 17 00:00:00 2001 From: lzhfromustc Date: Mon, 14 Oct 2019 23:35:08 -0400 Subject: [PATCH 06/15] dev:udp: Add an Unlock before a continue, to fix a double lock bug --- models/proto/udp/udp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/models/proto/udp/udp.go b/models/proto/udp/udp.go index 26776341..ed7f95a9 100644 --- a/models/proto/udp/udp.go +++ b/models/proto/udp/udp.go @@ -117,6 +117,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- if !ok { udpConn, err = net.DialUDP("udp", nil, dstAddr) if err != nil { + mu.Unlock() continue } udpConnMap[udpMsg.RemoteAddr.String()] = udpConn From 22a79710d825a0f5db799180d02ecfdfd165974f Mon Sep 17 00:00:00 2001 From: fatedier Date: Sat, 2 Nov 2019 21:05:37 +0800 Subject: [PATCH 07/15] bump version to v0.29.1 --- utils/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/version/version.go b/utils/version/version.go index 7a73de8f..cc47e55f 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.29.0" +var version string = "0.29.1" func Full() string { return version From 6da093a4028eb5b6ef1d5cc6d0f98f047557d8a1 Mon Sep 17 00:00:00 2001 From: fatedier Date: Sun, 3 Nov 2019 01:20:49 +0800 Subject: [PATCH 08/15] support bandwith limit for one proxy --- client/proxy/proxy.go | 42 ++++++++++++----- conf/frpc_full.ini | 2 + go.mod | 1 + go.sum | 2 + models/config/proxy.go | 17 +++++-- models/config/types.go | 100 +++++++++++++++++++++++++++++++++++++++++ utils/limit/reader.go | 51 +++++++++++++++++++++ utils/limit/writer.go | 60 +++++++++++++++++++++++++ 8 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 models/config/types.go create mode 100644 utils/limit/reader.go create mode 100644 utils/limit/writer.go diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index 2da68ce8..aca1b94e 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -30,6 +30,7 @@ import ( "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/plugin" "github.com/fatedier/frp/models/proto/udp" + "github.com/fatedier/frp/utils/limit" frpNet "github.com/fatedier/frp/utils/net" "github.com/fatedier/frp/utils/xlog" @@ -38,6 +39,7 @@ import ( "github.com/fatedier/golib/pool" fmux "github.com/hashicorp/yamux" pp "github.com/pires/go-proxyproto" + "golang.org/x/time/rate" ) // Proxy defines how to handle work connections for different proxy type. @@ -51,9 +53,16 @@ type Proxy interface { } func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) { + var limiter *rate.Limiter + limitBytes := pxyConf.GetBaseInfo().BandwithLimit.Bytes() + if limitBytes > 0 { + limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes)) + } + baseProxy := BaseProxy{ clientCfg: clientCfg, serverUDPPort: serverUDPPort, + limiter: limiter, xl: xlog.FromContextSafe(ctx), ctx: ctx, } @@ -96,6 +105,7 @@ type BaseProxy struct { closed bool clientCfg config.ClientCommonConf serverUDPPort int + limiter *rate.Limiter mu sync.RWMutex xl *xlog.Logger @@ -127,8 +137,8 @@ func (pxy *TcpProxy) Close() { } func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(pxy.clientCfg.Token), m) + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + conn, []byte(pxy.clientCfg.Token), m) } // HTTP @@ -156,8 +166,8 @@ func (pxy *HttpProxy) Close() { } func (pxy *HttpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(pxy.clientCfg.Token), m) + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + conn, []byte(pxy.clientCfg.Token), m) } // HTTPS @@ -185,8 +195,8 @@ func (pxy *HttpsProxy) Close() { } func (pxy *HttpsProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(pxy.clientCfg.Token), m) + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + conn, []byte(pxy.clientCfg.Token), m) } // STCP @@ -214,8 +224,8 @@ func (pxy *StcpProxy) Close() { } func (pxy *StcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(pxy.clientCfg.Token), m) + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + conn, []byte(pxy.clientCfg.Token), m) } // XTCP @@ -360,7 +370,7 @@ func (pxy *XtcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { return } - HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, + HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, muxConn, []byte(pxy.cfg.Sk), m) } @@ -429,6 +439,13 @@ func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { // close resources releated with old workConn pxy.Close() + if pxy.limiter != nil { + rwc := frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error { + return conn.Close() + }) + conn = frpNet.WrapReadWriteCloserToConn(rwc, conn) + } + pxy.mu.Lock() pxy.workConn = conn pxy.readCh = make(chan *msg.UdpPacket, 1024) @@ -491,13 +508,18 @@ func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { // Common handler for tcp work connections. func HandleTcpWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin, - baseInfo *config.BaseProxyConf, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) { + baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) { xl := xlog.FromContextSafe(ctx) var ( remote io.ReadWriteCloser err error ) remote = workConn + if limiter != nil { + remote = frpIo.WrapReadWriteCloser(limit.NewReader(workConn, limiter), limit.NewWriter(workConn, limiter), func() error { + return workConn.Close() + }) + } xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t", baseInfo.UseEncryption, baseInfo.UseCompression) diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index 158ff23f..afa07079 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -71,6 +71,8 @@ tls_enable = true type = tcp local_ip = 127.0.0.1 local_port = 22 +# limit bandwith for this proxy, unit is KB and MB +bandwith_limit = 1MB # true or false, if true, messages between frps and frpc will be encrypted, default is false use_encryption = false # if true, message will be compressed diff --git a/go.mod b/go.mod index 81de8abe..a71a97c0 100644 --- a/go.mod +++ b/go.mod @@ -29,4 +29,5 @@ require ( github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/text v0.3.2 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 ) diff --git a/go.sum b/go.sum index b9bdf2a0..26c9004f 100644 --- a/go.sum +++ b/go.sum @@ -46,4 +46,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/models/config/proxy.go b/models/config/proxy.go index 9ab1ef88..2b129cfa 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -125,6 +125,11 @@ type BaseProxyConf struct { // values include "v1", "v2", and "". If the value is "", a protocol // version will be automatically selected. By default, this value is "". ProxyProtocolVersion string `json:"proxy_protocol_version"` + + // BandwithLimit limit the proxy bandwith + // 0 means no limit + BandwithLimit BandwithQuantity `json:"bandwith_limit"` + LocalSvrConf HealthCheckConf } @@ -140,7 +145,8 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool { cfg.UseCompression != cmp.UseCompression || cfg.Group != cmp.Group || cfg.GroupKey != cmp.GroupKey || - cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion { + cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion || + cfg.BandwithLimit.Equal(&cmp.BandwithLimit) { return false } if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) { @@ -165,6 +171,7 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i var ( tmpStr string ok bool + err error ) cfg.ProxyName = prefix + name cfg.ProxyType = section["type"] @@ -183,11 +190,15 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i cfg.GroupKey = section["group_key"] cfg.ProxyProtocolVersion = section["proxy_protocol_version"] - if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { + if cfg.BandwithLimit, err = NewBandwithQuantity(section["bandwidth_limit"]); err != nil { return err } - if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil { + if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { + return err + } + + if err = cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil { return err } diff --git a/models/config/types.go b/models/config/types.go new file mode 100644 index 00000000..9f3de661 --- /dev/null +++ b/models/config/types.go @@ -0,0 +1,100 @@ +// Copyright 2019 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "errors" + "strconv" + "strings" +) + +const ( + MB = 1024 * 1024 + KB = 1024 +) + +type BandwithQuantity struct { + s string // MB or KB + + i int64 // bytes +} + +func NewBandwithQuantity(s string) (BandwithQuantity, error) { + q := BandwithQuantity{} + err := q.UnmarshalString(s) + if err != nil { + return q, err + } + return q, nil +} + +func (q *BandwithQuantity) Equal(u *BandwithQuantity) bool { + if q == nil && u == nil { + return true + } + if q != nil && u != nil { + return q.i == u.i + } + return false +} + +func (q *BandwithQuantity) String() string { + return q.s +} + +func (q *BandwithQuantity) UnmarshalString(s string) error { + q.s = strings.TrimSpace(s) + if q.s == "" { + return nil + } + + var ( + base int64 + f float64 + err error + ) + if strings.HasSuffix(s, "MB") { + base = MB + s = strings.TrimSuffix(s, "MB") + f, err = strconv.ParseFloat(s, 64) + if err != nil { + return err + } + } else if strings.HasSuffix(s, "KB") { + base = KB + s = strings.TrimSuffix(s, "KB") + f, err = strconv.ParseFloat(s, 64) + if err != nil { + return err + } + } else { + return errors.New("unit not support") + } + + q.i = int64(f * float64(base)) + return nil +} + +func (q *BandwithQuantity) UnmarshalJSON(b []byte) error { + return q.UnmarshalString(string(b)) +} + +func (q *BandwithQuantity) MarshalJSON() ([]byte, error) { + return []byte(q.s), nil +} + +func (q *BandwithQuantity) Bytes() int64 { + return q.i +} diff --git a/utils/limit/reader.go b/utils/limit/reader.go new file mode 100644 index 00000000..efa828f4 --- /dev/null +++ b/utils/limit/reader.go @@ -0,0 +1,51 @@ +// Copyright 2019 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package limit + +import ( + "context" + "io" + + "golang.org/x/time/rate" +) + +type Reader struct { + r io.Reader + limiter *rate.Limiter +} + +func NewReader(r io.Reader, limiter *rate.Limiter) *Reader { + return &Reader{ + r: r, + limiter: limiter, + } +} + +func (r *Reader) Read(p []byte) (n int, err error) { + b := r.limiter.Burst() + if b < len(p) { + p = p[:b] + } + n, err = r.r.Read(p) + if err != nil { + return + } + + err = r.limiter.WaitN(context.Background(), n) + if err != nil { + return + } + return +} diff --git a/utils/limit/writer.go b/utils/limit/writer.go new file mode 100644 index 00000000..5256d1e2 --- /dev/null +++ b/utils/limit/writer.go @@ -0,0 +1,60 @@ +// Copyright 2019 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package limit + +import ( + "context" + "io" + + "golang.org/x/time/rate" +) + +type Writer struct { + w io.Writer + limiter *rate.Limiter +} + +func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer { + return &Writer{ + w: w, + limiter: limiter, + } +} + +func (w *Writer) Write(p []byte) (n int, err error) { + var nn int + b := w.limiter.Burst() + for { + end := len(p) + if end == 0 { + break + } + if b < len(p) { + end = b + } + err = w.limiter.WaitN(context.Background(), end) + if err != nil { + return + } + + nn, err = w.w.Write(p[:end]) + n += nn + if err != nil { + return + } + p = p[end:] + } + return +} From 42425d8218b215d8ee941a0364eb72ffd6dd839b Mon Sep 17 00:00:00 2001 From: fatedier Date: Sun, 3 Nov 2019 01:21:47 +0800 Subject: [PATCH 09/15] update vendor files --- vendor/golang.org/x/time/AUTHORS | 3 + vendor/golang.org/x/time/CONTRIBUTORS | 3 + vendor/golang.org/x/time/LICENSE | 27 ++ vendor/golang.org/x/time/PATENTS | 22 ++ vendor/golang.org/x/time/rate/rate.go | 400 ++++++++++++++++++++++++++ vendor/modules.txt | 2 + 6 files changed, 457 insertions(+) create mode 100644 vendor/golang.org/x/time/AUTHORS create mode 100644 vendor/golang.org/x/time/CONTRIBUTORS create mode 100644 vendor/golang.org/x/time/LICENSE create mode 100644 vendor/golang.org/x/time/PATENTS create mode 100644 vendor/golang.org/x/time/rate/rate.go diff --git a/vendor/golang.org/x/time/AUTHORS b/vendor/golang.org/x/time/AUTHORS new file mode 100644 index 00000000..15167cd7 --- /dev/null +++ b/vendor/golang.org/x/time/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/time/CONTRIBUTORS b/vendor/golang.org/x/time/CONTRIBUTORS new file mode 100644 index 00000000..1c4577e9 --- /dev/null +++ b/vendor/golang.org/x/time/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/time/LICENSE b/vendor/golang.org/x/time/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/time/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/time/PATENTS b/vendor/golang.org/x/time/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/time/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go new file mode 100644 index 00000000..563f7042 --- /dev/null +++ b/vendor/golang.org/x/time/rate/rate.go @@ -0,0 +1,400 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rate provides a rate limiter. +package rate + +import ( + "context" + "fmt" + "math" + "sync" + "time" +) + +// Limit defines the maximum frequency of some events. +// Limit is represented as number of events per second. +// A zero Limit allows no events. +type Limit float64 + +// Inf is the infinite rate limit; it allows all events (even if burst is zero). +const Inf = Limit(math.MaxFloat64) + +// Every converts a minimum time interval between events to a Limit. +func Every(interval time.Duration) Limit { + if interval <= 0 { + return Inf + } + return 1 / Limit(interval.Seconds()) +} + +// A Limiter controls how frequently events are allowed to happen. +// It implements a "token bucket" of size b, initially full and refilled +// at rate r tokens per second. +// Informally, in any large enough time interval, the Limiter limits the +// rate to r tokens per second, with a maximum burst size of b events. +// As a special case, if r == Inf (the infinite rate), b is ignored. +// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets. +// +// The zero value is a valid Limiter, but it will reject all events. +// Use NewLimiter to create non-zero Limiters. +// +// Limiter has three main methods, Allow, Reserve, and Wait. +// Most callers should use Wait. +// +// Each of the three methods consumes a single token. +// They differ in their behavior when no token is available. +// If no token is available, Allow returns false. +// If no token is available, Reserve returns a reservation for a future token +// and the amount of time the caller must wait before using it. +// If no token is available, Wait blocks until one can be obtained +// or its associated context.Context is canceled. +// +// The methods AllowN, ReserveN, and WaitN consume n tokens. +type Limiter struct { + limit Limit + burst int + + mu sync.Mutex + tokens float64 + // last is the last time the limiter's tokens field was updated + last time.Time + // lastEvent is the latest time of a rate-limited event (past or future) + lastEvent time.Time +} + +// Limit returns the maximum overall event rate. +func (lim *Limiter) Limit() Limit { + lim.mu.Lock() + defer lim.mu.Unlock() + return lim.limit +} + +// Burst returns the maximum burst size. Burst is the maximum number of tokens +// that can be consumed in a single call to Allow, Reserve, or Wait, so higher +// Burst values allow more events to happen at once. +// A zero Burst allows no events, unless limit == Inf. +func (lim *Limiter) Burst() int { + return lim.burst +} + +// NewLimiter returns a new Limiter that allows events up to rate r and permits +// bursts of at most b tokens. +func NewLimiter(r Limit, b int) *Limiter { + return &Limiter{ + limit: r, + burst: b, + } +} + +// Allow is shorthand for AllowN(time.Now(), 1). +func (lim *Limiter) Allow() bool { + return lim.AllowN(time.Now(), 1) +} + +// AllowN reports whether n events may happen at time now. +// Use this method if you intend to drop / skip events that exceed the rate limit. +// Otherwise use Reserve or Wait. +func (lim *Limiter) AllowN(now time.Time, n int) bool { + return lim.reserveN(now, n, 0).ok +} + +// A Reservation holds information about events that are permitted by a Limiter to happen after a delay. +// A Reservation may be canceled, which may enable the Limiter to permit additional events. +type Reservation struct { + ok bool + lim *Limiter + tokens int + timeToAct time.Time + // This is the Limit at reservation time, it can change later. + limit Limit +} + +// OK returns whether the limiter can provide the requested number of tokens +// within the maximum wait time. If OK is false, Delay returns InfDuration, and +// Cancel does nothing. +func (r *Reservation) OK() bool { + return r.ok +} + +// Delay is shorthand for DelayFrom(time.Now()). +func (r *Reservation) Delay() time.Duration { + return r.DelayFrom(time.Now()) +} + +// InfDuration is the duration returned by Delay when a Reservation is not OK. +const InfDuration = time.Duration(1<<63 - 1) + +// DelayFrom returns the duration for which the reservation holder must wait +// before taking the reserved action. Zero duration means act immediately. +// InfDuration means the limiter cannot grant the tokens requested in this +// Reservation within the maximum wait time. +func (r *Reservation) DelayFrom(now time.Time) time.Duration { + if !r.ok { + return InfDuration + } + delay := r.timeToAct.Sub(now) + if delay < 0 { + return 0 + } + return delay +} + +// Cancel is shorthand for CancelAt(time.Now()). +func (r *Reservation) Cancel() { + r.CancelAt(time.Now()) + return +} + +// CancelAt indicates that the reservation holder will not perform the reserved action +// and reverses the effects of this Reservation on the rate limit as much as possible, +// considering that other reservations may have already been made. +func (r *Reservation) CancelAt(now time.Time) { + if !r.ok { + return + } + + r.lim.mu.Lock() + defer r.lim.mu.Unlock() + + if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) { + return + } + + // calculate tokens to restore + // The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved + // after r was obtained. These tokens should not be restored. + restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct)) + if restoreTokens <= 0 { + return + } + // advance time to now + now, _, tokens := r.lim.advance(now) + // calculate new number of tokens + tokens += restoreTokens + if burst := float64(r.lim.burst); tokens > burst { + tokens = burst + } + // update state + r.lim.last = now + r.lim.tokens = tokens + if r.timeToAct == r.lim.lastEvent { + prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens))) + if !prevEvent.Before(now) { + r.lim.lastEvent = prevEvent + } + } + + return +} + +// Reserve is shorthand for ReserveN(time.Now(), 1). +func (lim *Limiter) Reserve() *Reservation { + return lim.ReserveN(time.Now(), 1) +} + +// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen. +// The Limiter takes this Reservation into account when allowing future events. +// ReserveN returns false if n exceeds the Limiter's burst size. +// Usage example: +// r := lim.ReserveN(time.Now(), 1) +// if !r.OK() { +// // Not allowed to act! Did you remember to set lim.burst to be > 0 ? +// return +// } +// time.Sleep(r.Delay()) +// Act() +// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events. +// If you need to respect a deadline or cancel the delay, use Wait instead. +// To drop or skip events exceeding rate limit, use Allow instead. +func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation { + r := lim.reserveN(now, n, InfDuration) + return &r +} + +// Wait is shorthand for WaitN(ctx, 1). +func (lim *Limiter) Wait(ctx context.Context) (err error) { + return lim.WaitN(ctx, 1) +} + +// WaitN blocks until lim permits n events to happen. +// It returns an error if n exceeds the Limiter's burst size, the Context is +// canceled, or the expected wait time exceeds the Context's Deadline. +// The burst limit is ignored if the rate limit is Inf. +func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { + lim.mu.Lock() + burst := lim.burst + limit := lim.limit + lim.mu.Unlock() + + if n > burst && limit != Inf { + return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, lim.burst) + } + // Check if ctx is already cancelled + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + // Determine wait limit + now := time.Now() + waitLimit := InfDuration + if deadline, ok := ctx.Deadline(); ok { + waitLimit = deadline.Sub(now) + } + // Reserve + r := lim.reserveN(now, n, waitLimit) + if !r.ok { + return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n) + } + // Wait if necessary + delay := r.DelayFrom(now) + if delay == 0 { + return nil + } + t := time.NewTimer(delay) + defer t.Stop() + select { + case <-t.C: + // We can proceed. + return nil + case <-ctx.Done(): + // Context was canceled before we could proceed. Cancel the + // reservation, which may permit other events to proceed sooner. + r.Cancel() + return ctx.Err() + } +} + +// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit). +func (lim *Limiter) SetLimit(newLimit Limit) { + lim.SetLimitAt(time.Now(), newLimit) +} + +// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated +// or underutilized by those which reserved (using Reserve or Wait) but did not yet act +// before SetLimitAt was called. +func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit) { + lim.mu.Lock() + defer lim.mu.Unlock() + + now, _, tokens := lim.advance(now) + + lim.last = now + lim.tokens = tokens + lim.limit = newLimit +} + +// SetBurst is shorthand for SetBurstAt(time.Now(), newBurst). +func (lim *Limiter) SetBurst(newBurst int) { + lim.SetBurstAt(time.Now(), newBurst) +} + +// SetBurstAt sets a new burst size for the limiter. +func (lim *Limiter) SetBurstAt(now time.Time, newBurst int) { + lim.mu.Lock() + defer lim.mu.Unlock() + + now, _, tokens := lim.advance(now) + + lim.last = now + lim.tokens = tokens + lim.burst = newBurst +} + +// reserveN is a helper method for AllowN, ReserveN, and WaitN. +// maxFutureReserve specifies the maximum reservation wait duration allowed. +// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN. +func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation { + lim.mu.Lock() + + if lim.limit == Inf { + lim.mu.Unlock() + return Reservation{ + ok: true, + lim: lim, + tokens: n, + timeToAct: now, + } + } + + now, last, tokens := lim.advance(now) + + // Calculate the remaining number of tokens resulting from the request. + tokens -= float64(n) + + // Calculate the wait duration + var waitDuration time.Duration + if tokens < 0 { + waitDuration = lim.limit.durationFromTokens(-tokens) + } + + // Decide result + ok := n <= lim.burst && waitDuration <= maxFutureReserve + + // Prepare reservation + r := Reservation{ + ok: ok, + lim: lim, + limit: lim.limit, + } + if ok { + r.tokens = n + r.timeToAct = now.Add(waitDuration) + } + + // Update state + if ok { + lim.last = now + lim.tokens = tokens + lim.lastEvent = r.timeToAct + } else { + lim.last = last + } + + lim.mu.Unlock() + return r +} + +// advance calculates and returns an updated state for lim resulting from the passage of time. +// lim is not changed. +func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) { + last := lim.last + if now.Before(last) { + last = now + } + + // Avoid making delta overflow below when last is very old. + maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens) + elapsed := now.Sub(last) + if elapsed > maxElapsed { + elapsed = maxElapsed + } + + // Calculate the new number of tokens, due to time that passed. + delta := lim.limit.tokensFromDuration(elapsed) + tokens := lim.tokens + delta + if burst := float64(lim.burst); tokens > burst { + tokens = burst + } + + return now, last, tokens +} + +// durationFromTokens is a unit conversion function from the number of tokens to the duration +// of time it takes to accumulate them at a rate of limit tokens per second. +func (limit Limit) durationFromTokens(tokens float64) time.Duration { + seconds := tokens / float64(limit) + return time.Nanosecond * time.Duration(1e9*seconds) +} + +// tokensFromDuration is a unit conversion function from a time duration to the number of tokens +// which could be accumulated during that duration at a rate of limit tokens per second. +func (limit Limit) tokensFromDuration(d time.Duration) float64 { + // Split the integer and fractional parts ourself to minimize rounding errors. + // See golang.org/issues/34861. + sec := float64(d/time.Second) * float64(limit) + nsec := float64(d%time.Second) * float64(limit) + return sec + nsec/1e9 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 79bbd1c7..079212db 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -83,3 +83,5 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm +# golang.org/x/time v0.0.0-20191024005414-555d28b269f0 +golang.org/x/time/rate From 12cc53d69938860daedfbce89d4d29923555270f Mon Sep 17 00:00:00 2001 From: fatedier Date: Sat, 9 Nov 2019 01:13:30 +0800 Subject: [PATCH 10/15] update bandwidth_limit --- client/proxy/proxy.go | 2 +- models/config/proxy.go | 8 +++---- models/config/types.go | 46 +++++++++++++++++++++++-------------- models/config/types_test.go | 40 ++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 models/config/types_test.go diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index aca1b94e..268b317d 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -54,7 +54,7 @@ type Proxy interface { func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) { var limiter *rate.Limiter - limitBytes := pxyConf.GetBaseInfo().BandwithLimit.Bytes() + limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes() if limitBytes > 0 { limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes)) } diff --git a/models/config/proxy.go b/models/config/proxy.go index 2b129cfa..85c353ed 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -126,9 +126,9 @@ type BaseProxyConf struct { // version will be automatically selected. By default, this value is "". ProxyProtocolVersion string `json:"proxy_protocol_version"` - // BandwithLimit limit the proxy bandwith + // BandwidthLimit limit the proxy bandwidth // 0 means no limit - BandwithLimit BandwithQuantity `json:"bandwith_limit"` + BandwidthLimit BandwidthQuantity `json:"bandwidth_limit"` LocalSvrConf HealthCheckConf @@ -146,7 +146,7 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool { cfg.Group != cmp.Group || cfg.GroupKey != cmp.GroupKey || cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion || - cfg.BandwithLimit.Equal(&cmp.BandwithLimit) { + cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) { return false } if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) { @@ -190,7 +190,7 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i cfg.GroupKey = section["group_key"] cfg.ProxyProtocolVersion = section["proxy_protocol_version"] - if cfg.BandwithLimit, err = NewBandwithQuantity(section["bandwidth_limit"]); err != nil { + if cfg.BandwidthLimit, err = NewBandwidthQuantity(section["bandwidth_limit"]); err != nil { return err } diff --git a/models/config/types.go b/models/config/types.go index 9f3de661..87c240d5 100644 --- a/models/config/types.go +++ b/models/config/types.go @@ -15,6 +15,7 @@ package config import ( + "encoding/json" "errors" "strconv" "strings" @@ -25,14 +26,14 @@ const ( KB = 1024 ) -type BandwithQuantity struct { +type BandwidthQuantity struct { s string // MB or KB i int64 // bytes } -func NewBandwithQuantity(s string) (BandwithQuantity, error) { - q := BandwithQuantity{} +func NewBandwidthQuantity(s string) (BandwidthQuantity, error) { + q := BandwidthQuantity{} err := q.UnmarshalString(s) if err != nil { return q, err @@ -40,7 +41,7 @@ func NewBandwithQuantity(s string) (BandwithQuantity, error) { return q, nil } -func (q *BandwithQuantity) Equal(u *BandwithQuantity) bool { +func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool { if q == nil && u == nil { return true } @@ -50,13 +51,13 @@ func (q *BandwithQuantity) Equal(u *BandwithQuantity) bool { return false } -func (q *BandwithQuantity) String() string { +func (q *BandwidthQuantity) String() string { return q.s } -func (q *BandwithQuantity) UnmarshalString(s string) error { - q.s = strings.TrimSpace(s) - if q.s == "" { +func (q *BandwidthQuantity) UnmarshalString(s string) error { + s = strings.TrimSpace(s) + if s == "" { return nil } @@ -67,15 +68,15 @@ func (q *BandwithQuantity) UnmarshalString(s string) error { ) if strings.HasSuffix(s, "MB") { base = MB - s = strings.TrimSuffix(s, "MB") - f, err = strconv.ParseFloat(s, 64) + fstr := strings.TrimSuffix(s, "MB") + f, err = strconv.ParseFloat(fstr, 64) if err != nil { return err } } else if strings.HasSuffix(s, "KB") { base = KB - s = strings.TrimSuffix(s, "KB") - f, err = strconv.ParseFloat(s, 64) + fstr := strings.TrimSuffix(s, "KB") + f, err = strconv.ParseFloat(fstr, 64) if err != nil { return err } @@ -83,18 +84,29 @@ func (q *BandwithQuantity) UnmarshalString(s string) error { return errors.New("unit not support") } + q.s = s q.i = int64(f * float64(base)) return nil } -func (q *BandwithQuantity) UnmarshalJSON(b []byte) error { - return q.UnmarshalString(string(b)) +func (q *BandwidthQuantity) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + return q.UnmarshalString(str) } -func (q *BandwithQuantity) MarshalJSON() ([]byte, error) { - return []byte(q.s), nil +func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) { + return []byte("\"" + q.s + "\""), nil } -func (q *BandwithQuantity) Bytes() int64 { +func (q *BandwidthQuantity) Bytes() int64 { return q.i } diff --git a/models/config/types_test.go b/models/config/types_test.go new file mode 100644 index 00000000..ab03dfd2 --- /dev/null +++ b/models/config/types_test.go @@ -0,0 +1,40 @@ +// Copyright 2019 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +type Wrap struct { + B BandwidthQuantity `json:"b"` + Int int `json:"int"` +} + +func TestBandwidthQuantity(t *testing.T) { + assert := assert.New(t) + + var w Wrap + err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w) + assert.NoError(err) + assert.EqualValues(1*KB, w.B.Bytes()) + + buf, err := json.Marshal(&w) + assert.NoError(err) + assert.Equal(`{"b":"1KB","int":5}`, string(buf)) +} From 8affab1a2b42e71916fa08c666edf3a5c6ca0e37 Mon Sep 17 00:00:00 2001 From: CallanTaylor Date: Tue, 12 Nov 2019 11:38:55 +1300 Subject: [PATCH 11/15] Close file --- assets/assets.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/assets.go b/assets/assets.go index 504c0e82..2a9e9772 100644 --- a/assets/assets.go +++ b/assets/assets.go @@ -55,6 +55,7 @@ func ReadFile(file string) (content string, err error) { if err != nil { return content, err } + defer file.Close() buf, err := ioutil.ReadAll(file) if err != nil { return content, err @@ -65,6 +66,7 @@ func ReadFile(file string) (content string, err error) { if err != nil { return content, err } + defer file.Close() buf, err := ioutil.ReadAll(file) if err != nil { return content, err From 56c53909aa5181d6b5c3e929f4c9a6e2ed3150a1 Mon Sep 17 00:00:00 2001 From: kingjcy Date: Wed, 20 Nov 2019 20:56:55 +0800 Subject: [PATCH 12/15] plugin http2https plugin http2https --- models/plugin/http2https.go | 109 ++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 models/plugin/http2https.go diff --git a/models/plugin/http2https.go b/models/plugin/http2https.go new file mode 100644 index 00000000..04871c97 --- /dev/null +++ b/models/plugin/http2https.go @@ -0,0 +1,109 @@ +// Copyright 2019 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "strings" + + frpNet "github.com/fatedier/frp/utils/net" +) + +const PluginHTTP2HTTPS = "http2https" + +func init() { + Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin) +} + +type HTTP2HTTPSPlugin struct { + hostHeaderRewrite string + localAddr string + headers map[string]string + + l *Listener + s *http.Server +} + +func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) { + localAddr := params["plugin_local_addr"] + hostHeaderRewrite := params["plugin_host_header_rewrite"] + headers := make(map[string]string) + for k, v := range params { + if !strings.HasPrefix(k, "plugin_header_") { + continue + } + if k = strings.TrimPrefix(k, "plugin_header_"); k != "" { + headers[k] = v + } + } + + if localAddr == "" { + return nil, fmt.Errorf("plugin_local_addr is required") + } + + listener := NewProxyListener() + + p := &HTTPS2HTTPPlugin{ + localAddr: localAddr, + hostHeaderRewrite: hostHeaderRewrite, + headers: headers, + l: listener, + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + rp := &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "https" + req.URL.Host = p.localAddr + if p.hostHeaderRewrite != "" { + req.Host = p.hostHeaderRewrite + } + for k, v := range p.headers { + req.Header.Set(k, v) + } + }, + Transport: tr, + } + + p.s = &http.Server{ + Handler: rp, + } + + go p.s.Serve(listener) + + return p, nil +} + + +func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) { + wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) + p.l.PutConn(wrapConn) +} + +func (p *HTTP2HTTPSPlugin) Name() string { + return PluginHTTP2HTTPS +} + +func (p *HTTP2HTTPSPlugin) Close() error { + return nil +} From 62af5c8844f2436d6366fbf6192620abc52e99be Mon Sep 17 00:00:00 2001 From: kingjcy Date: Fri, 22 Nov 2019 15:18:20 +0800 Subject: [PATCH 13/15] handle close --- models/plugin/http2https.go | 3 +++ models/plugin/https2http.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/models/plugin/http2https.go b/models/plugin/http2https.go index 04871c97..6f5965e6 100644 --- a/models/plugin/http2https.go +++ b/models/plugin/http2https.go @@ -105,5 +105,8 @@ func (p *HTTP2HTTPSPlugin) Name() string { } func (p *HTTP2HTTPSPlugin) Close() error { + if err := p.s.Close();err != nil { + return err + } return nil } diff --git a/models/plugin/https2http.go b/models/plugin/https2http.go index 65540356..af5b6af9 100644 --- a/models/plugin/https2http.go +++ b/models/plugin/https2http.go @@ -126,5 +126,8 @@ func (p *HTTPS2HTTPPlugin) Name() string { } func (p *HTTPS2HTTPPlugin) Close() error { + if err := p.s.Close();err != nil { + return err + } return nil } From 1cdceee347b13dc9198595183e3b66e2970c23cc Mon Sep 17 00:00:00 2001 From: fatedier Date: Tue, 26 Nov 2019 09:15:24 +0800 Subject: [PATCH 14/15] bump version to v0.30.0 --- utils/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/version/version.go b/utils/version/version.go index cc47e55f..dde6de96 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.29.1" +var version string = "0.30.0" func Full() string { return version From c63737ab3ecb31047c1fa70875c1c0de233f6347 Mon Sep 17 00:00:00 2001 From: fatedier Date: Tue, 26 Nov 2019 10:23:37 +0800 Subject: [PATCH 15/15] update doc for bandwith limit --- README.md | 17 +++++++++++++++++ README_zh.md | 19 +++++++++++++++++++ conf/frpc_full.ini | 8 ++++++++ 3 files changed, 44 insertions(+) diff --git a/README.md b/README.md index ab9d8356..2890d645 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ frp also has a P2P connect mode. * [Get proxy status from client](#get-proxy-status-from-client) * [Only allowing certain ports on the server](#only-allowing-certain-ports-on-the-server) * [Port Reuse](#port-reuse) + * [Bandwidth Limit](#bandwidth-limit) + * [For Each Proxy](#for-each-proxy) * [TCP Stream Multiplexing](#tcp-stream-multiplexing) * [Support KCP Protocol](#support-kcp-protocol) * [Connection Pooling](#connection-pooling) @@ -495,6 +497,21 @@ allow_ports = 2000-3000,3001,3003,4000-50000 We would like to try to allow multiple proxies bind a same remote port with different protocols in the future. +### Bandwidth Limit + +#### For Each Proxy + +```ini +# frpc.ini +[ssh] +type = tcp +local_port = 22 +remote_port = 6000 +bandwidth_limit = 1MB +``` + +Set `bandwidth_limit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`. + ### TCP Stream Multiplexing frp 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. diff --git a/README_zh.md b/README_zh.md index a9e03cb2..6f62e387 100644 --- a/README_zh.md +++ b/README_zh.md @@ -33,6 +33,8 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp * [客户端查看代理状态](#客户端查看代理状态) * [端口白名单](#端口白名单) * [端口复用](#端口复用) + * [限速](#限速) + * [代理限速](#代理限速) * [TCP 多路复用](#tcp-多路复用) * [底层通信可选 kcp 协议](#底层通信可选-kcp-协议) * [连接池](#连接池) @@ -531,6 +533,23 @@ allow_ports = 2000-3000,3001,3003,4000-50000 后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。 +### 限速 + +#### 代理限速 + +目前支持在客户端的代理配置中设置代理级别的限速,限制单个 proxy 可以占用的带宽。 + +```ini +# frpc.ini +[ssh] +type = tcp +local_port = 22 +remote_port = 6000 +bandwith_limit = 1MB +``` + +在代理配置中增加 `bandwith_limit` 字段启用此功能,目前仅支持 `MB` 和 `KB` 单位。 + ### TCP 多路复用 从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。 diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index afa07079..14ca6ed3 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -207,6 +207,14 @@ plugin_key_path = ./server.key plugin_host_header_rewrite = 127.0.0.1 plugin_header_X-From-Where = frp +[plugin_http2https] +type = http +custom_domains = test.yourdomain.com +plugin = http2https +plugin_local_addr = 127.0.0.1:443 +plugin_host_header_rewrite = 127.0.0.1 +plugin_header_X-From-Where = frp + [secret_tcp] # If the type is secret tcp, remote_port is useless # Who want to connect local port should deploy another frpc with stcp proxy and role is visitor