3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-23 04:19:25 +01:00

Merge pull request #669 from slingamn/connectionlimits.4

fix #646
This commit is contained in:
Shivaram Lingamneni 2019-11-23 22:09:30 -05:00 committed by GitHub
commit 7f80f1b5ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 332 additions and 319 deletions

View File

@ -308,12 +308,11 @@ type Config struct {
forceTrailing bool forceTrailing bool
SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"` SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"`
} }
isupport isupport.List isupport isupport.List
ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` IPLimits connection_limits.LimiterConfig `yaml:"ip-limits"`
ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"`
Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` supportedCaps *caps.Set
supportedCaps *caps.Set capValues caps.Values
capValues caps.Values
} }
Languages struct { Languages struct {
@ -633,16 +632,6 @@ func LoadConfig(filename string) (config *Config, err error) {
// set this even if STS is disabled // set this even if STS is disabled
config.Server.capValues[caps.STS] = config.Server.STS.Value() config.Server.capValues[caps.STS] = config.Server.STS.Value()
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.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())
}
}
// process webirc blocks // process webirc blocks
var newWebIRC []webircConfig var newWebIRC []webircConfig
for _, webirc := range config.Server.WebIRC { for _, webirc := range config.Server.WebIRC {

View File

@ -8,69 +8,161 @@ import (
"fmt" "fmt"
"net" "net"
"sync" "sync"
"time"
"github.com/oragono/oragono/irc/utils" "github.com/oragono/oragono/irc/utils"
) )
// LimiterConfig controls the automated connection limits. var (
type LimiterConfig struct { ErrLimitExceeded = errors.New("too many concurrent connections")
Enabled bool ErrThrottleExceeded = errors.New("too many recent connection attempts")
CidrLenIPv4 int `yaml:"cidr-len-ipv4"` )
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
ConnsPerSubnet int `yaml:"connections-per-subnet"` type CustomLimitConfig struct {
IPsPerSubnet int `yaml:"ips-per-subnet"` // legacy name for ConnsPerSubnet MaxConcurrent int `yaml:"max-concurrent-connections"`
Exempted []string MaxPerWindow int `yaml:"max-connections-per-window"`
} }
var ( // tuples the key-value pair of a CIDR and its custom limit/throttle values
errTooManyClients = errors.New("Too many clients in subnet") type customLimit struct {
) CustomLimitConfig
ipNet net.IPNet
}
// LimiterConfig controls the automated connection limits.
// rawLimiterConfig contains all the YAML-visible fields;
// LimiterConfig contains additional denormalized private fields
type rawLimiterConfig struct {
Count bool
MaxConcurrent int `yaml:"max-concurrent-connections"`
Throttle bool
Window time.Duration
MaxPerWindow int `yaml:"max-connections-per-window"`
BanDuration time.Duration `yaml:"throttle-ban-duration"`
CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
Exempted []string
CustomLimits map[string]CustomLimitConfig `yaml:"custom-limits"`
}
type LimiterConfig struct {
rawLimiterConfig
ipv4Mask net.IPMask
ipv6Mask net.IPMask
exemptedNets []net.IPNet
customLimits []customLimit
}
func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
if err = unmarshal(&config.rawLimiterConfig); err != nil {
return err
}
return config.postprocess()
}
func (config *LimiterConfig) postprocess() (err error) {
config.exemptedNets, err = utils.ParseNetList(config.Exempted)
if err != nil {
return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
}
for netStr, customLimitConf := range config.CustomLimits {
normalizedNet, err := utils.NormalizedNetFromString(netStr)
if err != nil {
return fmt.Errorf("Could not parse custom limit specification: %v", err.Error())
}
config.customLimits = append(config.customLimits, customLimit{
CustomLimitConfig: customLimitConf,
ipNet: normalizedNet,
})
}
config.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
config.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
return nil
}
// Limiter manages the automated client connection limits. // Limiter manages the automated client connection limits.
type Limiter struct { type Limiter struct {
sync.Mutex sync.Mutex
enabled bool config *LimiterConfig
ipv4Mask net.IPMask
ipv6Mask net.IPMask
// subnetLimit is the maximum number of clients per subnet
subnetLimit int
// population holds IP -> count of clients connected from there
population map[string]int
// exemptedNets holds networks that are exempt from limits // IP/CIDR -> count of clients connected from there:
exemptedNets []net.IPNet limiter map[string]int
// IP/CIDR -> throttle state:
throttler map[string]ThrottleDetails
} }
// addrToKey canonicalizes `addr` to a string key. // addrToKey canonicalizes `addr` to a string key, and returns
func addrToKey(addr net.IP, v4Mask net.IPMask, v6Mask net.IPMask) string { // the relevant connection limit and throttle max-per-window values
if addr.To4() != nil { func (cl *Limiter) addrToKey(addr net.IP) (key string, limit int, throttle int) {
addr = addr.Mask(v4Mask) // IP.Mask() handles the 4-in-6 mapping for us // `key` will be a CIDR string like "8.8.8.8/32" or "2001:0db8::/32"
} else { for _, custom := range cl.config.customLimits {
addr = addr.Mask(v6Mask) if custom.ipNet.Contains(addr) {
return custom.ipNet.String(), custom.MaxConcurrent, custom.MaxPerWindow
}
} }
return addr.String()
var ipNet net.IPNet
addrv4 := addr.To4()
if addrv4 != nil {
ipNet = net.IPNet{
IP: addrv4.Mask(cl.config.ipv4Mask),
Mask: cl.config.ipv4Mask,
}
} else {
ipNet = net.IPNet{
IP: addr.Mask(cl.config.ipv6Mask),
Mask: cl.config.ipv6Mask,
}
}
return ipNet.String(), cl.config.MaxConcurrent, cl.config.MaxPerWindow
} }
// AddClient adds a client to our population if possible. If we can't, throws an error instead. // 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 *Limiter) AddClient(addr net.IP) error {
func (cl *Limiter) AddClient(addr net.IP, force bool) error {
cl.Lock() cl.Lock()
defer cl.Unlock() defer cl.Unlock()
// we don't track populations for exempted addresses or nets - this is by design // we don't track populations for exempted addresses or nets - this is by design
if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) { if utils.IPInNets(addr, cl.config.exemptedNets) {
return nil return nil
} }
// check population addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
if cl.population[addrString]+1 > cl.subnetLimit && !force { // XXX check throttle first; if we checked limit first and then checked throttle,
return errTooManyClients // we'd have to decrement the limit on an unsuccessful throttle check
if cl.config.Throttle {
details := cl.throttler[addrString] // retrieve mutable throttle state from the map
// add in constant state to process the limiting operation
g := GenericThrottle{
ThrottleDetails: details,
Duration: cl.config.Window,
Limit: maxPerWindow,
}
throttled, _ := g.Touch() // actually check the limit
cl.throttler[addrString] = g.ThrottleDetails // store modified mutable state
if throttled {
return ErrThrottleExceeded
}
} }
cl.population[addrString] = cl.population[addrString] + 1 // now check limiter
if cl.config.Count {
count := cl.limiter[addrString] + 1
if count > maxConcurrent {
return ErrLimitExceeded
}
cl.limiter[addrString] = count
}
return nil return nil
} }
@ -80,45 +172,43 @@ func (cl *Limiter) RemoveClient(addr net.IP) {
cl.Lock() cl.Lock()
defer cl.Unlock() defer cl.Unlock()
if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) { if !cl.config.Count || utils.IPInNets(addr, cl.config.exemptedNets) {
return return
} }
addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask) addrString, _, _ := cl.addrToKey(addr)
cl.population[addrString] = cl.population[addrString] - 1 count := cl.limiter[addrString]
count -= 1
// safety limiter if count < 0 {
if cl.population[addrString] < 0 { count = 0
cl.population[addrString] = 0
} }
cl.limiter[addrString] = count
} }
// ApplyConfig atomically applies a config update to a connection limit handler // ResetThrottle resets the throttle count for an IP
func (cl *Limiter) ApplyConfig(config LimiterConfig) error { func (cl *Limiter) ResetThrottle(addr net.IP) {
// assemble exempted nets
exemptedNets, err := utils.ParseNetList(config.Exempted)
if err != nil {
return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
}
cl.Lock() cl.Lock()
defer cl.Unlock() defer cl.Unlock()
if cl.population == nil { if !cl.config.Throttle || utils.IPInNets(addr, cl.config.exemptedNets) {
cl.population = make(map[string]int) return
} }
cl.enabled = config.Enabled addrString, _, _ := cl.addrToKey(addr)
cl.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32) delete(cl.throttler, addrString)
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 // ApplyConfig atomically applies a config update to a connection limit handler
cl.subnetLimit = config.ConnsPerSubnet func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
// but: check if the current key was left unset, but the legacy was set: cl.Lock()
if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 { defer cl.Unlock()
cl.subnetLimit = config.IPsPerSubnet
} if cl.limiter == nil {
cl.exemptedNets = exemptedNets cl.limiter = make(map[string]int)
}
return nil if cl.throttler == nil {
cl.throttler = make(map[string]ThrottleDetails)
}
cl.config = config
} }

View File

@ -0,0 +1,87 @@
// Copyright (c) 2018 Shivaram Lingamneni
// released under the MIT license
package connection_limits
import (
"net"
"testing"
"time"
)
func easyParseIP(ipstr string) (result net.IP) {
result = net.ParseIP(ipstr)
if result == nil {
panic(ipstr)
}
return
}
var baseConfig = LimiterConfig{
rawLimiterConfig: rawLimiterConfig{
Count: true,
MaxConcurrent: 4,
Throttle: true,
Window: time.Second * 600,
MaxPerWindow: 8,
CidrLenIPv4: 32,
CidrLenIPv6: 64,
Exempted: []string{"localhost"},
CustomLimits: map[string]CustomLimitConfig{
"8.8.0.0/16": {
MaxConcurrent: 128,
MaxPerWindow: 256,
},
},
},
}
func TestKeying(t *testing.T) {
config := baseConfig
config.postprocess()
var limiter Limiter
limiter.ApplyConfig(&config)
key, maxConc, maxWin := limiter.addrToKey(easyParseIP("1.1.1.1"))
assertEqual(key, "1.1.1.1/32", t)
assertEqual(maxConc, 4, t)
assertEqual(maxWin, 8, t)
key, maxConc, maxWin = limiter.addrToKey(easyParseIP("2607:5301:201:3100::7426"))
assertEqual(key, "2607:5301:201:3100::/64", t)
assertEqual(maxConc, 4, t)
assertEqual(maxWin, 8, t)
key, maxConc, maxWin = limiter.addrToKey(easyParseIP("8.8.4.4"))
assertEqual(key, "8.8.0.0/16", t)
assertEqual(maxConc, 128, t)
assertEqual(maxWin, 256, t)
}
func TestLimits(t *testing.T) {
regularIP := easyParseIP("2607:5301:201:3100::7426")
config := baseConfig
config.postprocess()
var limiter Limiter
limiter.ApplyConfig(&config)
for i := 0; i < 4; i++ {
err := limiter.AddClient(regularIP)
if err != nil {
t.Errorf("ip should not be blocked, but %v", err)
}
}
err := limiter.AddClient(regularIP)
if err != ErrLimitExceeded {
t.Errorf("ip should be blocked, but %v", err)
}
limiter.RemoveClient(regularIP)
err = limiter.AddClient(regularIP)
if err != nil {
t.Errorf("ip should not be blocked, but %v", err)
}
}

View File

@ -4,28 +4,9 @@
package connection_limits package connection_limits
import ( import (
"fmt"
"net"
"sync"
"time" "time"
"github.com/oragono/oragono/irc/utils"
) )
// ThrottlerConfig controls the automated connection throttling.
type ThrottlerConfig 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. // ThrottleDetails holds the connection-throttling details for a subnet/IP.
type ThrottleDetails struct { type ThrottleDetails struct {
Start time.Time Start time.Time
@ -68,111 +49,3 @@ func (g *GenericThrottle) touch(now time.Time) (throttled bool, remainingTime ti
return false, 0 return false, 0
} }
} }
// Throttler manages automated client connection throttling.
type Throttler struct {
sync.RWMutex
enabled bool
ipv4Mask net.IPMask
ipv6Mask net.IPMask
subnetLimit int
duration time.Duration
population map[string]ThrottleDetails
// used by the server to ban clients that go over this limit
banDuration time.Duration
banMessage string
// exemptedNets holds networks that are exempt from limits
exemptedNets []net.IPNet
}
// ResetFor removes any existing count for the given address.
func (ct *Throttler) ResetFor(addr net.IP) {
ct.Lock()
defer ct.Unlock()
if !ct.enabled {
return
}
// remove
addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask)
delete(ct.population, addrString)
}
// AddClient introduces a new client connection if possible. If we can't, throws an error instead.
func (ct *Throttler) AddClient(addr net.IP) error {
ct.Lock()
defer ct.Unlock()
if !ct.enabled {
return nil
}
// check exempted lists
if utils.IPInNets(addr, ct.exemptedNets) {
return nil
}
// check throttle
addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask)
details := ct.population[addrString] // retrieve mutable throttle state from the map
// add in constant state to process the limiting operation
g := GenericThrottle{
ThrottleDetails: details,
Duration: ct.duration,
Limit: ct.subnetLimit,
}
throttled, _ := g.Touch() // actually check the limit
ct.population[addrString] = g.ThrottleDetails // store modified mutable state
if throttled {
return errTooManyClients
} else {
return nil
}
}
func (ct *Throttler) BanDuration() time.Duration {
ct.RLock()
defer ct.RUnlock()
return ct.banDuration
}
func (ct *Throttler) BanMessage() string {
ct.RLock()
defer ct.RUnlock()
return ct.banMessage
}
// ApplyConfig atomically applies a config update to a throttler
func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error {
// assemble exempted nets
exemptedNets, err := utils.ParseNetList(config.Exempted)
if err != nil {
return fmt.Errorf("Could not parse throttle exemption list: %v", err.Error())
}
ct.Lock()
defer ct.Unlock()
if ct.population == nil {
ct.population = make(map[string]ThrottleDetails)
}
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.exemptedNets = exemptedNets
return nil
}

View File

@ -62,19 +62,23 @@ func TestGenericThrottleDisabled(t *testing.T) {
} }
} }
func makeTestThrottler(v4len, v6len int) *Throttler { func makeTestThrottler(v4len, v6len int) *Limiter {
minute, _ := time.ParseDuration("1m") minute, _ := time.ParseDuration("1m")
maxConnections := 3 maxConnections := 3
config := ThrottlerConfig{ config := LimiterConfig{
Enabled: true, rawLimiterConfig: rawLimiterConfig{
CidrLenIPv4: v4len, Count: false,
CidrLenIPv6: v6len, Throttle: true,
ConnectionsPerCidr: maxConnections, CidrLenIPv4: v4len,
Duration: minute, CidrLenIPv6: v6len,
MaxPerWindow: maxConnections,
Window: minute,
},
} }
var throttler Throttler config.postprocess()
throttler.ApplyConfig(config) var limiter Limiter
return &throttler limiter.ApplyConfig(&config)
return &limiter
} }
func TestConnectionThrottle(t *testing.T) { func TestConnectionThrottle(t *testing.T) {
@ -86,7 +90,7 @@ func TestConnectionThrottle(t *testing.T) {
assertEqual(err, nil, t) assertEqual(err, nil, t)
} }
err := throttler.AddClient(addr) err := throttler.AddClient(addr)
assertEqual(err, errTooManyClients, t) assertEqual(err, ErrThrottleExceeded, t)
} }
func TestConnectionThrottleIPv6(t *testing.T) { func TestConnectionThrottleIPv6(t *testing.T) {
@ -101,7 +105,7 @@ func TestConnectionThrottleIPv6(t *testing.T) {
assertEqual(err, nil, t) assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("2001:0db8::4")) err = throttler.AddClient(net.ParseIP("2001:0db8::4"))
assertEqual(err, errTooManyClients, t) assertEqual(err, ErrThrottleExceeded, t)
} }
func TestConnectionThrottleIPv4(t *testing.T) { func TestConnectionThrottleIPv4(t *testing.T) {
@ -116,5 +120,5 @@ func TestConnectionThrottleIPv4(t *testing.T) {
assertEqual(err, nil, t) assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("192.168.1.104")) err = throttler.AddClient(net.ParseIP("192.168.1.104"))
assertEqual(err, errTooManyClients, t) assertEqual(err, ErrThrottleExceeded, t)
} }

View File

@ -4,16 +4,10 @@
package connection_limits package connection_limits
import ( import (
"errors"
"sync" "sync"
"time" "time"
) )
var (
ErrLimitExceeded = errors.New("too many concurrent connections")
ErrThrottleExceeded = errors.New("too many recent connection attempts")
)
// TorLimiter is a combined limiter and throttler for use on connections // TorLimiter is a combined limiter and throttler for use on connections
// proxied from a Tor hidden service (so we don't have meaningful IPs, // proxied from a Tor hidden service (so we don't have meaningful IPs,
// a notion of CIDR width, etc.) // a notion of CIDR width, etc.)

View File

@ -46,6 +46,8 @@ var (
// we only have standard channels for now. TODO: any updates to this // we only have standard channels for now. TODO: any updates to this
// will also need to be reflected in CasefoldChannel // will also need to be reflected in CasefoldChannel
chanTypes = "#" chanTypes = "#"
throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect."
) )
// ListenerWrapper wraps a listener so it can be safely reconfigured or stopped // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
@ -59,34 +61,33 @@ type ListenerWrapper struct {
// Server is the main Oragono server. // Server is the main Oragono server.
type Server struct { type Server struct {
accounts AccountManager accounts AccountManager
channels ChannelManager channels ChannelManager
channelRegistry ChannelRegistry channelRegistry ChannelRegistry
clients ClientManager clients ClientManager
config unsafe.Pointer config unsafe.Pointer
configFilename string configFilename string
connectionLimiter connection_limits.Limiter connectionLimiter connection_limits.Limiter
connectionThrottler connection_limits.Throttler ctime time.Time
ctime time.Time dlines *DLineManager
dlines *DLineManager helpIndexManager HelpIndexManager
helpIndexManager HelpIndexManager klines *KLineManager
klines *KLineManager listeners map[string]*ListenerWrapper
listeners map[string]*ListenerWrapper logger *logger.Manager
logger *logger.Manager monitorManager MonitorManager
monitorManager MonitorManager name string
name string nameCasefolded string
nameCasefolded string rehashMutex sync.Mutex // tier 4
rehashMutex sync.Mutex // tier 4 rehashSignal chan os.Signal
rehashSignal chan os.Signal pprofServer *http.Server
pprofServer *http.Server resumeManager ResumeManager
resumeManager ResumeManager signals chan os.Signal
signals chan os.Signal snomasks SnoManager
snomasks SnoManager store *buntdb.DB
store *buntdb.DB torLimiter connection_limits.TorLimiter
torLimiter connection_limits.TorLimiter whoWas WhoWasList
whoWas WhoWasList stats Stats
stats Stats semaphores ServerSemaphores
semaphores ServerSemaphores
} }
var ( var (
@ -214,32 +215,28 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
} }
// check connection limits // check connection limits
err := server.connectionLimiter.AddClient(ipaddr, false) err := server.connectionLimiter.AddClient(ipaddr)
if err != nil { if err == connection_limits.ErrLimitExceeded {
// too many connections from one client, tell the client and close the connection // 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)) server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr))
return true, "Too many clients from your network" return true, "Too many clients from your network"
} } else if err == connection_limits.ErrThrottleExceeded {
duration := server.Config().Server.IPLimits.BanDuration
// check connection throttle
err = server.connectionThrottler.AddClient(ipaddr)
if err != nil {
// too many connections too quickly from client, tell them and close the connection
duration := server.connectionThrottler.BanDuration()
if duration == 0 { if duration == 0 {
return false, "" return false, ""
} }
server.dlines.AddIP(ipaddr, duration, server.connectionThrottler.BanMessage(), "Exceeded automated connection throttle", "auto.connection.throttler") server.dlines.AddIP(ipaddr, duration, throttleMessage, "Exceeded automated connection throttle", "auto.connection.throttler")
// they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now, // 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 // and once their temporary DLINE is finished they can fill up the throttler again
server.connectionThrottler.ResetFor(ipaddr) server.connectionLimiter.ResetThrottle(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 // 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( server.logger.Info(
"localconnect-ip", "localconnect-ip",
fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration)) fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration))
return true, server.connectionThrottler.BanMessage() return true, throttleMessage
} else if err != nil {
server.logger.Warning("internal", "unexpected ban result", err.Error())
} }
return false, "" return false, ""
@ -604,15 +601,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
// first, reload config sections for functionality implemented in subpackages: // first, reload config sections for functionality implemented in subpackages:
err = server.connectionLimiter.ApplyConfig(config.Server.ConnectionLimiter) server.connectionLimiter.ApplyConfig(&config.Server.IPLimits)
if err != nil {
return err
}
err = server.connectionThrottler.ApplyConfig(config.Server.ConnectionThrottler)
if err != nil {
return err
}
tlConf := &config.Server.TorListeners tlConf := &config.Server.TorListeners
server.torLimiter.Configure(tlConf.MaxConnections, tlConf.ThrottleDuration, tlConf.MaxConnectionsPerDuration) server.torLimiter.Configure(tlConf.MaxConnections, tlConf.ThrottleDuration, tlConf.MaxConnectionsPerDuration)

View File

@ -144,53 +144,42 @@ server:
# this works around that bug, allowing them to use SASL. # this works around that bug, allowing them to use SASL.
send-unprefixed-sasl: true send-unprefixed-sasl: true
# maximum number of connections per subnet # IP-based DoS protection
connection-limits: ip-limits:
# whether to enforce connection limits or not # whether to limit the total number of concurrent connections per IP/CIDR
enabled: true count: true
# maximum concurrent connections per IP/CIDR
max-concurrent-connections: 16
# how wide the cidr should be for IPv4 # whether to restrict the rate of new connections per IP/CIDR
throttle: true
# how long to keep track of connections for
window: 10m
# maximum number of new connections per IP/CIDR within the given duration
max-connections-per-window: 32
# how long to ban offenders for. after banning them, the number of connections is
# reset, which lets you use /UNDLINE to unban people
throttle-ban-duration: 10m
# how wide the CIDR should be for IPv4 (a /32 is a fully specified IPv4 address)
cidr-len-ipv4: 32 cidr-len-ipv4: 32
# how wide the CIDR should be for IPv6 (a /64 is the typical prefix assigned
# how wide the cidr should be for IPv6 # by an ISP to an individual customer for their LAN)
cidr-len-ipv6: 64 cidr-len-ipv6: 64
# maximum concurrent connections per subnet (defined above by the cidr length)
connections-per-subnet: 16
# IPs/networks which are exempted from connection limits # IPs/networks which are exempted from connection limits
exempted: exempted:
- "localhost" - "localhost"
# - "192.168.1.1" # - "192.168.1.1"
# - "2001:0db8::/32" # - "2001:0db8::/32"
# automated connection throttling # custom connection limits for certain IPs/networks. note that CIDR
connection-throttling: # widths defined here override the default CIDR width --- the limit
# whether to throttle connections or not # will apply to the entire CIDR no matter how large or small it is
enabled: true custom-limits:
# "8.8.0.0/16":
# how wide the cidr should be for IPv4 # max-concurrent-connections: 128
cidr-len-ipv4: 32 # max-connections-per-window: 1024
# how wide the cidr should be for IPv6
cidr-len-ipv6: 64
# how long to keep track of connections for
duration: 10m
# maximum number of connections, per subnet, within the given duration
max-connections: 32
# how long to ban offenders for, and the message to use
# after banning them, the number of connections is reset (which lets you use UNDLINE to unban people)
ban-duration: 10m
ban-message: You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect.
# IPs/networks which are exempted from connection throttling
exempted:
- "localhost"
# - "192.168.1.1"
# - "2001:0db8::/32"
# IP cloaking hides users' IP addresses from other users and from channel admins # IP cloaking hides users' IP addresses from other users and from channel admins
# (but not from server admins), while still allowing channel admins to ban # (but not from server admins), while still allowing channel admins to ban
@ -214,13 +203,11 @@ server:
# the cloaked hostname is derived only from the CIDR (most significant bits # the cloaked hostname is derived only from the CIDR (most significant bits
# of the IP address), up to a configurable number of bits. this is the # of the IP address), up to a configurable number of bits. this is the
# granularity at which bans will take effect for ipv4 (a /32 is a fully # granularity at which bans will take effect for IPv4. Note that changing
# specified IP address). note that changing this value will invalidate # this value will invalidate any stored bans.
# any stored bans.
cidr-len-ipv4: 32 cidr-len-ipv4: 32
# analogous value for ipv6 (an ipv6 /64 is the typical prefix assigned # analogous granularity for IPv6
# by an ISP to an individual customer for their LAN)
cidr-len-ipv6: 64 cidr-len-ipv6: 64
# number of bits of hash output to include in the cloaked hostname. # number of bits of hash output to include in the cloaked hostname.