remove indirections in Fakelag and NickTimer

This commit is contained in:
Shivaram Lingamneni 2019-03-08 03:12:21 -05:00
parent acd9eeeb15
commit 2e88f82e41
6 changed files with 79 additions and 76 deletions

View File

@ -57,7 +57,7 @@ type Client struct {
channels ChannelSet channels ChannelSet
ctime time.Time ctime time.Time
exitedSnomaskSent bool exitedSnomaskSent bool
fakelag *Fakelag fakelag Fakelag
flags *modes.ModeSet flags *modes.ModeSet
hasQuit bool hasQuit bool
hops int hops int
@ -75,7 +75,7 @@ type Client struct {
nickCasefolded string nickCasefolded string
nickMaskCasefolded string nickMaskCasefolded string
nickMaskString string // cache for nickmask string since it's used with lots of replies nickMaskString string // cache for nickmask string since it's used with lots of replies
nickTimer *NickTimer nickTimer NickTimer
oper *Oper oper *Oper
preregNick string preregNick string
proxiedIP net.IP // actual remote IP if using the PROXY protocol proxiedIP net.IP // actual remote IP if using the PROXY protocol
@ -217,23 +217,9 @@ func (client *Client) isAuthorized(config *Config) bool {
} }
func (client *Client) resetFakelag() { func (client *Client) resetFakelag() {
fakelag := func() *Fakelag { var flc FakelagConfig = client.server.Config().Fakelag
if client.HasRoleCapabs("nofakelag") { flc.Enabled = flc.Enabled && !client.HasRoleCapabs("nofakelag")
return nil client.fakelag.Initialize(flc)
}
flc := client.server.FakelagConfig()
if !flc.Enabled {
return nil
}
return NewFakelag(flc.Window, flc.BurstLimit, flc.MessagesPerWindow, flc.Cooldown)
}()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.fakelag = fakelag
} }
// IP returns the IP address of this client. // IP returns the IP address of this client.
@ -309,7 +295,7 @@ func (client *Client) run() {
client.idletimer = NewIdleTimer(client) client.idletimer = NewIdleTimer(client)
client.idletimer.Start() client.idletimer.Start()
client.nickTimer = NewNickTimer(client) client.nickTimer.Initialize(client)
client.resetFakelag() client.resetFakelag()

View File

@ -24,10 +24,7 @@ const (
// this is intentionally not threadsafe, because it should only be touched // this is intentionally not threadsafe, because it should only be touched
// from the loop that accepts the client's input and runs commands // from the loop that accepts the client's input and runs commands
type Fakelag struct { type Fakelag struct {
window time.Duration config FakelagConfig
burstLimit uint
throttleMessagesPerWindow uint
cooldown time.Duration
nowFunc func() time.Time nowFunc func() time.Time
sleepFunc func(time.Duration) sleepFunc func(time.Duration)
@ -36,21 +33,16 @@ type Fakelag struct {
lastTouch time.Time lastTouch time.Time
} }
func NewFakelag(window time.Duration, burstLimit uint, throttleMessagesPerWindow uint, cooldown time.Duration) *Fakelag { func (fl *Fakelag) Initialize(config FakelagConfig) {
return &Fakelag{ fl.config = config
window: window, fl.nowFunc = time.Now
burstLimit: burstLimit, fl.sleepFunc = time.Sleep
throttleMessagesPerWindow: throttleMessagesPerWindow, fl.state = FakelagBursting
cooldown: cooldown,
nowFunc: time.Now,
sleepFunc: time.Sleep,
state: FakelagBursting,
}
} }
// register a new command, sleep if necessary to delay it // register a new command, sleep if necessary to delay it
func (fl *Fakelag) Touch() { func (fl *Fakelag) Touch() {
if fl == nil { if !fl.config.Enabled {
return return
} }
@ -61,12 +53,12 @@ func (fl *Fakelag) Touch() {
if fl.state == FakelagBursting { if fl.state == FakelagBursting {
// determine if the previous burst is over // determine if the previous burst is over
if elapsed > fl.cooldown { if elapsed > fl.config.Cooldown {
fl.burstCount = 0 fl.burstCount = 0
} }
fl.burstCount++ fl.burstCount++
if fl.burstCount > fl.burstLimit { if fl.burstCount > fl.config.BurstLimit {
// reset burst window for next time // reset burst window for next time
fl.burstCount = 0 fl.burstCount = 0
// transition to throttling // transition to throttling
@ -78,13 +70,13 @@ func (fl *Fakelag) Touch() {
} }
if fl.state == FakelagThrottled { if fl.state == FakelagThrottled {
if elapsed > fl.cooldown { if elapsed > fl.config.Cooldown {
// let them burst again // let them burst again
fl.state = FakelagBursting fl.state = FakelagBursting
return return
} }
// space them out by at least window/messagesperwindow // space them out by at least window/messagesperwindow
sleepDuration := time.Duration((int64(fl.window) / int64(fl.throttleMessagesPerWindow)) - int64(elapsed)) sleepDuration := time.Duration((int64(fl.config.Window) / int64(fl.config.MessagesPerWindow)) - int64(elapsed))
if sleepDuration > 0 { if sleepDuration > 0 {
fl.sleepFunc(sleepDuration) fl.sleepFunc(sleepDuration)
// the touch time should take into account the time we slept // the touch time should take into account the time we slept

View File

@ -40,13 +40,20 @@ func (mt *mockTime) lastSleep() (slept bool, duration time.Duration) {
} }
func newFakelagForTesting(window time.Duration, burstLimit uint, throttleMessagesPerWindow uint, cooldown time.Duration) (*Fakelag, *mockTime) { func newFakelagForTesting(window time.Duration, burstLimit uint, throttleMessagesPerWindow uint, cooldown time.Duration) (*Fakelag, *mockTime) {
fl := NewFakelag(window, burstLimit, throttleMessagesPerWindow, cooldown) fl := Fakelag{}
fl.config = FakelagConfig{
Enabled: true,
Window: window,
BurstLimit: burstLimit,
MessagesPerWindow: throttleMessagesPerWindow,
Cooldown: cooldown,
}
mt := new(mockTime) mt := new(mockTime)
mt.now, _ = time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006") mt.now, _ = time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006")
mt.lastCheckedSleep = -1 mt.lastCheckedSleep = -1
fl.nowFunc = mt.Now fl.nowFunc = mt.Now
fl.sleepFunc = mt.Sleep fl.sleepFunc = mt.Sleep
return fl, mt return &fl, mt
} }
func TestFakelag(t *testing.T) { func TestFakelag(t *testing.T) {

View File

@ -46,10 +46,6 @@ func (server *Server) AccountConfig() *AccountConfig {
return &server.Config().Accounts return &server.Config().Accounts
} }
func (server *Server) FakelagConfig() *FakelagConfig {
return &server.Config().Fakelag
}
func (server *Server) GetOperator(name string) (oper *Oper) { func (server *Server) GetOperator(name string) (oper *Oper) {
name, err := CasefoldName(name) name, err := CasefoldName(name)
if err != nil { if err != nil {

View File

@ -6,6 +6,7 @@ package irc
import ( import (
"fmt" "fmt"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircfmt"
@ -180,33 +181,50 @@ type NickTimer struct {
sync.Mutex // tier 1 sync.Mutex // tier 1
// immutable after construction // immutable after construction
timeout time.Duration
client *Client client *Client
// mutable // mutable
stopped bool
nick string nick string
accountForNick string accountForNick string
account string account string
timeout time.Duration
timer *time.Timer timer *time.Timer
enabled uint32
} }
// NewNickTimer sets up a new nick timer (returning nil if timeout enforcement is not enabled) // Initialize sets up a NickTimer, based on server config settings.
func NewNickTimer(client *Client) *NickTimer { func (nt *NickTimer) Initialize(client *Client) {
config := client.server.AccountConfig().NickReservation if nt.client == nil {
if !(config.Enabled && (config.Method == NickReservationWithTimeout || config.AllowCustomEnforcement)) { nt.client = client // placate the race detector
return nil
} }
return &NickTimer{ config := &client.server.Config().Accounts.NickReservation
client: client, enabled := config.Enabled && (config.Method == NickReservationWithTimeout || config.AllowCustomEnforcement)
timeout: config.RenameTimeout,
nt.Lock()
defer nt.Unlock()
nt.timeout = config.RenameTimeout
if enabled {
atomic.StoreUint32(&nt.enabled, 1)
} else {
nt.stopInternal()
} }
} }
func (nt *NickTimer) Enabled() bool {
return atomic.LoadUint32(&nt.enabled) == 1
}
func (nt *NickTimer) Timeout() (timeout time.Duration) {
nt.Lock()
timeout = nt.timeout
nt.Unlock()
return
}
// Touch records a nick change and updates the timer as necessary // Touch records a nick change and updates the timer as necessary
func (nt *NickTimer) Touch() { func (nt *NickTimer) Touch() {
if nt == nil { if !nt.Enabled() {
return return
} }
@ -215,16 +233,12 @@ func (nt *NickTimer) Touch() {
accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton) accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton)
enforceTimeout := method == NickReservationWithTimeout enforceTimeout := method == NickReservationWithTimeout
var shouldWarn bool var shouldWarn, shouldRename bool
func() { func() {
nt.Lock() nt.Lock()
defer nt.Unlock() defer nt.Unlock()
if nt.stopped {
return
}
// the timer will not reset as long as the squatter is targeting the same account // the timer will not reset as long as the squatter is targeting the same account
accountChanged := accountForNick != nt.accountForNick accountChanged := accountForNick != nt.accountForNick
// change state // change state
@ -237,38 +251,39 @@ func (nt *NickTimer) Touch() {
nt.timer.Stop() nt.timer.Stop()
nt.timer = nil nt.timer = nil
} }
if enforceTimeout && delinquent && accountChanged { if enforceTimeout && delinquent && (accountChanged || nt.timer == nil) {
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout) nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
shouldWarn = true shouldWarn = true
} else if method == NickReservationStrict && delinquent {
shouldRename = true // this can happen if reservation was enabled by rehash
} }
}() }()
if shouldWarn { if shouldWarn {
nt.sendWarning() nt.client.Send(nil, "NickServ", "NOTICE", nt.client.Nick(), fmt.Sprintf(ircfmt.Unescape(nt.client.t(nsTimeoutNotice)), nt.Timeout()))
} else if shouldRename {
nt.client.Notice(nt.client.t("Nickname is reserved by a different account"))
nt.client.server.RandomlyRename(nt.client)
} }
} }
// Stop stops counting time and cleans up the timer // Stop stops counting time and cleans up the timer
func (nt *NickTimer) Stop() { func (nt *NickTimer) Stop() {
if nt == nil {
return
}
nt.Lock() nt.Lock()
defer nt.Unlock() defer nt.Unlock()
nt.stopInternal()
}
func (nt *NickTimer) stopInternal() {
if nt.timer != nil { if nt.timer != nil {
nt.timer.Stop() nt.timer.Stop()
nt.timer = nil nt.timer = nil
} }
nt.stopped = true atomic.StoreUint32(&nt.enabled, 0)
}
func (nt *NickTimer) sendWarning() {
nt.client.Send(nil, "NickServ", "NOTICE", nt.client.Nick(), fmt.Sprintf(ircfmt.Unescape(nt.client.t(nsTimeoutNotice)), nt.timeout))
} }
func (nt *NickTimer) processTimeout() { func (nt *NickTimer) processTimeout() {
baseMsg := "Nick is reserved and authentication timeout expired: %v" baseMsg := "Nick is reserved and authentication timeout expired: %v"
nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout)) nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.Timeout()))
nt.client.server.RandomlyRename(nt.client) nt.client.server.RandomlyRename(nt.client)
} }

View File

@ -826,6 +826,13 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
if sendRawOutputNotice { if sendRawOutputNotice {
sClient.Notice(sClient.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) sClient.Notice(sClient.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
} }
if !oldConfig.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.Enabled {
sClient.nickTimer.Initialize(sClient)
sClient.nickTimer.Touch()
} else if oldConfig.Accounts.NickReservation.Enabled && !config.Accounts.NickReservation.Enabled {
sClient.nickTimer.Stop()
}
} }
} }