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

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