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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package connection_limits
  4. import (
  5. "crypto/md5"
  6. "errors"
  7. "fmt"
  8. "net"
  9. "sync"
  10. "time"
  11. "github.com/oragono/oragono/irc/flatip"
  12. "github.com/oragono/oragono/irc/utils"
  13. )
  14. var (
  15. ErrLimitExceeded = errors.New("too many concurrent connections")
  16. ErrThrottleExceeded = errors.New("too many recent connection attempts")
  17. )
  18. type CustomLimitConfig struct {
  19. Nets []string
  20. MaxConcurrent int `yaml:"max-concurrent-connections"`
  21. MaxPerWindow int `yaml:"max-connections-per-window"`
  22. }
  23. // tuples the key-value pair of a CIDR and its custom limit/throttle values
  24. type customLimit struct {
  25. name [16]byte
  26. maxConcurrent int
  27. maxPerWindow int
  28. nets []flatip.IPNet
  29. }
  30. type limiterKey struct {
  31. maskedIP flatip.IP
  32. prefixLen uint8 // 0 for the fake nets we generate for custom limits
  33. }
  34. // LimiterConfig controls the automated connection limits.
  35. // rawLimiterConfig contains all the YAML-visible fields;
  36. // LimiterConfig contains additional denormalized private fields
  37. type rawLimiterConfig struct {
  38. Count bool
  39. MaxConcurrent int `yaml:"max-concurrent-connections"`
  40. Throttle bool
  41. Window time.Duration
  42. MaxPerWindow int `yaml:"max-connections-per-window"`
  43. BanDuration time.Duration `yaml:"throttle-ban-duration"`
  44. CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
  45. CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
  46. Exempted []string
  47. CustomLimits map[string]CustomLimitConfig `yaml:"custom-limits"`
  48. }
  49. type LimiterConfig struct {
  50. rawLimiterConfig
  51. exemptedNets []flatip.IPNet
  52. customLimits []customLimit
  53. }
  54. func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
  55. if err = unmarshal(&config.rawLimiterConfig); err != nil {
  56. return err
  57. }
  58. return config.postprocess()
  59. }
  60. func (config *LimiterConfig) postprocess() (err error) {
  61. exemptedNets, err := utils.ParseNetList(config.Exempted)
  62. if err != nil {
  63. return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
  64. }
  65. config.exemptedNets = make([]flatip.IPNet, len(exemptedNets))
  66. for i, exempted := range exemptedNets {
  67. config.exemptedNets[i] = flatip.FromNetIPNet(exempted)
  68. }
  69. for identifier, customLimitConf := range config.CustomLimits {
  70. nets := make([]flatip.IPNet, len(customLimitConf.Nets))
  71. for i, netStr := range customLimitConf.Nets {
  72. normalizedNet, err := flatip.ParseToNormalizedNet(netStr)
  73. if err != nil {
  74. return fmt.Errorf("Bad net %s in custom-limits block %s: %w", netStr, identifier, err)
  75. }
  76. nets[i] = normalizedNet
  77. }
  78. if len(customLimitConf.Nets) == 0 {
  79. // see #1421: this is the legacy config format where the
  80. // dictionary key of the block is a CIDR string
  81. normalizedNet, err := flatip.ParseToNormalizedNet(identifier)
  82. if err != nil {
  83. return fmt.Errorf("Custom limit block %s has no defined nets", identifier)
  84. }
  85. nets = []flatip.IPNet{normalizedNet}
  86. }
  87. config.customLimits = append(config.customLimits, customLimit{
  88. maxConcurrent: customLimitConf.MaxConcurrent,
  89. maxPerWindow: customLimitConf.MaxPerWindow,
  90. name: md5.Sum([]byte(identifier)),
  91. nets: nets,
  92. })
  93. }
  94. return nil
  95. }
  96. // Limiter manages the automated client connection limits.
  97. type Limiter struct {
  98. sync.Mutex
  99. config *LimiterConfig
  100. // IP/CIDR -> count of clients connected from there:
  101. limiter map[limiterKey]int
  102. // IP/CIDR -> throttle state:
  103. throttler map[limiterKey]ThrottleDetails
  104. }
  105. // addrToKey canonicalizes `addr` to a string key, and returns
  106. // the relevant connection limit and throttle max-per-window values
  107. func (cl *Limiter) addrToKey(flat flatip.IP) (key limiterKey, limit int, throttle int) {
  108. for _, custom := range cl.config.customLimits {
  109. for _, net := range custom.nets {
  110. if net.Contains(flat) {
  111. return limiterKey{maskedIP: custom.name, prefixLen: 0}, custom.maxConcurrent, custom.maxPerWindow
  112. }
  113. }
  114. }
  115. var prefixLen int
  116. if flat.IsIPv4() {
  117. prefixLen = cl.config.CidrLenIPv4
  118. flat = flat.Mask(prefixLen, 32)
  119. prefixLen += 96
  120. } else {
  121. prefixLen = cl.config.CidrLenIPv6
  122. flat = flat.Mask(prefixLen, 128)
  123. }
  124. return limiterKey{maskedIP: flat, prefixLen: uint8(prefixLen)}, cl.config.MaxConcurrent, cl.config.MaxPerWindow
  125. }
  126. // AddClient adds a client to our population if possible. If we can't, throws an error instead.
  127. func (cl *Limiter) AddClient(addr net.IP) error {
  128. flat := flatip.FromNetIP(addr)
  129. cl.Lock()
  130. defer cl.Unlock()
  131. // we don't track populations for exempted addresses or nets - this is by design
  132. if flatip.IPInNets(flat, cl.config.exemptedNets) {
  133. return nil
  134. }
  135. addrString, maxConcurrent, maxPerWindow := cl.addrToKey(flat)
  136. // XXX check throttle first; if we checked limit first and then checked throttle,
  137. // we'd have to decrement the limit on an unsuccessful throttle check
  138. if cl.config.Throttle {
  139. details := cl.throttler[addrString] // retrieve mutable throttle state from the map
  140. // add in constant state to process the limiting operation
  141. g := GenericThrottle{
  142. ThrottleDetails: details,
  143. Duration: cl.config.Window,
  144. Limit: maxPerWindow,
  145. }
  146. throttled, _ := g.Touch() // actually check the limit
  147. cl.throttler[addrString] = g.ThrottleDetails // store modified mutable state
  148. if throttled {
  149. return ErrThrottleExceeded
  150. }
  151. }
  152. // now check limiter
  153. if cl.config.Count {
  154. count := cl.limiter[addrString] + 1
  155. if count > maxConcurrent {
  156. return ErrLimitExceeded
  157. }
  158. cl.limiter[addrString] = count
  159. }
  160. return nil
  161. }
  162. // RemoveClient removes the given address from our population
  163. func (cl *Limiter) RemoveClient(addr net.IP) {
  164. flat := flatip.FromNetIP(addr)
  165. cl.Lock()
  166. defer cl.Unlock()
  167. if !cl.config.Count || flatip.IPInNets(flat, cl.config.exemptedNets) {
  168. return
  169. }
  170. addrString, _, _ := cl.addrToKey(flat)
  171. count := cl.limiter[addrString]
  172. count -= 1
  173. if count < 0 {
  174. count = 0
  175. }
  176. cl.limiter[addrString] = count
  177. }
  178. // ResetThrottle resets the throttle count for an IP
  179. func (cl *Limiter) ResetThrottle(addr net.IP) {
  180. flat := flatip.FromNetIP(addr)
  181. cl.Lock()
  182. defer cl.Unlock()
  183. if !cl.config.Throttle || flatip.IPInNets(flat, cl.config.exemptedNets) {
  184. return
  185. }
  186. addrString, _, _ := cl.addrToKey(flat)
  187. delete(cl.throttler, addrString)
  188. }
  189. // ApplyConfig atomically applies a config update to a connection limit handler
  190. func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
  191. cl.Lock()
  192. defer cl.Unlock()
  193. if cl.limiter == nil {
  194. cl.limiter = make(map[limiterKey]int)
  195. }
  196. if cl.throttler == nil {
  197. cl.throttler = make(map[limiterKey]ThrottleDetails)
  198. }
  199. cl.config = config
  200. }