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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. "sync"
  9. "time"
  10. "github.com/ergochat/ergo/irc/flatip"
  11. "github.com/ergochat/ergo/irc/utils"
  12. )
  13. var (
  14. ErrLimitExceeded = errors.New("too many concurrent connections")
  15. ErrThrottleExceeded = errors.New("too many recent connection attempts")
  16. )
  17. type CustomLimitConfig struct {
  18. Nets []string
  19. MaxConcurrent int `yaml:"max-concurrent-connections"`
  20. MaxPerWindow int `yaml:"max-connections-per-window"`
  21. }
  22. // tuples the key-value pair of a CIDR and its custom limit/throttle values
  23. type customLimit struct {
  24. name [16]byte
  25. customID string // operator-configured identifier for a custom net
  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. CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
  44. CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
  45. Exempted []string
  46. CustomLimits map[string]CustomLimitConfig `yaml:"custom-limits"`
  47. }
  48. type LimiterConfig struct {
  49. rawLimiterConfig
  50. exemptedNets []flatip.IPNet
  51. customLimits []customLimit
  52. }
  53. func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
  54. if err = unmarshal(&config.rawLimiterConfig); err != nil {
  55. return err
  56. }
  57. return config.postprocess()
  58. }
  59. func (config *LimiterConfig) postprocess() (err error) {
  60. exemptedNets, err := utils.ParseNetList(config.Exempted)
  61. if err != nil {
  62. return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
  63. }
  64. config.exemptedNets = make([]flatip.IPNet, len(exemptedNets))
  65. for i, exempted := range exemptedNets {
  66. config.exemptedNets[i] = flatip.FromNetIPNet(exempted)
  67. }
  68. for identifier, customLimitConf := range config.CustomLimits {
  69. nets := make([]flatip.IPNet, len(customLimitConf.Nets))
  70. for i, netStr := range customLimitConf.Nets {
  71. normalizedNet, err := flatip.ParseToNormalizedNet(netStr)
  72. if err != nil {
  73. return fmt.Errorf("Bad net %s in custom-limits block %s: %w", netStr, identifier, err)
  74. }
  75. nets[i] = normalizedNet
  76. }
  77. if len(customLimitConf.Nets) == 0 {
  78. // see #1421: this is the legacy config format where the
  79. // dictionary key of the block is a CIDR string
  80. normalizedNet, err := flatip.ParseToNormalizedNet(identifier)
  81. if err != nil {
  82. return fmt.Errorf("Custom limit block %s has no defined nets", identifier)
  83. }
  84. nets = []flatip.IPNet{normalizedNet}
  85. }
  86. config.customLimits = append(config.customLimits, customLimit{
  87. maxConcurrent: customLimitConf.MaxConcurrent,
  88. maxPerWindow: customLimitConf.MaxPerWindow,
  89. name: md5.Sum([]byte(identifier)),
  90. customID: 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(addr flatip.IP) (key limiterKey, customID string, limit int, throttle int) {
  108. for _, custom := range cl.config.customLimits {
  109. for _, net := range custom.nets {
  110. if net.Contains(addr) {
  111. return limiterKey{maskedIP: custom.name, prefixLen: 0}, custom.customID, custom.maxConcurrent, custom.maxPerWindow
  112. }
  113. }
  114. }
  115. var prefixLen int
  116. if addr.IsIPv4() {
  117. prefixLen = cl.config.CidrLenIPv4
  118. addr = addr.Mask(prefixLen, 32)
  119. prefixLen += 96
  120. } else {
  121. prefixLen = cl.config.CidrLenIPv6
  122. addr = addr.Mask(prefixLen, 128)
  123. }
  124. return limiterKey{maskedIP: addr, 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 flatip.IP) error {
  128. cl.Lock()
  129. defer cl.Unlock()
  130. // we don't track populations for exempted addresses or nets - this is by design
  131. if flatip.IPInNets(addr, cl.config.exemptedNets) {
  132. return nil
  133. }
  134. addrString, _, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
  135. // check limiter
  136. var count int
  137. if cl.config.Count {
  138. count = cl.limiter[addrString] + 1
  139. if count > maxConcurrent {
  140. return ErrLimitExceeded
  141. }
  142. }
  143. if cl.config.Throttle {
  144. details := cl.throttler[addrString] // retrieve mutable throttle state from the map
  145. // add in constant state to process the limiting operation
  146. g := GenericThrottle{
  147. ThrottleDetails: details,
  148. Duration: cl.config.Window,
  149. Limit: maxPerWindow,
  150. }
  151. throttled, _ := g.Touch() // actually check the limit
  152. cl.throttler[addrString] = g.ThrottleDetails // store modified mutable state
  153. if throttled {
  154. // back out the limiter add
  155. return ErrThrottleExceeded
  156. }
  157. }
  158. // success, record in limiter
  159. if cl.config.Count {
  160. cl.limiter[addrString] = count
  161. }
  162. return nil
  163. }
  164. // RemoveClient removes the given address from our population
  165. func (cl *Limiter) RemoveClient(addr flatip.IP) {
  166. cl.Lock()
  167. defer cl.Unlock()
  168. if !cl.config.Count || flatip.IPInNets(addr, cl.config.exemptedNets) {
  169. return
  170. }
  171. addrString, _, _, _ := cl.addrToKey(addr)
  172. count := cl.limiter[addrString]
  173. count -= 1
  174. if count < 0 {
  175. count = 0
  176. }
  177. cl.limiter[addrString] = count
  178. }
  179. type LimiterStatus struct {
  180. Exempt bool
  181. Count int
  182. MaxCount int
  183. Throttle int
  184. MaxPerWindow int
  185. ThrottleDuration time.Duration
  186. }
  187. func (cl *Limiter) Status(addr flatip.IP) (netName string, status LimiterStatus) {
  188. cl.Lock()
  189. defer cl.Unlock()
  190. if flatip.IPInNets(addr, cl.config.exemptedNets) {
  191. status.Exempt = true
  192. return
  193. }
  194. status.ThrottleDuration = cl.config.Window
  195. limiterKey, customID, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
  196. status.MaxCount = maxConcurrent
  197. status.MaxPerWindow = maxPerWindow
  198. status.Count = cl.limiter[limiterKey]
  199. status.Throttle = cl.throttler[limiterKey].Count
  200. netName = customID
  201. if netName == "" {
  202. netName = flatip.IPNet{
  203. IP: limiterKey.maskedIP,
  204. PrefixLen: limiterKey.prefixLen,
  205. }.String()
  206. }
  207. return
  208. }
  209. // ResetThrottle resets the throttle count for an IP
  210. func (cl *Limiter) ResetThrottle(addr flatip.IP) {
  211. cl.Lock()
  212. defer cl.Unlock()
  213. if !cl.config.Throttle || flatip.IPInNets(addr, cl.config.exemptedNets) {
  214. return
  215. }
  216. addrString, _, _, _ := cl.addrToKey(addr)
  217. delete(cl.throttler, addrString)
  218. }
  219. // ApplyConfig atomically applies a config update to a connection limit handler
  220. func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
  221. cl.Lock()
  222. defer cl.Unlock()
  223. if cl.limiter == nil {
  224. cl.limiter = make(map[limiterKey]int)
  225. }
  226. if cl.throttler == nil {
  227. cl.throttler = make(map[limiterKey]ThrottleDetails)
  228. }
  229. cl.config = config
  230. }