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.

connection_throttling.go 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "fmt"
  6. "net"
  7. "time"
  8. "github.com/DanielOaks/girc-go/ircmsg"
  9. )
  10. // ThrottleDetails holds the connection-throttling details for a subnet/IP.
  11. type ThrottleDetails struct {
  12. Start time.Time
  13. ClientCount int
  14. }
  15. // ConnectionThrottle manages automated client connection throttling.
  16. type ConnectionThrottle struct {
  17. enabled bool
  18. ipv4Mask net.IPMask
  19. ipv6Mask net.IPMask
  20. subnetLimit int
  21. duration time.Duration
  22. population map[string]ThrottleDetails
  23. // used by the server to ban clients that go over this limit
  24. BanDuration time.Duration
  25. BanMessage string
  26. BanMessageBytes []byte
  27. // exemptedIPs holds IPs that are exempt from limits
  28. exemptedIPs map[string]bool
  29. // exemptedNets holds networks that are exempt from limits
  30. exemptedNets []net.IPNet
  31. }
  32. // maskAddr masks the given IPv4/6 address with our cidr limit masks.
  33. func (ct *ConnectionThrottle) maskAddr(addr net.IP) net.IP {
  34. if addr.To4() == nil {
  35. // IPv6 addr
  36. addr = addr.Mask(ct.ipv6Mask)
  37. } else {
  38. // IPv4 addr
  39. addr = addr.Mask(ct.ipv4Mask)
  40. }
  41. return addr
  42. }
  43. // ResetFor removes any existing count for the given address.
  44. func (ct *ConnectionThrottle) ResetFor(addr net.IP) {
  45. if !ct.enabled {
  46. return
  47. }
  48. // remove
  49. ct.maskAddr(addr)
  50. addrString := addr.String()
  51. delete(ct.population, addrString)
  52. }
  53. // AddClient introduces a new client connection if possible. If we can't, throws an error instead.
  54. func (ct *ConnectionThrottle) AddClient(addr net.IP) error {
  55. if !ct.enabled {
  56. return nil
  57. }
  58. // check exempted lists
  59. if ct.exemptedIPs[addr.String()] {
  60. return nil
  61. }
  62. for _, ex := range ct.exemptedNets {
  63. if ex.Contains(addr) {
  64. return nil
  65. }
  66. }
  67. // check throttle
  68. ct.maskAddr(addr)
  69. addrString := addr.String()
  70. details, exists := ct.population[addrString]
  71. if !exists || details.Start.Add(ct.duration).Before(time.Now()) {
  72. details = ThrottleDetails{
  73. Start: time.Now(),
  74. }
  75. }
  76. if details.ClientCount+1 > ct.subnetLimit {
  77. return errTooManyClients
  78. }
  79. details.ClientCount++
  80. ct.population[addrString] = details
  81. return nil
  82. }
  83. // NewConnectionThrottle returns a new client connection throttler.
  84. func NewConnectionThrottle(config ConnectionThrottleConfig) (*ConnectionThrottle, error) {
  85. var ct ConnectionThrottle
  86. ct.enabled = config.Enabled
  87. ct.population = make(map[string]ThrottleDetails)
  88. ct.exemptedIPs = make(map[string]bool)
  89. ct.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
  90. ct.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
  91. ct.subnetLimit = config.ConnectionsPerCidr
  92. ct.duration = config.Duration
  93. ct.BanDuration = config.BanDuration
  94. ct.BanMessage = config.BanMessage
  95. ircmsgOutput := ircmsg.MakeMessage(nil, "", "ERROR", ct.BanMessage)
  96. msg, err := ircmsgOutput.Line()
  97. if err != nil {
  98. return nil, fmt.Errorf("Could not make error message: %s", err.Error())
  99. }
  100. ct.BanMessageBytes = []byte(msg)
  101. // assemble exempted nets
  102. for _, cidr := range config.Exempted {
  103. ipaddr := net.ParseIP(cidr)
  104. _, netaddr, err := net.ParseCIDR(cidr)
  105. if ipaddr == nil && err != nil {
  106. return nil, fmt.Errorf("Could not parse exempted IP/network [%s]", cidr)
  107. }
  108. if ipaddr != nil {
  109. ct.exemptedIPs[ipaddr.String()] = true
  110. } else {
  111. ct.exemptedNets = append(ct.exemptedNets, *netaddr)
  112. }
  113. }
  114. return &ct, nil
  115. }