diff --git a/Release.md b/Release.md index 725ebe94..12dc5488 100644 --- a/Release.md +++ b/Release.md @@ -1,3 +1,9 @@ +### Notable Changes + +We have optimized the heartbeat mechanism when tcpmux is enabled (enabled by default). The default value of `heartbeatInterval` has been adjusted to -1. This update ensures that when tcpmux is active, the client does not send additional heartbeats to the server. Since tcpmux incorporates its own heartbeat system, this change effectively reduces unnecessary data consumption, streamlining communication efficiency between client and server. + +When connecting to frps versions older than v0.39.0 might encounter compatibility issues due to changes in the heartbeat mechanism. As a temporary workaround, setting the `heartbeatInterval` to 30 can help maintain stable connectivity with these older versions. We recommend updating to the latest frps version to leverage full functionality and improvements. + ### Features * Show tcpmux proxies on the frps dashboard. diff --git a/client/control.go b/client/control.go index e1916890..eeea1285 100644 --- a/client/control.go +++ b/client/control.go @@ -20,8 +20,6 @@ import ( "sync/atomic" "time" - "github.com/samber/lo" - "github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/visitor" "github.com/fatedier/frp/pkg/auth" @@ -236,10 +234,8 @@ func (ctl *Control) registerMsgHandlers() { func (ctl *Control) heartbeatWorker() { xl := ctl.xl - // TODO(fatedier): Change default value of HeartbeatInterval to -1 if tcpmux is enabled. - // Users can still enable heartbeat feature by setting HeartbeatInterval to a positive value. if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 { - // send heartbeat to server + // Send heartbeat to server. sendHeartBeat := func() (bool, error) { xl.Debugf("send heartbeat to server") pingMsg := &msg.Ping{} @@ -263,10 +259,8 @@ func (ctl *Control) heartbeatWorker() { ) } - // Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature. - if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 && - !lo.FromPtr(ctl.sessionCtx.Common.Transport.TCPMux) { - + // Check heartbeat timeout. + if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 { go wait.Until(func() { if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second { xl.Warnf("heartbeat timeout") diff --git a/pkg/config/v1/client.go b/pkg/config/v1/client.go index 52b87690..35d8071c 100644 --- a/pkg/config/v1/client.go +++ b/pkg/config/v1/client.go @@ -136,8 +136,14 @@ func (c *ClientTransportConfig) Complete() { c.PoolCount = util.EmptyOr(c.PoolCount, 1) c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true)) c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60) - c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30) - c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90) + if lo.FromPtr(c.TCPMux) { + // If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux. + c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, -1) + c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1) + } else { + c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30) + c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90) + } c.QUIC.Complete() c.TLS.Complete() } diff --git a/pkg/config/v1/server.go b/pkg/config/v1/server.go index 03b05d9d..c4ef59de 100644 --- a/pkg/config/v1/server.go +++ b/pkg/config/v1/server.go @@ -179,7 +179,12 @@ func (c *ServerTransportConfig) Complete() { c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60) c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200) c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5) - c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90) + if lo.FromPtr(c.TCPMux) { + // If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux. + c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1) + } else { + c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90) + } c.QUIC.Complete() if c.TLS.TrustedCaFile != "" { c.TLS.Force = true diff --git a/server/control.go b/server/control.go index ea8a34c1..0b6b3174 100644 --- a/server/control.go +++ b/server/control.go @@ -297,20 +297,18 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { } func (ctl *Control) heartbeatWorker() { - xl := ctl.xl - - // Don't need application heartbeat if TCPMux is enabled, - // yamux will do same thing. - // TODO(fatedier): let default HeartbeatTimeout to -1 if TCPMux is enabled. Users can still set it to positive value to enable it. - if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 { - go wait.Until(func() { - if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second { - xl.Warnf("heartbeat timeout") - ctl.conn.Close() - return - } - }, time.Second, ctl.doneCh) + if ctl.serverCfg.Transport.HeartbeatTimeout <= 0 { + return } + + xl := ctl.xl + go wait.Until(func() { + if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second { + xl.Warnf("heartbeat timeout") + ctl.conn.Close() + return + } + }, time.Second, ctl.doneCh) } // block until Control closed