3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 07:59:31 +01:00
ergo/irc/idletimer.go

269 lines
6.8 KiB
Go
Raw Normal View History

2017-10-15 18:24:28 +02:00
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"fmt"
"sync"
"time"
2019-02-18 04:59:13 +01:00
"github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/caps"
2017-10-15 18:24:28 +02:00
)
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
// Resumeable clients (clients who have negotiated caps.Resume) get longer:
ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
)
2017-10-15 18:24:28 +02:00
// client idleness state machine
type TimerState uint
const (
TimerUnregistered TimerState = iota // client is unregistered
TimerActive // client is actively sending commands
TimerIdle // client is idle, we sent PING and are waiting for PONG
2017-12-07 05:15:35 +01:00
TimerDead // client was terminated
2017-10-15 18:24:28 +02:00
)
type IdleTimer struct {
2017-11-22 10:41:11 +01:00
sync.Mutex // tier 1
2017-10-15 18:24:28 +02:00
// immutable after construction
2018-01-30 05:26:29 +01:00
registerTimeout time.Duration
client *Client
2017-10-15 18:24:28 +02:00
// mutable
2018-01-30 05:26:29 +01:00
idleTimeout time.Duration
quitTimeout time.Duration
2018-01-30 05:26:29 +01:00
state TimerState
timer *time.Timer
2017-10-15 18:24:28 +02:00
}
// Initialize sets up an IdleTimer and starts counting idle time;
// if there is no activity from the client, it will eventually be stopped.
func (it *IdleTimer) Initialize(client *Client) {
it.client = client
it.registerTimeout = RegisterTimeout
it.idleTimeout, it.quitTimeout = it.recomputeDurations()
it.Lock()
defer it.Unlock()
it.state = TimerUnregistered
it.resetTimeout()
2017-10-15 18:24:28 +02:00
}
// recomputeDurations recomputes the idle and quit durations, given the client's caps.
func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duration) {
totalTimeout := DefaultTotalTimeout
2018-01-30 05:26:29 +01:00
// if they have the resume cap, wait longer before pinging them out
// to give them a chance to resume their connection
if it.client.capabilities.Has(caps.Resume) {
totalTimeout = ResumeableTotalTimeout
2018-01-30 05:26:29 +01:00
}
idleTimeout = DefaultIdleTimeout
if it.client.isTor {
idleTimeout = TorIdleTimeout
}
quitTimeout = totalTimeout - idleTimeout
return
2018-01-30 05:26:29 +01:00
}
2017-12-07 05:15:35 +01:00
func (it *IdleTimer) Touch() {
idleTimeout, quitTimeout := it.recomputeDurations()
2018-01-30 05:26:29 +01:00
2017-12-07 05:15:35 +01:00
it.Lock()
defer it.Unlock()
it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
2017-12-07 05:15:35 +01:00
// a touch transitions TimerUnregistered or TimerIdle into TimerActive
if it.state != TimerDead {
it.state = TimerActive
it.resetTimeout()
2017-10-15 18:24:28 +02:00
}
}
2017-12-07 05:15:35 +01:00
func (it *IdleTimer) processTimeout() {
idleTimeout, quitTimeout := it.recomputeDurations()
2018-01-30 05:26:29 +01:00
2017-12-07 05:15:35 +01:00
var previousState TimerState
func() {
it.Lock()
defer it.Unlock()
it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
2017-12-07 05:15:35 +01:00
previousState = it.state
// TimerActive transitions to TimerIdle, all others to TimerDead
if it.state == TimerActive {
// send them a ping, give them time to respond
it.state = TimerIdle
it.resetTimeout()
} else {
it.state = TimerDead
}
}()
2017-10-15 18:24:28 +02:00
2017-12-07 05:15:35 +01:00
if previousState == TimerActive {
it.client.Ping()
} else {
it.client.Quit(it.quitMessage(previousState))
2018-01-21 02:59:52 +01:00
it.client.destroy(false)
2017-10-15 18:24:28 +02:00
}
}
// Stop stops counting idle time.
func (it *IdleTimer) Stop() {
if it == nil {
return
}
2017-10-15 18:24:28 +02:00
it.Lock()
defer it.Unlock()
2017-12-07 05:15:35 +01:00
it.state = TimerDead
it.resetTimeout()
}
func (it *IdleTimer) resetTimeout() {
if it.timer != nil {
it.timer.Stop()
}
var nextTimeout time.Duration
switch it.state {
case TimerUnregistered:
nextTimeout = it.registerTimeout
case TimerActive:
2018-01-30 05:26:29 +01:00
nextTimeout = it.idleTimeout
2017-12-07 05:15:35 +01:00
case TimerIdle:
nextTimeout = it.quitTimeout
case TimerDead:
return
}
it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
2017-10-15 18:24:28 +02:00
}
func (it *IdleTimer) quitMessage(state TimerState) string {
switch state {
case TimerUnregistered:
return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
case TimerIdle:
// how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
2018-01-30 05:26:29 +01:00
it.Lock()
defer it.Unlock()
2017-10-15 18:24:28 +02:00
return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
default:
// shouldn't happen
return ""
}
}
// NickTimer manages timing out of clients who are squatting reserved nicks
type NickTimer struct {
sync.Mutex // tier 1
// immutable after construction
timeout time.Duration
client *Client
// mutable
stopped bool
nick string
accountForNick string
account string
timer *time.Timer
}
// NewNickTimer sets up a new nick timer (returning nil if timeout enforcement is not enabled)
func NewNickTimer(client *Client) *NickTimer {
config := client.server.AccountConfig().NickReservation
if !(config.Enabled && (config.Method == NickReservationWithTimeout || config.AllowCustomEnforcement)) {
return nil
}
return &NickTimer{
client: client,
timeout: config.RenameTimeout,
}
}
// Touch records a nick change and updates the timer as necessary
func (nt *NickTimer) Touch() {
if nt == nil {
return
}
cfnick, skeleton := nt.client.uniqueIdentifiers()
account := nt.client.Account()
accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton)
enforceTimeout := method == NickReservationWithTimeout
var shouldWarn bool
func() {
nt.Lock()
defer nt.Unlock()
if nt.stopped {
return
}
// the timer will not reset as long as the squatter is targeting the same account
accountChanged := accountForNick != nt.accountForNick
// change state
nt.nick = cfnick
nt.account = account
nt.accountForNick = accountForNick
delinquent := accountForNick != "" && accountForNick != account
if nt.timer != nil && (!enforceTimeout || !delinquent || accountChanged) {
nt.timer.Stop()
nt.timer = nil
}
if enforceTimeout && delinquent && accountChanged {
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
shouldWarn = true
}
}()
if shouldWarn {
nt.sendWarning()
}
}
// Stop stops counting time and cleans up the timer
func (nt *NickTimer) Stop() {
if nt == nil {
return
}
nt.Lock()
defer nt.Unlock()
if nt.timer != nil {
nt.timer.Stop()
nt.timer = nil
}
nt.stopped = true
}
func (nt *NickTimer) sendWarning() {
2019-02-18 04:59:13 +01:00
nt.client.Send(nil, "NickServ", "NOTICE", nt.client.Nick(), fmt.Sprintf(ircfmt.Unescape(nt.client.t(nsTimeoutNotice)), nt.timeout))
}
func (nt *NickTimer) processTimeout() {
baseMsg := "Nick is reserved and authentication timeout expired: %v"
nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout))
nt.client.server.RandomlyRename(nt.client)
}