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 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. "time"
  10. "github.com/oragono/oragono/irc/utils"
  11. )
  12. var (
  13. ErrLimitExceeded = errors.New("too many concurrent connections")
  14. ErrThrottleExceeded = errors.New("too many recent connection attempts")
  15. )
  16. type CustomLimitConfig struct {
  17. MaxConcurrent int `yaml:"max-concurrent-connections"`
  18. MaxPerWindow int `yaml:"max-connections-per-window"`
  19. }
  20. // tuples the key-value pair of a CIDR and its custom limit/throttle values
  21. type customLimit struct {
  22. CustomLimitConfig
  23. ipNet net.IPNet
  24. }
  25. // LimiterConfig controls the automated connection limits.
  26. // RawLimiterConfig contains all the YAML-visible fields;
  27. // LimiterConfig contains additional denormalized private fields
  28. type RawLimiterConfig struct {
  29. Limit bool
  30. MaxConcurrent int `yaml:"max-concurrent-connections"`
  31. Throttle bool
  32. Window time.Duration
  33. MaxPerWindow int `yaml:"max-connections-per-window"`
  34. BanDuration time.Duration `yaml:"throttle-ban-duration"`
  35. CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
  36. CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
  37. Exempted []string
  38. CustomLimits map[string]CustomLimitConfig `yaml:"custom-limits"`
  39. }
  40. type LimiterConfig struct {
  41. RawLimiterConfig
  42. ipv4Mask net.IPMask
  43. ipv6Mask net.IPMask
  44. exemptedNets []net.IPNet
  45. customLimits []customLimit
  46. }
  47. func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
  48. if err = unmarshal(&config.RawLimiterConfig); err != nil {
  49. return err
  50. }
  51. return config.postprocess()
  52. }
  53. func (config *LimiterConfig) postprocess() (err error) {
  54. config.exemptedNets, err = utils.ParseNetList(config.Exempted)
  55. if err != nil {
  56. return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
  57. }
  58. for netStr, customLimitConf := range config.CustomLimits {
  59. normalizedNet, err := utils.NormalizedNetFromString(netStr)
  60. if err != nil {
  61. return fmt.Errorf("Could not parse custom limit specification: %v", err.Error())
  62. }
  63. config.customLimits = append(config.customLimits, customLimit{
  64. CustomLimitConfig: customLimitConf,
  65. ipNet: normalizedNet,
  66. })
  67. }
  68. config.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
  69. config.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
  70. return nil
  71. }
  72. // Limiter manages the automated client connection limits.
  73. type Limiter struct {
  74. sync.Mutex
  75. config *LimiterConfig
  76. // IP/CIDR -> count of clients connected from there:
  77. limiter map[string]int
  78. // IP/CIDR -> throttle state:
  79. throttler map[string]ThrottleDetails
  80. }
  81. // addrToKey canonicalizes `addr` to a string key, and returns
  82. // the relevant connection limit and throttle max-per-window values
  83. func (cl *Limiter) addrToKey(addr net.IP) (key string, limit int, throttle int) {
  84. // `key` will be a CIDR string like "8.8.8.8/32" or "2001:0db8::/32"
  85. for _, custom := range cl.config.customLimits {
  86. if custom.ipNet.Contains(addr) {
  87. return custom.ipNet.String(), custom.MaxConcurrent, custom.MaxPerWindow
  88. }
  89. }
  90. var ipNet net.IPNet
  91. addrv4 := addr.To4()
  92. if addrv4 != nil {
  93. ipNet = net.IPNet{
  94. IP: addrv4.Mask(cl.config.ipv4Mask),
  95. Mask: cl.config.ipv4Mask,
  96. }
  97. } else {
  98. ipNet = net.IPNet{
  99. IP: addr.Mask(cl.config.ipv6Mask),
  100. Mask: cl.config.ipv6Mask,
  101. }
  102. }
  103. return ipNet.String(), cl.config.MaxConcurrent, cl.config.MaxPerWindow
  104. }
  105. // AddClient adds a client to our population if possible. If we can't, throws an error instead.
  106. func (cl *Limiter) AddClient(addr net.IP) error {
  107. cl.Lock()
  108. defer cl.Unlock()
  109. // we don't track populations for exempted addresses or nets - this is by design
  110. if utils.IPInNets(addr, cl.config.exemptedNets) {
  111. return nil
  112. }
  113. addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
  114. // XXX check throttle first; if we checked limit first and then checked throttle,
  115. // we'd have to decrement the limit on an unsuccessful throttle check
  116. if cl.config.Throttle {
  117. details := cl.throttler[addrString] // retrieve mutable throttle state from the map
  118. // add in constant state to process the limiting operation
  119. g := GenericThrottle{
  120. ThrottleDetails: details,
  121. Duration: cl.config.Window,
  122. Limit: maxPerWindow,
  123. }
  124. throttled, _ := g.Touch() // actually check the limit
  125. cl.throttler[addrString] = g.ThrottleDetails // store modified mutable state
  126. if throttled {
  127. return ErrThrottleExceeded
  128. }
  129. }
  130. // now check limiter
  131. if cl.config.Limit {
  132. count := cl.limiter[addrString] + 1
  133. if count > maxConcurrent {
  134. return ErrLimitExceeded
  135. }
  136. cl.limiter[addrString] = count
  137. }
  138. return nil
  139. }
  140. // RemoveClient removes the given address from our population
  141. func (cl *Limiter) RemoveClient(addr net.IP) {
  142. cl.Lock()
  143. defer cl.Unlock()
  144. if !cl.config.Limit || utils.IPInNets(addr, cl.config.exemptedNets) {
  145. return
  146. }
  147. addrString, _, _ := cl.addrToKey(addr)
  148. count := cl.limiter[addrString]
  149. count -= 1
  150. if count < 0 {
  151. count = 0
  152. }
  153. cl.limiter[addrString] = count
  154. }
  155. // ResetThrottle resets the throttle count for an IP
  156. func (cl *Limiter) ResetThrottle(addr net.IP) {
  157. cl.Lock()
  158. defer cl.Unlock()
  159. if !cl.config.Throttle || utils.IPInNets(addr, cl.config.exemptedNets) {
  160. return
  161. }
  162. addrString, _, _ := cl.addrToKey(addr)
  163. delete(cl.throttler, addrString)
  164. }
  165. // ApplyConfig atomically applies a config update to a connection limit handler
  166. func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
  167. cl.Lock()
  168. defer cl.Unlock()
  169. if cl.limiter == nil {
  170. cl.limiter = make(map[string]int)
  171. }
  172. if cl.throttler == nil {
  173. cl.throttler = make(map[string]ThrottleDetails)
  174. }
  175. cl.config = config
  176. }