diff --git a/default.yaml b/default.yaml index a8d12236..5d1dfee0 100644 --- a/default.yaml +++ b/default.yaml @@ -180,6 +180,17 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true + # idle timeouts for inactive clients + idle-timeouts: + # give the client this long to complete connection registration (i.e. the initial + # IRC handshake, including capability negotiation and SASL) + registration: 60s + # if the client hasn't sent anything for this long, send them a PING + ping: 1m30s + # if the client hasn't sent anything for this long (including the PONG to the + # above PING), disconnect them + disconnect: 2m30s + # relaying using the RELAYMSG command relaymsg: # is relaymsg enabled at all? diff --git a/irc/client.go b/irc/client.go index d90789ea..be98a5e9 100644 --- a/irc/client.go +++ b/irc/client.go @@ -53,18 +53,16 @@ const ( pushQueueLengthPerClient = 16 ) +var ( + // idle timeouts for client connections, set from the config + RegisterTimeout, PingTimeout, DisconnectTimeout time.Duration +) + const ( - // RegisterTimeout is how long clients have to register before we disconnect them - RegisterTimeout = time.Minute - // DefaultIdleTimeout is how long without traffic before we send the client a PING - DefaultIdleTimeout = time.Minute + 30*time.Second // For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug // (single-onion circuits will close unless the client sends data once every 60 seconds): // https://bugs.torproject.org/29665 - TorIdleTimeout = time.Second * 30 - // This is how long a client gets without sending any message, including the PONG to our - // PING, before we disconnect them: - DefaultTotalTimeout = 2*time.Minute + 30*time.Second + TorPingTimeout = time.Second * 30 // round off the ping interval by this much, see below: PingCoalesceThreshold = time.Second @@ -863,19 +861,19 @@ func (client *Client) updateIdleTimer(session *Session, now time.Time) { session.pingSent = false if session.idleTimer == nil { - pingTimeout := DefaultIdleTimeout - if session.isTor { - pingTimeout = TorIdleTimeout + pingTimeout := PingTimeout + if session.isTor && TorPingTimeout < pingTimeout { + pingTimeout = TorPingTimeout } session.idleTimer = time.AfterFunc(pingTimeout, session.handleIdleTimeout) } } func (session *Session) handleIdleTimeout() { - totalTimeout := DefaultTotalTimeout - pingTimeout := DefaultIdleTimeout - if session.isTor { - pingTimeout = TorIdleTimeout + totalTimeout := DisconnectTimeout + pingTimeout := PingTimeout + if session.isTor && TorPingTimeout < pingTimeout { + pingTimeout = TorPingTimeout } session.client.stateMutex.Lock() diff --git a/irc/config.go b/irc/config.go index 6939cf5c..22da3d97 100644 --- a/irc/config.go +++ b/irc/config.go @@ -45,6 +45,10 @@ import ( "github.com/ergochat/ergo/irc/webpush" ) +const ( + defaultProxyDeadline = time.Minute +) + // here's how this works: exported (capitalized) members of the config structs // are defined in the YAML file and deserialized directly from there. They may // be postprocessed and overwritten by LoadConfig. Unexported (lowercase) members @@ -577,7 +581,12 @@ type Config struct { MOTD string motdLines []string MOTDFormatting bool `yaml:"motd-formatting"` - Relaymsg struct { + IdleTimeouts struct { + Registration time.Duration + Ping time.Duration + Disconnect time.Duration + } `yaml:"idle-timeouts"` + Relaymsg struct { Enabled bool Separators string AvailableToChanops bool `yaml:"available-to-chanops"` @@ -986,7 +995,7 @@ func (conf *Config) prepareListeners() (err error) { conf.Server.trueListeners = make(map[string]utils.ListenerConfig) for addr, block := range conf.Server.Listeners { var lconf utils.ListenerConfig - lconf.ProxyDeadline = RegisterTimeout + lconf.ProxyDeadline = defaultProxyDeadline lconf.Tor = block.Tor lconf.STSOnly = block.STSOnly if lconf.STSOnly && !conf.Server.STS.Enabled { @@ -1236,6 +1245,23 @@ func LoadConfig(filename string) (config *Config, err error) { } } + if config.Server.IdleTimeouts.Registration <= 0 { + config.Server.IdleTimeouts.Registration = time.Minute + } + if config.Server.IdleTimeouts.Ping <= 0 { + config.Server.IdleTimeouts.Ping = time.Minute + 30*time.Second + } + if config.Server.IdleTimeouts.Disconnect <= 0 { + config.Server.IdleTimeouts.Disconnect = 2*time.Minute + 30*time.Second + } + + if !(config.Server.IdleTimeouts.Ping < config.Server.IdleTimeouts.Disconnect) { + return nil, fmt.Errorf( + "ping timeout %v must be strictly less than disconnect timeout %v, to give the client time to respond", + config.Server.IdleTimeouts.Ping, config.Server.IdleTimeouts.Disconnect, + ) + } + if config.Server.CoerceIdent != "" { if config.Server.CheckIdent { return nil, errors.New("Can't configure both check-ident and coerce-ident") diff --git a/irc/server.go b/irc/server.go index 7aee6705..c59ac571 100644 --- a/irc/server.go +++ b/irc/server.go @@ -690,6 +690,9 @@ func (server *Server) applyConfig(config *Config) (err error) { globalCasemappingSetting = config.Server.Casemapping globalUtf8EnforcementSetting = config.Server.EnforceUtf8 MaxLineLen = config.Server.MaxLineLen + RegisterTimeout = config.Server.IdleTimeouts.Registration + PingTimeout = config.Server.IdleTimeouts.Ping + DisconnectTimeout = config.Server.IdleTimeouts.Disconnect } else { // enforce configs that can't be changed after launch: if server.name != config.Server.Name { @@ -715,6 +718,8 @@ func (server *Server) applyConfig(config *Config) (err error) { return fmt.Errorf("Cannot enable MySQL after launching the server, rehash aborted") } else if oldConfig.Server.MaxLineLen != config.Server.MaxLineLen { return fmt.Errorf("Cannot change max-line-len after launching the server, rehash aborted") + } else if oldConfig.Server.IdleTimeouts != config.Server.IdleTimeouts { + return fmt.Errorf("Cannot change idle-timeouts after launching the server, rehash aborted") } } diff --git a/traditional.yaml b/traditional.yaml index 4b29ec34..70f31b81 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -154,6 +154,17 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true + # idle timeouts for inactive clients + idle-timeouts: + # give the client this long to complete connection registration (i.e. the initial + # IRC handshake, including capability negotiation and SASL) + registration: 60s + # if the client hasn't sent anything for this long, send them a PING + ping: 1m30s + # if the client hasn't sent anything for this long (including the PONG to the + # above PING), disconnect them + disconnect: 2m30s + # relaying using the RELAYMSG command relaymsg: # is relaymsg enabled at all?