// Copyright (c) 2016-2017 Daniel Oaks // released under the MIT license package connection_limits import ( "errors" "fmt" "net" "sync" "github.com/oragono/oragono/irc/utils" ) // LimiterConfig controls the automated connection limits. type LimiterConfig struct { Enabled bool CidrLenIPv4 int `yaml:"cidr-len-ipv4"` CidrLenIPv6 int `yaml:"cidr-len-ipv6"` ConnsPerSubnet int `yaml:"connections-per-subnet"` IPsPerSubnet int `yaml:"ips-per-subnet"` // legacy name for ConnsPerSubnet Exempted []string } var ( errTooManyClients = errors.New("Too many clients in subnet") ) // Limiter manages the automated client connection limits. type Limiter struct { sync.Mutex enabled bool 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 exemptedNets []net.IPNet } // addrToKey canonicalizes `addr` to a string key. func addrToKey(addr net.IP, v4Mask net.IPMask, v6Mask net.IPMask) string { if addr.To4() != nil { addr = addr.Mask(v4Mask) // IP.Mask() handles the 4-in-6 mapping for us } else { addr = addr.Mask(v6Mask) } return addr.String() } // 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, force bool) error { cl.Lock() defer cl.Unlock() // we don't track populations for exempted addresses or nets - this is by design if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) { return nil } // check population addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask) if cl.population[addrString]+1 > cl.subnetLimit && !force { return errTooManyClients } cl.population[addrString] = cl.population[addrString] + 1 return nil } // RemoveClient removes the given address from our population func (cl *Limiter) RemoveClient(addr net.IP) { cl.Lock() defer cl.Unlock() if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) { return } addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask) cl.population[addrString] = cl.population[addrString] - 1 // safety limiter if cl.population[addrString] < 0 { cl.population[addrString] = 0 } } // ApplyConfig atomically applies a config update to a connection limit handler func (cl *Limiter) ApplyConfig(config LimiterConfig) error { // 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() defer cl.Unlock() if cl.population == nil { cl.population = make(map[string]int) } 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.ConnsPerSubnet // but: check if the current key was left unset, but the legacy was set: if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 { cl.subnetLimit = config.IPsPerSubnet } cl.exemptedNets = exemptedNets return nil }