You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

limiter.go 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package connection_limits
  4. import (
  5. "errors"
  6. "fmt"
  7. "net"
  8. "sync"
  9. "github.com/oragono/oragono/irc/utils"
  10. )
  11. // LimiterConfig controls the automated connection limits.
  12. type LimiterConfig struct {
  13. Enabled bool
  14. CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
  15. CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
  16. ConnsPerSubnet int `yaml:"connections-per-subnet"`
  17. IPsPerSubnet int `yaml:"ips-per-subnet"` // legacy name for ConnsPerSubnet
  18. Exempted []string
  19. }
  20. var (
  21. errTooManyClients = errors.New("Too many clients in subnet")
  22. )
  23. // Limiter manages the automated client connection limits.
  24. type Limiter struct {
  25. sync.Mutex
  26. enabled bool
  27. ipv4Mask net.IPMask
  28. ipv6Mask net.IPMask
  29. // subnetLimit is the maximum number of clients per subnet
  30. subnetLimit int
  31. // population holds IP -> count of clients connected from there
  32. population map[string]int
  33. // exemptedNets holds networks that are exempt from limits
  34. exemptedNets []net.IPNet
  35. }
  36. // addrToKey canonicalizes `addr` to a string key.
  37. func addrToKey(addr net.IP, v4Mask net.IPMask, v6Mask net.IPMask) string {
  38. if addr.To4() != nil {
  39. addr = addr.Mask(v4Mask) // IP.Mask() handles the 4-in-6 mapping for us
  40. } else {
  41. addr = addr.Mask(v6Mask)
  42. }
  43. return addr.String()
  44. }
  45. // AddClient adds a client to our population if possible. If we can't, throws an error instead.
  46. // 'force' is used to add already-existing clients (i.e. ones that are already on the network).
  47. func (cl *Limiter) AddClient(addr net.IP, force bool) error {
  48. cl.Lock()
  49. defer cl.Unlock()
  50. if !cl.enabled {
  51. return nil
  52. }
  53. // check exempted lists
  54. // we don't track populations for exempted addresses or nets - this is by design
  55. if utils.IPInNets(addr, cl.exemptedNets) {
  56. return nil
  57. }
  58. // check population
  59. addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
  60. if cl.population[addrString]+1 > cl.subnetLimit && !force {
  61. return errTooManyClients
  62. }
  63. cl.population[addrString] = cl.population[addrString] + 1
  64. return nil
  65. }
  66. // RemoveClient removes the given address from our population
  67. func (cl *Limiter) RemoveClient(addr net.IP) {
  68. cl.Lock()
  69. defer cl.Unlock()
  70. if !cl.enabled {
  71. return
  72. }
  73. addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
  74. cl.population[addrString] = cl.population[addrString] - 1
  75. // safety limiter
  76. if cl.population[addrString] < 0 {
  77. cl.population[addrString] = 0
  78. }
  79. }
  80. // NewLimiter returns a new connection limit handler.
  81. // The handler is functional, but disabled; it can be enabled via `ApplyConfig`.
  82. func NewLimiter() *Limiter {
  83. var cl Limiter
  84. // initialize empty population; all other state is configurable
  85. cl.population = make(map[string]int)
  86. return &cl
  87. }
  88. // ApplyConfig atomically applies a config update to a connection limit handler
  89. func (cl *Limiter) ApplyConfig(config LimiterConfig) error {
  90. // assemble exempted nets
  91. exemptedNets, err := utils.ParseNetList(config.Exempted)
  92. if err != nil {
  93. return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
  94. }
  95. cl.Lock()
  96. defer cl.Unlock()
  97. cl.enabled = config.Enabled
  98. cl.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
  99. cl.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
  100. // subnetLimit is explicitly NOT capped at a minimum of one.
  101. // this is so that CL config can be used to allow ONLY clients from exempted IPs/nets
  102. cl.subnetLimit = config.ConnsPerSubnet
  103. // but: check if the current key was left unset, but the legacy was set:
  104. if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 {
  105. cl.subnetLimit = config.IPsPerSubnet
  106. }
  107. cl.exemptedNets = exemptedNets
  108. return nil
  109. }