mirror of
https://github.com/fatedier/frp.git
synced 2024-12-13 18:21:14 +01:00
allow to disable application layer heartbeat to reduce traffic cost (#2758)
fix #2754
This commit is contained in:
parent
4bfc89d988
commit
293003fcdb
@ -311,16 +311,27 @@ func (ctl *Control) msgHandler() {
|
|||||||
}()
|
}()
|
||||||
defer ctl.msgHandlerShutdown.Done()
|
defer ctl.msgHandlerShutdown.Done()
|
||||||
|
|
||||||
|
var hbSendCh <-chan time.Time
|
||||||
|
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
||||||
|
// Just keep it here to keep compatible with old version frps.
|
||||||
|
if ctl.clientCfg.HeartbeatInterval > 0 {
|
||||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
||||||
defer hbSend.Stop()
|
defer hbSend.Stop()
|
||||||
|
hbSendCh = hbSend.C
|
||||||
|
}
|
||||||
|
|
||||||
|
var hbCheckCh <-chan time.Time
|
||||||
|
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||||
|
if ctl.clientCfg.HeartbeatInterval > 0 && ctl.clientCfg.HeartbeatTimeout > 0 && !ctl.clientCfg.TCPMux {
|
||||||
hbCheck := time.NewTicker(time.Second)
|
hbCheck := time.NewTicker(time.Second)
|
||||||
defer hbCheck.Stop()
|
defer hbCheck.Stop()
|
||||||
|
hbCheckCh = hbCheck.C
|
||||||
|
}
|
||||||
|
|
||||||
ctl.lastPong = time.Now()
|
ctl.lastPong = time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-hbSend.C:
|
case <-hbSendCh:
|
||||||
// send heartbeat to server
|
// send heartbeat to server
|
||||||
xl.Debug("send heartbeat to server")
|
xl.Debug("send heartbeat to server")
|
||||||
pingMsg := &msg.Ping{}
|
pingMsg := &msg.Ping{}
|
||||||
@ -329,7 +340,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctl.sendCh <- pingMsg
|
ctl.sendCh <- pingMsg
|
||||||
case <-hbCheck.C:
|
case <-hbCheckCh:
|
||||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
// let reader() stop
|
// let reader() stop
|
||||||
|
@ -249,7 +249,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
|||||||
|
|
||||||
if svr.cfg.TCPMux {
|
if svr.cfg.TCPMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
|
||||||
fmuxCfg.LogOutput = io.Discard
|
fmuxCfg.LogOutput = io.Discard
|
||||||
session, err = fmux.Client(conn, fmuxCfg)
|
session, err = fmux.Client(conn, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,6 +61,9 @@ pool_count = 5
|
|||||||
|
|
||||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||||
tcp_mux = true
|
tcp_mux = true
|
||||||
|
# specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcp_mux is true.
|
||||||
|
# tcp_mux_keepalive_interval = 60
|
||||||
|
|
||||||
# your proxy name will be changed to {user}.{proxy}
|
# your proxy name will be changed to {user}.{proxy}
|
||||||
user = your_name
|
user = your_name
|
||||||
@ -89,7 +92,8 @@ tls_enable = true
|
|||||||
# start = ssh,dns
|
# start = ssh,dns
|
||||||
|
|
||||||
# heartbeat configure, it's not recommended to modify the default value
|
# heartbeat configure, it's not recommended to modify the default value
|
||||||
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 90
|
# The default value of heartbeat_interval is 10 and heartbeat_timeout is 90. Set negative value
|
||||||
|
# to disable it.
|
||||||
# heartbeat_interval = 30
|
# heartbeat_interval = 30
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ oidc_skip_expiry_check = false
|
|||||||
oidc_skip_issuer_check = false
|
oidc_skip_issuer_check = false
|
||||||
|
|
||||||
# heartbeat configure, it's not recommended to modify the default value
|
# heartbeat configure, it's not recommended to modify the default value
|
||||||
# the default value of heartbeat_timeout is 90
|
# the default value of heartbeat_timeout is 90. Set negative value to disable it.
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
|
|
||||||
# user_conn_timeout configure, it's not recommended to modify the default value
|
# user_conn_timeout configure, it's not recommended to modify the default value
|
||||||
@ -121,6 +121,9 @@ subdomain_host = frps.com
|
|||||||
|
|
||||||
# if tcp stream multiplexing is used, default is true
|
# if tcp stream multiplexing is used, default is true
|
||||||
tcp_mux = true
|
tcp_mux = true
|
||||||
|
# specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcp_mux is true.
|
||||||
|
# tcp_mux_keepalive_interval = 60
|
||||||
|
|
||||||
# custom 404 page for HTTP requests
|
# custom 404 page for HTTP requests
|
||||||
# custom_404_page = /path/to/404.html
|
# custom_404_page = /path/to/404.html
|
||||||
|
@ -86,6 +86,9 @@ type ClientCommonConf struct {
|
|||||||
// the server must have TCP multiplexing enabled as well. By default, this
|
// the server must have TCP multiplexing enabled as well. By default, this
|
||||||
// value is true.
|
// value is true.
|
||||||
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
||||||
|
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||||
|
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||||
|
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||||
// User specifies a prefix for proxy names to distinguish them from other
|
// User specifies a prefix for proxy names to distinguish them from other
|
||||||
// clients. If this value is not "", proxy names will automatically be
|
// clients. If this value is not "", proxy names will automatically be
|
||||||
// changed to "{user}.{proxy_name}". By default, this value is "".
|
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||||
@ -129,11 +132,11 @@ type ClientCommonConf struct {
|
|||||||
DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
|
DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
|
||||||
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||||
// server, in seconds. It is not recommended to change this value. By
|
// server, in seconds. It is not recommended to change this value. By
|
||||||
// default, this value is 30.
|
// default, this value is 30. Set negative value to disable it.
|
||||||
HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"`
|
HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"`
|
||||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||||
// before the connection is terminated, in seconds. It is not recommended
|
// before the connection is terminated, in seconds. It is not recommended
|
||||||
// to change this value. By default, this value is 90.
|
// to change this value. By default, this value is 90. Set negative value to disable it.
|
||||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||||
// Client meta info
|
// Client meta info
|
||||||
Metas map[string]string `ini:"-" json:"metas"`
|
Metas map[string]string `ini:"-" json:"metas"`
|
||||||
@ -163,6 +166,7 @@ func GetDefaultClientConf() ClientCommonConf {
|
|||||||
AssetsDir: "",
|
AssetsDir: "",
|
||||||
PoolCount: 1,
|
PoolCount: 1,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
User: "",
|
User: "",
|
||||||
DNSServer: "",
|
DNSServer: "",
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
@ -189,13 +193,11 @@ func (cfg *ClientCommonConf) Complete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *ClientCommonConf) Validate() error {
|
func (cfg *ClientCommonConf) Validate() error {
|
||||||
if cfg.HeartbeatInterval <= 0 {
|
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||||
return fmt.Errorf("invalid heartbeat_interval")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.TLSEnable == false {
|
if cfg.TLSEnable == false {
|
||||||
if cfg.TLSCertFile != "" {
|
if cfg.TLSCertFile != "" {
|
||||||
|
@ -274,6 +274,7 @@ func Test_LoadClientCommonConf(t *testing.T) {
|
|||||||
AssetsDir: "./static9",
|
AssetsDir: "./static9",
|
||||||
PoolCount: 59,
|
PoolCount: 59,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
User: "your_name",
|
User: "your_name",
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
@ -118,6 +118,9 @@ type ServerCommonConf struct {
|
|||||||
// from a client to share a single TCP connection. By default, this value
|
// from a client to share a single TCP connection. By default, this value
|
||||||
// is true.
|
// is true.
|
||||||
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
||||||
|
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||||
|
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||||
|
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||||
// value is "", a default page will be displayed. By default, this value is
|
// value is "", a default page will be displayed. By default, this value is
|
||||||
// "".
|
// "".
|
||||||
@ -154,7 +157,7 @@ type ServerCommonConf struct {
|
|||||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||||
// before terminating the connection. It is not recommended to change this
|
// before terminating the connection. It is not recommended to change this
|
||||||
// value. By default, this value is 90.
|
// value. By default, this value is 90. Set negative value to disable it.
|
||||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||||
// UserConnTimeout specifies the maximum time to wait for a work
|
// UserConnTimeout specifies the maximum time to wait for a work
|
||||||
// connection. By default, this value is 10.
|
// connection. By default, this value is 10.
|
||||||
@ -194,6 +197,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
SubDomainHost: "",
|
SubDomainHost: "",
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
|
@ -139,6 +139,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
TLSTrustedCaFile: "ca.crt",
|
TLSTrustedCaFile: "ca.crt",
|
||||||
SubDomainHost: "frps.com",
|
SubDomainHost: "frps.com",
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
UDPPacketSize: 1509,
|
UDPPacketSize: 1509,
|
||||||
|
|
||||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||||
@ -189,6 +190,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
HeartbeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
|
@ -400,12 +400,19 @@ func (ctl *Control) manager() {
|
|||||||
defer ctl.allShutdown.Start()
|
defer ctl.allShutdown.Start()
|
||||||
defer ctl.managerShutdown.Done()
|
defer ctl.managerShutdown.Done()
|
||||||
|
|
||||||
|
var heartbeatCh <-chan time.Time
|
||||||
|
if ctl.serverCfg.TCPMux || ctl.serverCfg.HeartbeatTimeout <= 0 {
|
||||||
|
// Don't need application heartbeat here.
|
||||||
|
// yamux will do same thing.
|
||||||
|
} else {
|
||||||
heartbeat := time.NewTicker(time.Second)
|
heartbeat := time.NewTicker(time.Second)
|
||||||
defer heartbeat.Stop()
|
defer heartbeat.Stop()
|
||||||
|
heartbeatCh = heartbeat.C
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-heartbeat.C:
|
case <-heartbeatCh:
|
||||||
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
return
|
return
|
||||||
|
@ -406,7 +406,7 @@ func (svr *Service) HandleListener(l net.Listener) {
|
|||||||
go func(ctx context.Context, frpConn net.Conn) {
|
go func(ctx context.Context, frpConn net.Conn) {
|
||||||
if svr.cfg.TCPMux {
|
if svr.cfg.TCPMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
|
||||||
fmuxCfg.LogOutput = io.Discard
|
fmuxCfg.LogOutput = io.Discard
|
||||||
session, err := fmux.Server(frpConn, fmuxCfg)
|
session, err := fmux.Server(frpConn, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
48
test/e2e/features/heartbeat.go
Normal file
48
test/e2e/features/heartbeat.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("[Feature: Heartbeat]", func() {
|
||||||
|
f := framework.NewDefaultFramework()
|
||||||
|
|
||||||
|
It("disable application layer heartbeat", func() {
|
||||||
|
serverPort := f.AllocPort()
|
||||||
|
serverConf := fmt.Sprintf(`
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = %d
|
||||||
|
heartbeat_timeout = -1
|
||||||
|
tcp_mux_keepalive_interval = 2
|
||||||
|
`, serverPort)
|
||||||
|
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf := fmt.Sprintf(`
|
||||||
|
[common]
|
||||||
|
server_port = %d
|
||||||
|
log_level = trace
|
||||||
|
heartbeat_interval = -1
|
||||||
|
heartbeat_timeout = -1
|
||||||
|
tcp_mux_keepalive_interval = 2
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = %d
|
||||||
|
remote_port = %d
|
||||||
|
`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||||
|
|
||||||
|
// run frps and frpc
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user