From a2ac4eeef9451d5f405a19d56a242478afd2c309 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 9 Oct 2017 01:47:04 -0400 Subject: [PATCH 1/5] refactor limits and throttling --- irc/client.go | 2 -- irc/connection_limits.go | 46 +++++++++++++++++------- irc/connection_throttling.go | 69 ++++++++++++++++++++++++++---------- irc/server.go | 52 ++++++++------------------- 4 files changed, 100 insertions(+), 69 deletions(-) diff --git a/irc/client.go b/irc/client.go index 241c8a51..340c298c 100644 --- a/irc/client.go +++ b/irc/client.go @@ -542,9 +542,7 @@ func (client *Client) destroy() { ipaddr := client.IP() // this check shouldn't be required but eh if ipaddr != nil { - client.server.connectionLimitsMutex.Lock() client.server.connectionLimits.RemoveClient(ipaddr) - client.server.connectionLimitsMutex.Unlock() } // alert monitors diff --git a/irc/connection_limits.go b/irc/connection_limits.go index cc1ba34f..207c51f9 100644 --- a/irc/connection_limits.go +++ b/irc/connection_limits.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net" + "sync" ) var ( @@ -15,6 +16,8 @@ var ( // ConnectionLimits manages the automated client connection limits. type ConnectionLimits struct { + sync.Mutex + enabled bool ipv4Mask net.IPMask ipv6Mask net.IPMask @@ -45,6 +48,9 @@ func (cl *ConnectionLimits) maskAddr(addr net.IP) net.IP { // AddClient adds a client to our population if possible. If we can't, throws an error instead. // 'force' is used to add already-existing clients (i.e. ones that are already on the network). func (cl *ConnectionLimits) AddClient(addr net.IP, force bool) error { + cl.Lock() + defer cl.Unlock() + if !cl.enabled { return nil } @@ -75,6 +81,9 @@ func (cl *ConnectionLimits) AddClient(addr net.IP, force bool) error { // RemoveClient removes the given address from our population func (cl *ConnectionLimits) RemoveClient(addr net.IP) { + cl.Lock() + defer cl.Unlock() + if !cl.enabled { return } @@ -89,34 +98,47 @@ func (cl *ConnectionLimits) RemoveClient(addr net.IP) { } // NewConnectionLimits returns a new connection limit handler. -func NewConnectionLimits(config ConnectionLimitsConfig) (*ConnectionLimits, error) { +// The handler is functional, but disabled; it can be enabled via `ApplyConfig`. +func NewConnectionLimits() *ConnectionLimits { var cl ConnectionLimits - cl.enabled = config.Enabled + // initialize empty population; all other state is configurable cl.population = make(map[string]int) - cl.exemptedIPs = make(map[string]bool) - cl.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32) - cl.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128) - // subnetLimit is explicitly NOT capped at a minimum of one. - // this is so that CL config can be used to allow ONLY clients from exempted IPs/nets - cl.subnetLimit = config.IPsPerCidr + return &cl +} +// ApplyConfig atomically applies a config update to a connection limit handler +func (cl *ConnectionLimits) ApplyConfig(config ConnectionLimitsConfig) error { // assemble exempted nets + exemptedIPs := make(map[string]bool) + var exemptedNets []net.IPNet for _, cidr := range config.Exempted { ipaddr := net.ParseIP(cidr) _, netaddr, err := net.ParseCIDR(cidr) if ipaddr == nil && err != nil { - return nil, fmt.Errorf("Could not parse exempted IP/network [%s]", cidr) + return fmt.Errorf("Could not parse exempted IP/network [%s]", cidr) } if ipaddr != nil { - cl.exemptedIPs[ipaddr.String()] = true + exemptedIPs[ipaddr.String()] = true } else { - cl.exemptedNets = append(cl.exemptedNets, *netaddr) + exemptedNets = append(exemptedNets, *netaddr) } } - return &cl, nil + cl.Lock() + defer cl.Unlock() + + cl.enabled = config.Enabled + cl.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32) + cl.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128) + // subnetLimit is explicitly NOT capped at a minimum of one. + // this is so that CL config can be used to allow ONLY clients from exempted IPs/nets + cl.subnetLimit = config.IPsPerCidr + cl.exemptedIPs = exemptedIPs + cl.exemptedNets = exemptedNets + + return nil } diff --git a/irc/connection_throttling.go b/irc/connection_throttling.go index c5d7cb84..f2737481 100644 --- a/irc/connection_throttling.go +++ b/irc/connection_throttling.go @@ -6,6 +6,7 @@ package irc import ( "fmt" "net" + "sync" "time" ) @@ -17,6 +18,8 @@ type ThrottleDetails struct { // ConnectionThrottle manages automated client connection throttling. type ConnectionThrottle struct { + sync.RWMutex + enabled bool ipv4Mask net.IPMask ipv6Mask net.IPMask @@ -25,9 +28,8 @@ type ConnectionThrottle struct { population map[string]ThrottleDetails // used by the server to ban clients that go over this limit - BanDuration time.Duration - BanMessage string - BanMessageBytes []byte + banDuration time.Duration + banMessage string // exemptedIPs holds IPs that are exempt from limits exemptedIPs map[string]bool @@ -50,6 +52,9 @@ func (ct *ConnectionThrottle) maskAddr(addr net.IP) net.IP { // ResetFor removes any existing count for the given address. func (ct *ConnectionThrottle) ResetFor(addr net.IP) { + ct.Lock() + defer ct.Unlock() + if !ct.enabled { return } @@ -62,6 +67,9 @@ func (ct *ConnectionThrottle) ResetFor(addr net.IP) { // AddClient introduces a new client connection if possible. If we can't, throws an error instead. func (ct *ConnectionThrottle) AddClient(addr net.IP) error { + ct.Lock() + defer ct.Unlock() + if !ct.enabled { return nil } @@ -97,38 +105,63 @@ func (ct *ConnectionThrottle) AddClient(addr net.IP) error { return nil } +func (ct *ConnectionThrottle) BanDuration() time.Duration { + ct.RLock() + defer ct.RUnlock() + + return ct.banDuration +} + +func (ct *ConnectionThrottle) BanMessage() string { + ct.RLock() + defer ct.RUnlock() + + return ct.banMessage +} + // NewConnectionThrottle returns a new client connection throttler. -func NewConnectionThrottle(config ConnectionThrottleConfig) (*ConnectionThrottle, error) { +// The throttler is functional, but disabled; it can be enabled via `ApplyConfig`. +func NewConnectionThrottle() *ConnectionThrottle { var ct ConnectionThrottle - ct.enabled = config.Enabled + // initialize empty population; all other state is configurable ct.population = make(map[string]ThrottleDetails) - ct.exemptedIPs = make(map[string]bool) - ct.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32) - ct.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128) - ct.subnetLimit = config.ConnectionsPerCidr - - ct.duration = config.Duration - - ct.BanDuration = config.BanDuration - ct.BanMessage = config.BanMessage + return &ct +} +// ApplyConfig atomically applies a config update to a throttler +func (ct *ConnectionThrottle) ApplyConfig(config ConnectionThrottleConfig) error { // assemble exempted nets + exemptedIPs := make(map[string]bool) + var exemptedNets []net.IPNet for _, cidr := range config.Exempted { ipaddr := net.ParseIP(cidr) _, netaddr, err := net.ParseCIDR(cidr) if ipaddr == nil && err != nil { - return nil, fmt.Errorf("Could not parse exempted IP/network [%s]", cidr) + return fmt.Errorf("Could not parse exempted IP/network [%s]", cidr) } if ipaddr != nil { - ct.exemptedIPs[ipaddr.String()] = true + exemptedIPs[ipaddr.String()] = true } else { - ct.exemptedNets = append(ct.exemptedNets, *netaddr) + exemptedNets = append(exemptedNets, *netaddr) } } - return &ct, nil + ct.Lock() + defer ct.Unlock() + + ct.enabled = config.Enabled + ct.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32) + ct.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128) + ct.subnetLimit = config.ConnectionsPerCidr + ct.duration = config.Duration + ct.banDuration = config.BanDuration + ct.banMessage = config.BanMessage + ct.exemptedIPs = exemptedIPs + ct.exemptedNets = exemptedNets + + return nil } diff --git a/irc/server.go b/irc/server.go index e3f3c704..5062ce79 100644 --- a/irc/server.go +++ b/irc/server.go @@ -87,9 +87,7 @@ type Server struct { configFilename string configurableStateMutex sync.RWMutex // generic protection for server state modified by rehash() connectionLimits *ConnectionLimits - connectionLimitsMutex sync.Mutex // used when affecting the connection limiter, to make sure rehashing doesn't make things go out-of-whack connectionThrottle *ConnectionThrottle - connectionThrottleMutex sync.Mutex // used when affecting the connection limiter, to make sure rehashing doesn't make things go out-of-whack ctime time.Time defaultChannelModes Modes dlines *DLineManager @@ -149,6 +147,8 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) { channels: *NewChannelNameMap(), clients: NewClientLookupSet(), commands: make(chan Command), + connectionLimits: connection_limiting.NewConnectionLimits(), + connectionThrottle: connection_limiting.NewConnectionThrottle(), listeners: make(map[string]*ListenerWrapper), logger: logger, monitorManager: NewMonitorManager(), @@ -301,32 +301,29 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { } // check connection limits - server.connectionLimitsMutex.Lock() err := server.connectionLimits.AddClient(ipaddr, false) - server.connectionLimitsMutex.Unlock() if err != nil { // too many connections from one client, tell the client and close the connection return true, "Too many clients from your network" } // check connection throttle - server.connectionThrottleMutex.Lock() err = server.connectionThrottle.AddClient(ipaddr) - server.connectionThrottleMutex.Unlock() if err != nil { // too many connections too quickly from client, tell them and close the connection + duration := server.connectionThrottle.BanDuration() length := &IPRestrictTime{ - Duration: server.connectionThrottle.BanDuration, - Expires: time.Now().Add(server.connectionThrottle.BanDuration), + Duration: duration, + Expires: time.Now().Add(duration), } - server.dlines.AddIP(ipaddr, length, server.connectionThrottle.BanMessage, "Exceeded automated connection throttle") + server.dlines.AddIP(ipaddr, length, server.connectionThrottle.BanMessage(), "Exceeded automated connection throttle") // they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now, // and once their temporary DLINE is finished they can fill up the throttler again server.connectionThrottle.ResetFor(ipaddr) // this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us - return true, server.connectionThrottle.BanMessage + return true, server.connectionThrottle.BanMessage() } return false, "" @@ -1229,18 +1226,6 @@ func (server *Server) applyConfig(config *Config, initial bool) error { return fmt.Errorf("Server name isn't valid [%s]: %s", config.Server.Name, err.Error()) } - // confirm connectionLimits are fine - connectionLimits, err := NewConnectionLimits(config.Server.ConnectionLimits) - if err != nil { - return fmt.Errorf("Error rehashing config file connection-limits: %s", err.Error()) - } - - // confirm connectionThrottler is fine - connectionThrottle, err := NewConnectionThrottle(config.Server.ConnectionThrottle) - if err != nil { - return fmt.Errorf("Error rehashing config file connection-throttle: %s", err.Error()) - } - // confirm operator stuff all exists and is fine operclasses, err := config.OperatorClasses() if err != nil { @@ -1272,22 +1257,15 @@ func (server *Server) applyConfig(config *Config, initial bool) error { // apply new PROXY command restrictions server.proxyAllowedFrom = config.Server.ProxyAllowedFrom - // apply new connectionlimits - server.connectionLimitsMutex.Lock() - server.connectionLimits = connectionLimits - server.connectionThrottleMutex.Lock() - server.connectionThrottle = connectionThrottle - - server.clients.ByNickMutex.RLock() - for _, client := range server.clients.ByNick { - ipaddr := client.IP() - if ipaddr != nil { - server.connectionLimits.AddClient(ipaddr, true) - } + err = server.connectionLimits.ApplyConfig(config.Server.ConnectionLimits) + if err != nil { + return err + } + + err = server.connectionThrottle.ApplyConfig(config.Server.ConnectionThrottle) + if err != nil { + return err } - server.clients.ByNickMutex.RUnlock() - server.connectionThrottleMutex.Unlock() - server.connectionLimitsMutex.Unlock() // setup new and removed caps addedCaps := caps.NewSet() From ac9ac5ef19d40456c3b3d01cf6997249f98a8d6b Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 9 Oct 2017 01:58:57 -0400 Subject: [PATCH 2/5] create separate irc/connection_limiting package --- irc/config.go | 28 ++----------------- .../limits.go} | 11 +++++++- .../throttle.go} | 16 ++++++++++- irc/server.go | 5 ++-- 4 files changed, 31 insertions(+), 29 deletions(-) rename irc/{connection_limits.go => connection_limiting/limits.go} (92%) rename irc/{connection_throttling.go => connection_limiting/throttle.go} (85%) diff --git a/irc/config.go b/irc/config.go index be5b3d37..92e98ab0 100644 --- a/irc/config.go +++ b/irc/config.go @@ -15,6 +15,7 @@ import ( "time" "code.cloudfoundry.org/bytefmt" + "github.com/oragono/oragono/irc/connection_limiting" "github.com/oragono/oragono/irc/custime" "github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/passwd" @@ -108,29 +109,6 @@ func (conf *OperConfig) PasswordBytes() []byte { return bytes } -// ConnectionLimitsConfig controls the automated connection limits. -type ConnectionLimitsConfig struct { - Enabled bool - CidrLenIPv4 int `yaml:"cidr-len-ipv4"` - CidrLenIPv6 int `yaml:"cidr-len-ipv6"` - IPsPerCidr int `yaml:"ips-per-subnet"` - Exempted []string -} - -// ConnectionThrottleConfig controls the automated connection throttling. -type ConnectionThrottleConfig struct { - Enabled bool - CidrLenIPv4 int `yaml:"cidr-len-ipv4"` - CidrLenIPv6 int `yaml:"cidr-len-ipv6"` - ConnectionsPerCidr int `yaml:"max-connections"` - DurationString string `yaml:"duration"` - Duration time.Duration `yaml:"duration-time"` - BanDurationString string `yaml:"ban-duration"` - BanDuration time.Duration - BanMessage string `yaml:"ban-message"` - Exempted []string -} - // LineLenConfig controls line lengths. type LineLenConfig struct { Tags int @@ -184,8 +162,8 @@ type Config struct { ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` MaxSendQString string `yaml:"max-sendq"` MaxSendQBytes uint64 - ConnectionLimits ConnectionLimitsConfig `yaml:"connection-limits"` - ConnectionThrottle ConnectionThrottleConfig `yaml:"connection-throttling"` + ConnectionLimits connection_limiting.ConnectionLimitsConfig `yaml:"connection-limits"` + ConnectionThrottle connection_limiting.ConnectionThrottleConfig `yaml:"connection-throttling"` } Datastore struct { diff --git a/irc/connection_limits.go b/irc/connection_limiting/limits.go similarity index 92% rename from irc/connection_limits.go rename to irc/connection_limiting/limits.go index 207c51f9..3402a452 100644 --- a/irc/connection_limits.go +++ b/irc/connection_limiting/limits.go @@ -1,7 +1,7 @@ // Copyright (c) 2016-2017 Daniel Oaks // released under the MIT license -package irc +package connection_limiting import ( "errors" @@ -10,6 +10,15 @@ import ( "sync" ) +// ConnectionLimitsConfig controls the automated connection limits. +type ConnectionLimitsConfig struct { + Enabled bool + CidrLenIPv4 int `yaml:"cidr-len-ipv4"` + CidrLenIPv6 int `yaml:"cidr-len-ipv6"` + IPsPerCidr int `yaml:"ips-per-subnet"` + Exempted []string +} + var ( errTooManyClients = errors.New("Too many clients in subnet") ) diff --git a/irc/connection_throttling.go b/irc/connection_limiting/throttle.go similarity index 85% rename from irc/connection_throttling.go rename to irc/connection_limiting/throttle.go index f2737481..91925217 100644 --- a/irc/connection_throttling.go +++ b/irc/connection_limiting/throttle.go @@ -1,7 +1,7 @@ // Copyright (c) 2016-2017 Daniel Oaks // released under the MIT license -package irc +package connection_limiting import ( "fmt" @@ -10,6 +10,20 @@ import ( "time" ) +// ConnectionThrottleConfig controls the automated connection throttling. +type ConnectionThrottleConfig struct { + Enabled bool + CidrLenIPv4 int `yaml:"cidr-len-ipv4"` + CidrLenIPv6 int `yaml:"cidr-len-ipv6"` + ConnectionsPerCidr int `yaml:"max-connections"` + DurationString string `yaml:"duration"` + Duration time.Duration `yaml:"duration-time"` + BanDurationString string `yaml:"ban-duration"` + BanDuration time.Duration + BanMessage string `yaml:"ban-message"` + Exempted []string +} + // ThrottleDetails holds the connection-throttling details for a subnet/IP. type ThrottleDetails struct { Start time.Time diff --git a/irc/server.go b/irc/server.go index 5062ce79..6b22de34 100644 --- a/irc/server.go +++ b/irc/server.go @@ -24,6 +24,7 @@ import ( "github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircmsg" "github.com/oragono/oragono/irc/caps" + "github.com/oragono/oragono/irc/connection_limiting" "github.com/oragono/oragono/irc/isupport" "github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/passwd" @@ -86,8 +87,8 @@ type Server struct { commands chan Command configFilename string configurableStateMutex sync.RWMutex // generic protection for server state modified by rehash() - connectionLimits *ConnectionLimits - connectionThrottle *ConnectionThrottle + connectionLimits *connection_limiting.ConnectionLimits + connectionThrottle *connection_limiting.ConnectionThrottle ctime time.Time defaultChannelModes Modes dlines *DLineManager From 58faad90dd5ff33babc95e3a22916449f77c4b2c Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 9 Oct 2017 13:17:49 -0400 Subject: [PATCH 3/5] add loglines for IP limits --- irc/server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/irc/server.go b/irc/server.go index 6b22de34..bf1fe26c 100644 --- a/irc/server.go +++ b/irc/server.go @@ -298,6 +298,7 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { // check DLINEs isBanned, info := server.dlines.CheckIP(ipaddr) if isBanned { + server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected by d-line", ipaddr)) return true, info.BanMessage("You are banned from this server (%s)") } @@ -305,6 +306,7 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { err := server.connectionLimits.AddClient(ipaddr, false) if err != nil { // too many connections from one client, tell the client and close the connection + server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr)) return true, "Too many clients from your network" } @@ -324,6 +326,9 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { server.connectionThrottle.ResetFor(ipaddr) // this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us + server.logger.Info( + "localconnect-ip", + fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration)) return true, server.connectionThrottle.BanMessage() } From 0e5eec303756183863573489001d1759803583ed Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 9 Oct 2017 13:48:58 -0400 Subject: [PATCH 4/5] fix a config comment --- oragono.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oragono.yaml b/oragono.yaml index 55f566b6..3f68dad7 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -71,7 +71,7 @@ server: # maximum number of connections per subnet connection-limits: - # whether to throttle limits or not + # whether to enforce connection limits or not enabled: true # how wide the cidr should be for IPv4 From d66470f1c47c232fdb08a4976aa32029aa831f4a Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 9 Oct 2017 17:37:13 -0400 Subject: [PATCH 5/5] review fix: rename various packages and objects --- irc/client.go | 2 +- irc/config.go | 34 ++++++------ .../limiter.go} | 24 ++++----- .../throttler.go} | 28 +++++----- irc/server.go | 52 +++++++++---------- 5 files changed, 70 insertions(+), 70 deletions(-) rename irc/{connection_limiting/limits.go => connection_limits/limiter.go} (83%) rename irc/{connection_limiting/throttle.go => connection_limits/throttler.go} (82%) diff --git a/irc/client.go b/irc/client.go index 340c298c..5c1298bd 100644 --- a/irc/client.go +++ b/irc/client.go @@ -542,7 +542,7 @@ func (client *Client) destroy() { ipaddr := client.IP() // this check shouldn't be required but eh if ipaddr != nil { - client.server.connectionLimits.RemoveClient(ipaddr) + client.server.connectionLimiter.RemoveClient(ipaddr) } // alert monitors diff --git a/irc/config.go b/irc/config.go index 92e98ab0..6eaa2be4 100644 --- a/irc/config.go +++ b/irc/config.go @@ -15,7 +15,7 @@ import ( "time" "code.cloudfoundry.org/bytefmt" - "github.com/oragono/oragono/irc/connection_limiting" + "github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/custime" "github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/passwd" @@ -151,19 +151,19 @@ type Config struct { Server struct { PassConfig - Password string - Name string - Listen []string - TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"` - STS STSConfig - CheckIdent bool `yaml:"check-ident"` - MOTD string - MOTDFormatting bool `yaml:"motd-formatting"` - ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` - MaxSendQString string `yaml:"max-sendq"` - MaxSendQBytes uint64 - ConnectionLimits connection_limiting.ConnectionLimitsConfig `yaml:"connection-limits"` - ConnectionThrottle connection_limiting.ConnectionThrottleConfig `yaml:"connection-throttling"` + Password string + Name string + Listen []string + TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"` + STS STSConfig + CheckIdent bool `yaml:"check-ident"` + MOTD string + MOTDFormatting bool `yaml:"motd-formatting"` + ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` + MaxSendQString string `yaml:"max-sendq"` + MaxSendQBytes uint64 + ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` + ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` } Datastore struct { @@ -383,12 +383,12 @@ func LoadConfig(filename string) (config *Config, err error) { return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port) } } - if config.Server.ConnectionThrottle.Enabled { - config.Server.ConnectionThrottle.Duration, err = time.ParseDuration(config.Server.ConnectionThrottle.DurationString) + if config.Server.ConnectionThrottler.Enabled { + config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString) if err != nil { return nil, fmt.Errorf("Could not parse connection-throttle duration: %s", err.Error()) } - config.Server.ConnectionThrottle.BanDuration, err = time.ParseDuration(config.Server.ConnectionThrottle.BanDurationString) + config.Server.ConnectionThrottler.BanDuration, err = time.ParseDuration(config.Server.ConnectionThrottler.BanDurationString) if err != nil { return nil, fmt.Errorf("Could not parse connection-throttle ban-duration: %s", err.Error()) } diff --git a/irc/connection_limiting/limits.go b/irc/connection_limits/limiter.go similarity index 83% rename from irc/connection_limiting/limits.go rename to irc/connection_limits/limiter.go index 3402a452..11d1d666 100644 --- a/irc/connection_limiting/limits.go +++ b/irc/connection_limits/limiter.go @@ -1,7 +1,7 @@ // Copyright (c) 2016-2017 Daniel Oaks // released under the MIT license -package connection_limiting +package connection_limits import ( "errors" @@ -10,8 +10,8 @@ import ( "sync" ) -// ConnectionLimitsConfig controls the automated connection limits. -type ConnectionLimitsConfig struct { +// LimiterConfig controls the automated connection limits. +type LimiterConfig struct { Enabled bool CidrLenIPv4 int `yaml:"cidr-len-ipv4"` CidrLenIPv6 int `yaml:"cidr-len-ipv6"` @@ -23,8 +23,8 @@ var ( errTooManyClients = errors.New("Too many clients in subnet") ) -// ConnectionLimits manages the automated client connection limits. -type ConnectionLimits struct { +// Limiter manages the automated client connection limits. +type Limiter struct { sync.Mutex enabled bool @@ -42,7 +42,7 @@ type ConnectionLimits struct { } // maskAddr masks the given IPv4/6 address with our cidr limit masks. -func (cl *ConnectionLimits) maskAddr(addr net.IP) net.IP { +func (cl *Limiter) maskAddr(addr net.IP) net.IP { if addr.To4() == nil { // IPv6 addr addr = addr.Mask(cl.ipv6Mask) @@ -56,7 +56,7 @@ func (cl *ConnectionLimits) maskAddr(addr net.IP) net.IP { // AddClient adds a client to our population if possible. If we can't, throws an error instead. // 'force' is used to add already-existing clients (i.e. ones that are already on the network). -func (cl *ConnectionLimits) AddClient(addr net.IP, force bool) error { +func (cl *Limiter) AddClient(addr net.IP, force bool) error { cl.Lock() defer cl.Unlock() @@ -89,7 +89,7 @@ func (cl *ConnectionLimits) AddClient(addr net.IP, force bool) error { } // RemoveClient removes the given address from our population -func (cl *ConnectionLimits) RemoveClient(addr net.IP) { +func (cl *Limiter) RemoveClient(addr net.IP) { cl.Lock() defer cl.Unlock() @@ -106,10 +106,10 @@ func (cl *ConnectionLimits) RemoveClient(addr net.IP) { } } -// NewConnectionLimits returns a new connection limit handler. +// NewLimiter returns a new connection limit handler. // The handler is functional, but disabled; it can be enabled via `ApplyConfig`. -func NewConnectionLimits() *ConnectionLimits { - var cl ConnectionLimits +func NewLimiter() *Limiter { + var cl Limiter // initialize empty population; all other state is configurable cl.population = make(map[string]int) @@ -118,7 +118,7 @@ func NewConnectionLimits() *ConnectionLimits { } // ApplyConfig atomically applies a config update to a connection limit handler -func (cl *ConnectionLimits) ApplyConfig(config ConnectionLimitsConfig) error { +func (cl *Limiter) ApplyConfig(config LimiterConfig) error { // assemble exempted nets exemptedIPs := make(map[string]bool) var exemptedNets []net.IPNet diff --git a/irc/connection_limiting/throttle.go b/irc/connection_limits/throttler.go similarity index 82% rename from irc/connection_limiting/throttle.go rename to irc/connection_limits/throttler.go index 91925217..aa29d241 100644 --- a/irc/connection_limiting/throttle.go +++ b/irc/connection_limits/throttler.go @@ -1,7 +1,7 @@ // Copyright (c) 2016-2017 Daniel Oaks // released under the MIT license -package connection_limiting +package connection_limits import ( "fmt" @@ -10,8 +10,8 @@ import ( "time" ) -// ConnectionThrottleConfig controls the automated connection throttling. -type ConnectionThrottleConfig struct { +// ThrottlerConfig controls the automated connection throttling. +type ThrottlerConfig struct { Enabled bool CidrLenIPv4 int `yaml:"cidr-len-ipv4"` CidrLenIPv6 int `yaml:"cidr-len-ipv6"` @@ -30,8 +30,8 @@ type ThrottleDetails struct { ClientCount int } -// ConnectionThrottle manages automated client connection throttling. -type ConnectionThrottle struct { +// Throttler manages automated client connection throttling. +type Throttler struct { sync.RWMutex enabled bool @@ -52,7 +52,7 @@ type ConnectionThrottle struct { } // maskAddr masks the given IPv4/6 address with our cidr limit masks. -func (ct *ConnectionThrottle) maskAddr(addr net.IP) net.IP { +func (ct *Throttler) maskAddr(addr net.IP) net.IP { if addr.To4() == nil { // IPv6 addr addr = addr.Mask(ct.ipv6Mask) @@ -65,7 +65,7 @@ func (ct *ConnectionThrottle) maskAddr(addr net.IP) net.IP { } // ResetFor removes any existing count for the given address. -func (ct *ConnectionThrottle) ResetFor(addr net.IP) { +func (ct *Throttler) ResetFor(addr net.IP) { ct.Lock() defer ct.Unlock() @@ -80,7 +80,7 @@ func (ct *ConnectionThrottle) ResetFor(addr net.IP) { } // AddClient introduces a new client connection if possible. If we can't, throws an error instead. -func (ct *ConnectionThrottle) AddClient(addr net.IP) error { +func (ct *Throttler) AddClient(addr net.IP) error { ct.Lock() defer ct.Unlock() @@ -119,24 +119,24 @@ func (ct *ConnectionThrottle) AddClient(addr net.IP) error { return nil } -func (ct *ConnectionThrottle) BanDuration() time.Duration { +func (ct *Throttler) BanDuration() time.Duration { ct.RLock() defer ct.RUnlock() return ct.banDuration } -func (ct *ConnectionThrottle) BanMessage() string { +func (ct *Throttler) BanMessage() string { ct.RLock() defer ct.RUnlock() return ct.banMessage } -// NewConnectionThrottle returns a new client connection throttler. +// NewThrottler returns a new client connection throttler. // The throttler is functional, but disabled; it can be enabled via `ApplyConfig`. -func NewConnectionThrottle() *ConnectionThrottle { - var ct ConnectionThrottle +func NewThrottler() *Throttler { + var ct Throttler // initialize empty population; all other state is configurable ct.population = make(map[string]ThrottleDetails) @@ -145,7 +145,7 @@ func NewConnectionThrottle() *ConnectionThrottle { } // ApplyConfig atomically applies a config update to a throttler -func (ct *ConnectionThrottle) ApplyConfig(config ConnectionThrottleConfig) error { +func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error { // assemble exempted nets exemptedIPs := make(map[string]bool) var exemptedNets []net.IPNet diff --git a/irc/server.go b/irc/server.go index bf1fe26c..c419b190 100644 --- a/irc/server.go +++ b/irc/server.go @@ -24,7 +24,7 @@ import ( "github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircmsg" "github.com/oragono/oragono/irc/caps" - "github.com/oragono/oragono/irc/connection_limiting" + "github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/isupport" "github.com/oragono/oragono/irc/logger" "github.com/oragono/oragono/irc/passwd" @@ -87,8 +87,8 @@ type Server struct { commands chan Command configFilename string configurableStateMutex sync.RWMutex // generic protection for server state modified by rehash() - connectionLimits *connection_limiting.ConnectionLimits - connectionThrottle *connection_limiting.ConnectionThrottle + connectionLimiter *connection_limits.Limiter + connectionThrottler *connection_limits.Throttler ctime time.Time defaultChannelModes Modes dlines *DLineManager @@ -144,21 +144,21 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) { // initialize data structures server := &Server{ - accounts: make(map[string]*ClientAccount), - channels: *NewChannelNameMap(), - clients: NewClientLookupSet(), - commands: make(chan Command), - connectionLimits: connection_limiting.NewConnectionLimits(), - connectionThrottle: connection_limiting.NewConnectionThrottle(), - listeners: make(map[string]*ListenerWrapper), - logger: logger, - monitorManager: NewMonitorManager(), - newConns: make(chan clientConn), - registeredChannels: make(map[string]*RegisteredChannel), - rehashSignal: make(chan os.Signal, 1), - signals: make(chan os.Signal, len(ServerExitSignals)), - snomasks: NewSnoManager(), - whoWas: NewWhoWasList(config.Limits.WhowasEntries), + accounts: make(map[string]*ClientAccount), + channels: *NewChannelNameMap(), + clients: NewClientLookupSet(), + commands: make(chan Command), + connectionLimiter: connection_limits.NewLimiter(), + connectionThrottler: connection_limits.NewThrottler(), + listeners: make(map[string]*ListenerWrapper), + logger: logger, + monitorManager: NewMonitorManager(), + newConns: make(chan clientConn), + registeredChannels: make(map[string]*RegisteredChannel), + rehashSignal: make(chan os.Signal, 1), + signals: make(chan os.Signal, len(ServerExitSignals)), + snomasks: NewSnoManager(), + whoWas: NewWhoWasList(config.Limits.WhowasEntries), } if err := server.applyConfig(config, true); err != nil { @@ -303,7 +303,7 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { } // check connection limits - err := server.connectionLimits.AddClient(ipaddr, false) + err := server.connectionLimiter.AddClient(ipaddr, false) if err != nil { // too many connections from one client, tell the client and close the connection server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr)) @@ -311,25 +311,25 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { } // check connection throttle - err = server.connectionThrottle.AddClient(ipaddr) + err = server.connectionThrottler.AddClient(ipaddr) if err != nil { // too many connections too quickly from client, tell them and close the connection - duration := server.connectionThrottle.BanDuration() + duration := server.connectionThrottler.BanDuration() length := &IPRestrictTime{ Duration: duration, Expires: time.Now().Add(duration), } - server.dlines.AddIP(ipaddr, length, server.connectionThrottle.BanMessage(), "Exceeded automated connection throttle") + server.dlines.AddIP(ipaddr, length, server.connectionThrottler.BanMessage(), "Exceeded automated connection throttle") // they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now, // and once their temporary DLINE is finished they can fill up the throttler again - server.connectionThrottle.ResetFor(ipaddr) + server.connectionThrottler.ResetFor(ipaddr) // this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us server.logger.Info( "localconnect-ip", fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration)) - return true, server.connectionThrottle.BanMessage() + return true, server.connectionThrottler.BanMessage() } return false, "" @@ -1263,12 +1263,12 @@ func (server *Server) applyConfig(config *Config, initial bool) error { // apply new PROXY command restrictions server.proxyAllowedFrom = config.Server.ProxyAllowedFrom - err = server.connectionLimits.ApplyConfig(config.Server.ConnectionLimits) + err = server.connectionLimiter.ApplyConfig(config.Server.ConnectionLimiter) if err != nil { return err } - err = server.connectionThrottle.ApplyConfig(config.Server.ConnectionThrottle) + err = server.connectionThrottler.ApplyConfig(config.Server.ConnectionThrottler) if err != nil { return err }