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.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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/oragono/oragono/irc/flatip"
  11. "github.com/oragono/oragono/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. maxConcurrent int
  26. maxPerWindow int
  27. nets []flatip.IPNet
  28. }
  29. type limiterKey struct {
  30. maskedIP flatip.IP
  31. prefixLen uint8 // 0 for the fake nets we generate for custom limits
  32. }
  33. // LimiterConfig controls the automated connection limits.
  34. // rawLimiterConfig contains all the YAML-visible fields;
  35. // LimiterConfig contains additional denormalized private fields
  36. type rawLimiterConfig struct {
  37. Count bool
  38. MaxConcurrent int `yaml:"max-concurrent-connections"`
  39. Throttle bool
  40. Window time.Duration
  41. MaxPerWindow int `yaml:"max-connections-per-window"`
  42. CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
  43. CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
  44. Exempted []string
  45. CustomLimits map[string]CustomLimitConfig `yaml:"custom-limits"`
  46. }
  47. type LimiterConfig struct {
  48. rawLimiterConfig
  49. exemptedNets []flatip.IPNet
  50. customLimits []customLimit
  51. }
  52. func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
  53. if err = unmarshal(&config.rawLimiterConfig); err != nil {
  54. return err
  55. }
  56. return config.postprocess()
  57. }
  58. func (config *LimiterConfig) postprocess() (err error) {
  59. exemptedNets, err := utils.ParseNetList(config.Exempted)
  60. if err != nil {
  61. return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
  62. }
  63. config.exemptedNets = make([]flatip.IPNet, len(exemptedNets))
  64. for i, exempted := range exemptedNets {
  65. config.exemptedNets[i] = flatip.FromNetIPNet(exempted)
  66. }
  67. for identifier, customLimitConf := range config.CustomLimits {
  68. nets := make([]flatip.IPNet, len(customLimitConf.Nets))
  69. for i, netStr := range customLimitConf.Nets {
  70. normalizedNet, err := flatip.ParseToNormalizedNet(netStr)
  71. if err != nil {
  72. return fmt.Errorf("Bad net %s in custom-limits block %s: %w", netStr, identifier, err)
  73. }
  74. nets[i] = normalizedNet
  75. }
  76. if len(customLimitConf.Nets) == 0 {
  77. // see #1421: this is the legacy config format where the
  78. // dictionary key of the block is a CIDR string
  79. normalizedNet, err := flatip.ParseToNormalizedNet(identifier)
  80. if err != nil {
  81. return fmt.Errorf("Custom limit block %s has no defined nets", identifier)
  82. }
  83. nets = []flatip.IPNet{normalizedNet}
  84. }
  85. config.customLimits = append(config.customLimits, customLimit{
  86. maxConcurrent: customLimitConf.MaxConcurrent,
  87. maxPerWindow: customLimitConf.MaxPerWindow,
  88. name: md5.Sum([]byte(identifier)),
  89. nets: nets,
  90. })
  91. }
  92. return nil
  93. }
  94. // Limiter manages the automated client connection limits.
  95. type Limiter struct {
  96. sync.Mutex
  97. config *LimiterConfig
  98. // IP/CIDR -> count of clients connected from there:
  99. limiter map[limiterKey]int
  100. // IP/CIDR -> throttle state:
  101. throttler map[limiterKey]ThrottleDetails
  102. }
  103. // addrToKey canonicalizes `addr` to a string key, and returns
  104. // the relevant connection limit and throttle max-per-window values
  105. func (cl *Limiter) addrToKey(addr flatip.IP) (key limiterKey, limit int, throttle int) {
  106. for _, custom := range cl.config.customLimits {
  107. for _, net := range custom.nets {
  108. if net.Contains(addr) {
  109. return limiterKey{maskedIP: custom.name, prefixLen: 0}, custom.maxConcurrent, custom.maxPerWindow
  110. }
  111. }
  112. }
  113. var prefixLen int
  114. if addr.IsIPv4() {
  115. prefixLen = cl.config.CidrLenIPv4
  116. addr = addr.Mask(prefixLen, 32)
  117. prefixLen += 96
  118. } else {
  119. prefixLen = cl.config.CidrLenIPv6
  120. addr = addr.Mask(prefixLen, 128)
  121. }
  122. return limiterKey{maskedIP: addr, prefixLen: uint8(prefixLen)}, cl.config.MaxConcurrent, cl.config.MaxPerWindow
  123. }
  124. // AddClient adds a client to our population if possible. If we can't, throws an error instead.
  125. func (cl *Limiter) AddClient(addr flatip.IP) error {
  126. cl.Lock()
  127. defer cl.Unlock()
  128. // we don't track populations for exempted addresses or nets - this is by design
  129. if flatip.IPInNets(addr, cl.config.exemptedNets) {
  130. return nil
  131. }
  132. addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
  133. // check limiter
  134. var count int
  135. if cl.config.Count {
  136. count = cl.limiter[addrString] + 1
  137. if count > maxConcurrent {
  138. return ErrLimitExceeded
  139. }
  140. }
  141. if cl.config.Throttle {
  142. details := cl.throttler[addrString] // retrieve mutable throttle state from the map
  143. // add in constant state to process the limiting operation
  144. g := GenericThrottle{
  145. ThrottleDetails: details,
  146. Duration: cl.config.Window,
  147. Limit: maxPerWindow,
  148. }
  149. throttled, _ := g.Touch() // actually check the limit
  150. cl.throttler[addrString] = g.ThrottleDetails // store modified mutable state
  151. if throttled {
  152. // back out the limiter add
  153. return ErrThrottleExceeded
  154. }
  155. }
  156. // success, record in limiter
  157. if cl.config.Count {
  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 flatip.IP) {
  164. cl.Lock()
  165. defer cl.Unlock()
  166. if !cl.config.Count || flatip.IPInNets(addr, cl.config.exemptedNets) {
  167. return
  168. }
  169. addrString, _, _ := cl.addrToKey(addr)
  170. count := cl.limiter[addrString]
  171. count -= 1
  172. if count < 0 {
  173. count = 0
  174. }
  175. cl.limiter[addrString] = count
  176. }
  177. type LimiterStatus struct {
  178. Exempt bool
  179. Count int
  180. MaxCount int
  181. Throttle int
  182. MaxPerWindow int
  183. ThrottleDuration time.Duration
  184. }
  185. func (cl *Limiter) Status(addr flatip.IP) (status LimiterStatus) {
  186. cl.Lock()
  187. defer cl.Unlock()
  188. if flatip.IPInNets(addr, cl.config.exemptedNets) {
  189. status.Exempt = true
  190. return
  191. }
  192. status.ThrottleDuration = cl.config.Window
  193. addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
  194. status.MaxCount = maxConcurrent
  195. status.MaxPerWindow = maxPerWindow
  196. status.Count = cl.limiter[addrString]
  197. status.Throttle = cl.throttler[addrString].Count
  198. return
  199. }
  200. // ResetThrottle resets the throttle count for an IP
  201. func (cl *Limiter) ResetThrottle(addr flatip.IP) {
  202. cl.Lock()
  203. defer cl.Unlock()
  204. if !cl.config.Throttle || flatip.IPInNets(addr, cl.config.exemptedNets) {
  205. return
  206. }
  207. addrString, _, _ := cl.addrToKey(addr)
  208. delete(cl.throttler, addrString)
  209. }
  210. // ApplyConfig atomically applies a config update to a connection limit handler
  211. func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
  212. cl.Lock()
  213. defer cl.Unlock()
  214. if cl.limiter == nil {
  215. cl.limiter = make(map[limiterKey]int)
  216. }
  217. if cl.throttler == nil {
  218. cl.throttler = make(map[limiterKey]ThrottleDetails)
  219. }
  220. cl.config = config
  221. }