Explorar el Código

fix #646

Includes a partially backwards-incompatible config change
tags/v2.0.0-rc1
Shivaram Lingamneni hace 4 años
padre
commit
4050b6571a

+ 5
- 16
irc/config.go Ver fichero

@@ -308,12 +308,11 @@ type Config struct {
308 308
 			forceTrailing      bool
309 309
 			SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"`
310 310
 		}
311
-		isupport            isupport.List
312
-		ConnectionLimiter   connection_limits.LimiterConfig   `yaml:"connection-limits"`
313
-		ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
314
-		Cloaks              cloaks.CloakConfig                `yaml:"ip-cloaking"`
315
-		supportedCaps       *caps.Set
316
-		capValues           caps.Values
311
+		isupport      isupport.List
312
+		IPLimits      connection_limits.LimiterConfig `yaml:"ip-limits"`
313
+		Cloaks        cloaks.CloakConfig              `yaml:"ip-cloaking"`
314
+		supportedCaps *caps.Set
315
+		capValues     caps.Values
317 316
 	}
318 317
 
319 318
 	Languages struct {
@@ -633,16 +632,6 @@ func LoadConfig(filename string) (config *Config, err error) {
633 632
 	// set this even if STS is disabled
634 633
 	config.Server.capValues[caps.STS] = config.Server.STS.Value()
635 634
 
636
-	if config.Server.ConnectionThrottler.Enabled {
637
-		config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString)
638
-		if err != nil {
639
-			return nil, fmt.Errorf("Could not parse connection-throttle duration: %s", err.Error())
640
-		}
641
-		config.Server.ConnectionThrottler.BanDuration, err = time.ParseDuration(config.Server.ConnectionThrottler.BanDurationString)
642
-		if err != nil {
643
-			return nil, fmt.Errorf("Could not parse connection-throttle ban-duration: %s", err.Error())
644
-		}
645
-	}
646 635
 	// process webirc blocks
647 636
 	var newWebIRC []webircConfig
648 637
 	for _, webirc := range config.Server.WebIRC {

+ 150
- 60
irc/connection_limits/limiter.go Ver fichero

@@ -8,69 +8,161 @@ import (
8 8
 	"fmt"
9 9
 	"net"
10 10
 	"sync"
11
+	"time"
11 12
 
12 13
 	"github.com/oragono/oragono/irc/utils"
13 14
 )
14 15
 
16
+var (
17
+	ErrLimitExceeded    = errors.New("too many concurrent connections")
18
+	ErrThrottleExceeded = errors.New("too many recent connection attempts")
19
+)
20
+
21
+type CustomLimitConfig struct {
22
+	MaxConcurrent int `yaml:"max-concurrent-connections"`
23
+	MaxPerWindow  int `yaml:"max-connections-per-window"`
24
+}
25
+
26
+// tuples the key-value pair of a CIDR and its custom limit/throttle values
27
+type customLimit struct {
28
+	CustomLimitConfig
29
+	ipNet net.IPNet
30
+}
31
+
15 32
 // LimiterConfig controls the automated connection limits.
33
+// RawLimiterConfig contains all the YAML-visible fields;
34
+// LimiterConfig contains additional denormalized private fields
35
+type RawLimiterConfig struct {
36
+	Limit         bool
37
+	MaxConcurrent int `yaml:"max-concurrent-connections"`
38
+
39
+	Throttle     bool
40
+	Window       time.Duration
41
+	MaxPerWindow int           `yaml:"max-connections-per-window"`
42
+	BanDuration  time.Duration `yaml:"throttle-ban-duration"`
43
+
44
+	CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
45
+	CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
46
+
47
+	Exempted []string
48
+
49
+	CustomLimits map[string]CustomLimitConfig `yaml:"custom-limits"`
50
+}
51
+
16 52
 type LimiterConfig struct {
17
-	Enabled        bool
18
-	CidrLenIPv4    int `yaml:"cidr-len-ipv4"`
19
-	CidrLenIPv6    int `yaml:"cidr-len-ipv6"`
20
-	ConnsPerSubnet int `yaml:"connections-per-subnet"`
21
-	IPsPerSubnet   int `yaml:"ips-per-subnet"` // legacy name for ConnsPerSubnet
22
-	Exempted       []string
53
+	RawLimiterConfig
54
+
55
+	ipv4Mask     net.IPMask
56
+	ipv6Mask     net.IPMask
57
+	exemptedNets []net.IPNet
58
+	customLimits []customLimit
23 59
 }
24 60
 
25
-var (
26
-	errTooManyClients = errors.New("Too many clients in subnet")
27
-)
61
+func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
62
+	if err = unmarshal(&config.RawLimiterConfig); err != nil {
63
+		return err
64
+	}
65
+	return config.postprocess()
66
+}
67
+
68
+func (config *LimiterConfig) postprocess() (err error) {
69
+	config.exemptedNets, err = utils.ParseNetList(config.Exempted)
70
+	if err != nil {
71
+		return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
72
+	}
73
+
74
+	for netStr, customLimitConf := range config.CustomLimits {
75
+		normalizedNet, err := utils.NormalizedNetFromString(netStr)
76
+		if err != nil {
77
+			return fmt.Errorf("Could not parse custom limit specification: %v", err.Error())
78
+		}
79
+		config.customLimits = append(config.customLimits, customLimit{
80
+			CustomLimitConfig: customLimitConf,
81
+			ipNet:             normalizedNet,
82
+		})
83
+	}
84
+
85
+	config.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
86
+	config.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
87
+
88
+	return nil
89
+}
28 90
 
29 91
 // Limiter manages the automated client connection limits.
30 92
 type Limiter struct {
31 93
 	sync.Mutex
32 94
 
33
-	enabled  bool
34
-	ipv4Mask net.IPMask
35
-	ipv6Mask net.IPMask
36
-	// subnetLimit is the maximum number of clients per subnet
37
-	subnetLimit int
38
-	// population holds IP -> count of clients connected from there
39
-	population map[string]int
95
+	config *LimiterConfig
40 96
 
41
-	// exemptedNets holds networks that are exempt from limits
42
-	exemptedNets []net.IPNet
97
+	// IP/CIDR -> count of clients connected from there:
98
+	limiter map[string]int
99
+	// IP/CIDR -> throttle state:
100
+	throttler map[string]ThrottleDetails
43 101
 }
44 102
 
45
-// addrToKey canonicalizes `addr` to a string key.
46
-func addrToKey(addr net.IP, v4Mask net.IPMask, v6Mask net.IPMask) string {
47
-	if addr.To4() != nil {
48
-		addr = addr.Mask(v4Mask) // IP.Mask() handles the 4-in-6 mapping for us
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 net.IP) (key string, limit int, throttle int) {
106
+	// `key` will be a CIDR string like "8.8.8.8/32" or "2001:0db8::/32"
107
+	for _, custom := range cl.config.customLimits {
108
+		if custom.ipNet.Contains(addr) {
109
+			return custom.ipNet.String(), custom.MaxConcurrent, custom.MaxPerWindow
110
+		}
111
+	}
112
+
113
+	var ipNet net.IPNet
114
+	addrv4 := addr.To4()
115
+	if addrv4 != nil {
116
+		ipNet = net.IPNet{
117
+			IP:   addrv4.Mask(cl.config.ipv4Mask),
118
+			Mask: cl.config.ipv4Mask,
119
+		}
49 120
 	} else {
50
-		addr = addr.Mask(v6Mask)
121
+		ipNet = net.IPNet{
122
+			IP:   addr.Mask(cl.config.ipv6Mask),
123
+			Mask: cl.config.ipv6Mask,
124
+		}
51 125
 	}
52
-	return addr.String()
126
+	return ipNet.String(), cl.config.MaxConcurrent, cl.config.MaxPerWindow
53 127
 }
54 128
 
55 129
 // AddClient adds a client to our population if possible. If we can't, throws an error instead.
56
-// 'force' is used to add already-existing clients (i.e. ones that are already on the network).
57
-func (cl *Limiter) AddClient(addr net.IP, force bool) error {
130
+func (cl *Limiter) AddClient(addr net.IP) error {
58 131
 	cl.Lock()
59 132
 	defer cl.Unlock()
60 133
 
61 134
 	// we don't track populations for exempted addresses or nets - this is by design
62
-	if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) {
135
+	if utils.IPInNets(addr, cl.config.exemptedNets) {
63 136
 		return nil
64 137
 	}
65 138
 
66
-	// check population
67
-	addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
68
-
69
-	if cl.population[addrString]+1 > cl.subnetLimit && !force {
70
-		return errTooManyClients
139
+	addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
140
+
141
+	// XXX check throttle first; if we checked limit first and then checked throttle,
142
+	// we'd have to decrement the limit on an unsuccessful throttle check
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
+			return ErrThrottleExceeded
155
+		}
71 156
 	}
72 157
 
73
-	cl.population[addrString] = cl.population[addrString] + 1
158
+	// now check limiter
159
+	if cl.config.Limit {
160
+		count := cl.limiter[addrString] + 1
161
+		if count > maxConcurrent {
162
+			return ErrLimitExceeded
163
+		}
164
+		cl.limiter[addrString] = count
165
+	}
74 166
 
75 167
 	return nil
76 168
 }
@@ -80,45 +172,43 @@ func (cl *Limiter) RemoveClient(addr net.IP) {
80 172
 	cl.Lock()
81 173
 	defer cl.Unlock()
82 174
 
83
-	if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) {
175
+	if !cl.config.Limit || utils.IPInNets(addr, cl.config.exemptedNets) {
84 176
 		return
85 177
 	}
86 178
 
87
-	addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
88
-	cl.population[addrString] = cl.population[addrString] - 1
89
-
90
-	// safety limiter
91
-	if cl.population[addrString] < 0 {
92
-		cl.population[addrString] = 0
179
+	addrString, _, _ := cl.addrToKey(addr)
180
+	count := cl.limiter[addrString]
181
+	count -= 1
182
+	if count < 0 {
183
+		count = 0
93 184
 	}
185
+	cl.limiter[addrString] = count
94 186
 }
95 187
 
96
-// ApplyConfig atomically applies a config update to a connection limit handler
97
-func (cl *Limiter) ApplyConfig(config LimiterConfig) error {
98
-	// assemble exempted nets
99
-	exemptedNets, err := utils.ParseNetList(config.Exempted)
100
-	if err != nil {
101
-		return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
188
+// ResetThrottle resets the throttle count for an IP
189
+func (cl *Limiter) ResetThrottle(addr net.IP) {
190
+	cl.Lock()
191
+	defer cl.Unlock()
192
+
193
+	if !cl.config.Throttle || utils.IPInNets(addr, cl.config.exemptedNets) {
194
+		return
102 195
 	}
103 196
 
197
+	addrString, _, _ := cl.addrToKey(addr)
198
+	delete(cl.throttler, addrString)
199
+}
200
+
201
+// ApplyConfig atomically applies a config update to a connection limit handler
202
+func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
104 203
 	cl.Lock()
105 204
 	defer cl.Unlock()
106 205
 
107
-	if cl.population == nil {
108
-		cl.population = make(map[string]int)
206
+	if cl.limiter == nil {
207
+		cl.limiter = make(map[string]int)
109 208
 	}
110
-
111
-	cl.enabled = config.Enabled
112
-	cl.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
113
-	cl.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
114
-	// subnetLimit is explicitly NOT capped at a minimum of one.
115
-	// this is so that CL config can be used to allow ONLY clients from exempted IPs/nets
116
-	cl.subnetLimit = config.ConnsPerSubnet
117
-	// but: check if the current key was left unset, but the legacy was set:
118
-	if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 {
119
-		cl.subnetLimit = config.IPsPerSubnet
209
+	if cl.throttler == nil {
210
+		cl.throttler = make(map[string]ThrottleDetails)
120 211
 	}
121
-	cl.exemptedNets = exemptedNets
122 212
 
123
-	return nil
213
+	cl.config = config
124 214
 }

+ 87
- 0
irc/connection_limits/limiter_test.go Ver fichero

@@ -0,0 +1,87 @@
1
+// Copyright (c) 2018 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package connection_limits
5
+
6
+import (
7
+	"net"
8
+	"testing"
9
+	"time"
10
+)
11
+
12
+func easyParseIP(ipstr string) (result net.IP) {
13
+	result = net.ParseIP(ipstr)
14
+	if result == nil {
15
+		panic(ipstr)
16
+	}
17
+	return
18
+}
19
+
20
+var baseConfig = LimiterConfig{
21
+	RawLimiterConfig: RawLimiterConfig{
22
+		Limit:         true,
23
+		MaxConcurrent: 4,
24
+
25
+		Throttle:     true,
26
+		Window:       time.Second * 600,
27
+		MaxPerWindow: 8,
28
+
29
+		CidrLenIPv4: 32,
30
+		CidrLenIPv6: 64,
31
+
32
+		Exempted: []string{"localhost"},
33
+
34
+		CustomLimits: map[string]CustomLimitConfig{
35
+			"8.8.0.0/16": {
36
+				MaxConcurrent: 128,
37
+				MaxPerWindow:  256,
38
+			},
39
+		},
40
+	},
41
+}
42
+
43
+func TestKeying(t *testing.T) {
44
+	config := baseConfig
45
+	config.postprocess()
46
+	var limiter Limiter
47
+	limiter.ApplyConfig(&config)
48
+
49
+	key, maxConc, maxWin := limiter.addrToKey(easyParseIP("1.1.1.1"))
50
+	assertEqual(key, "1.1.1.1/32", t)
51
+	assertEqual(maxConc, 4, t)
52
+	assertEqual(maxWin, 8, t)
53
+
54
+	key, maxConc, maxWin = limiter.addrToKey(easyParseIP("2607:5301:201:3100::7426"))
55
+	assertEqual(key, "2607:5301:201:3100::/64", t)
56
+	assertEqual(maxConc, 4, t)
57
+	assertEqual(maxWin, 8, t)
58
+
59
+	key, maxConc, maxWin = limiter.addrToKey(easyParseIP("8.8.4.4"))
60
+	assertEqual(key, "8.8.0.0/16", t)
61
+	assertEqual(maxConc, 128, t)
62
+	assertEqual(maxWin, 256, t)
63
+}
64
+
65
+func TestLimits(t *testing.T) {
66
+	regularIP := easyParseIP("2607:5301:201:3100::7426")
67
+	config := baseConfig
68
+	config.postprocess()
69
+	var limiter Limiter
70
+	limiter.ApplyConfig(&config)
71
+
72
+	for i := 0; i < 4; i++ {
73
+		err := limiter.AddClient(regularIP)
74
+		if err != nil {
75
+			t.Errorf("ip should not be blocked, but %v", err)
76
+		}
77
+	}
78
+	err := limiter.AddClient(regularIP)
79
+	if err != ErrLimitExceeded {
80
+		t.Errorf("ip should be blocked, but %v", err)
81
+	}
82
+	limiter.RemoveClient(regularIP)
83
+	err = limiter.AddClient(regularIP)
84
+	if err != nil {
85
+		t.Errorf("ip should not be blocked, but %v", err)
86
+	}
87
+}

+ 0
- 127
irc/connection_limits/throttler.go Ver fichero

@@ -4,28 +4,9 @@
4 4
 package connection_limits
5 5
 
6 6
 import (
7
-	"fmt"
8
-	"net"
9
-	"sync"
10 7
 	"time"
11
-
12
-	"github.com/oragono/oragono/irc/utils"
13 8
 )
14 9
 
15
-// ThrottlerConfig controls the automated connection throttling.
16
-type ThrottlerConfig struct {
17
-	Enabled            bool
18
-	CidrLenIPv4        int           `yaml:"cidr-len-ipv4"`
19
-	CidrLenIPv6        int           `yaml:"cidr-len-ipv6"`
20
-	ConnectionsPerCidr int           `yaml:"max-connections"`
21
-	DurationString     string        `yaml:"duration"`
22
-	Duration           time.Duration `yaml:"duration-time"`
23
-	BanDurationString  string        `yaml:"ban-duration"`
24
-	BanDuration        time.Duration
25
-	BanMessage         string `yaml:"ban-message"`
26
-	Exempted           []string
27
-}
28
-
29 10
 // ThrottleDetails holds the connection-throttling details for a subnet/IP.
30 11
 type ThrottleDetails struct {
31 12
 	Start time.Time
@@ -68,111 +49,3 @@ func (g *GenericThrottle) touch(now time.Time) (throttled bool, remainingTime ti
68 49
 		return false, 0
69 50
 	}
70 51
 }
71
-
72
-// Throttler manages automated client connection throttling.
73
-type Throttler struct {
74
-	sync.RWMutex
75
-
76
-	enabled     bool
77
-	ipv4Mask    net.IPMask
78
-	ipv6Mask    net.IPMask
79
-	subnetLimit int
80
-	duration    time.Duration
81
-	population  map[string]ThrottleDetails
82
-
83
-	// used by the server to ban clients that go over this limit
84
-	banDuration time.Duration
85
-	banMessage  string
86
-
87
-	// exemptedNets holds networks that are exempt from limits
88
-	exemptedNets []net.IPNet
89
-}
90
-
91
-// ResetFor removes any existing count for the given address.
92
-func (ct *Throttler) ResetFor(addr net.IP) {
93
-	ct.Lock()
94
-	defer ct.Unlock()
95
-
96
-	if !ct.enabled {
97
-		return
98
-	}
99
-
100
-	// remove
101
-	addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask)
102
-	delete(ct.population, addrString)
103
-}
104
-
105
-// AddClient introduces a new client connection if possible. If we can't, throws an error instead.
106
-func (ct *Throttler) AddClient(addr net.IP) error {
107
-	ct.Lock()
108
-	defer ct.Unlock()
109
-
110
-	if !ct.enabled {
111
-		return nil
112
-	}
113
-
114
-	// check exempted lists
115
-	if utils.IPInNets(addr, ct.exemptedNets) {
116
-		return nil
117
-	}
118
-
119
-	// check throttle
120
-	addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask)
121
-
122
-	details := ct.population[addrString] // retrieve mutable throttle state from the map
123
-	// add in constant state to process the limiting operation
124
-	g := GenericThrottle{
125
-		ThrottleDetails: details,
126
-		Duration:        ct.duration,
127
-		Limit:           ct.subnetLimit,
128
-	}
129
-	throttled, _ := g.Touch()                     // actually check the limit
130
-	ct.population[addrString] = g.ThrottleDetails // store modified mutable state
131
-
132
-	if throttled {
133
-		return errTooManyClients
134
-	} else {
135
-		return nil
136
-	}
137
-}
138
-
139
-func (ct *Throttler) BanDuration() time.Duration {
140
-	ct.RLock()
141
-	defer ct.RUnlock()
142
-
143
-	return ct.banDuration
144
-}
145
-
146
-func (ct *Throttler) BanMessage() string {
147
-	ct.RLock()
148
-	defer ct.RUnlock()
149
-
150
-	return ct.banMessage
151
-}
152
-
153
-// ApplyConfig atomically applies a config update to a throttler
154
-func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error {
155
-	// assemble exempted nets
156
-	exemptedNets, err := utils.ParseNetList(config.Exempted)
157
-	if err != nil {
158
-		return fmt.Errorf("Could not parse throttle exemption list: %v", err.Error())
159
-	}
160
-
161
-	ct.Lock()
162
-	defer ct.Unlock()
163
-
164
-	if ct.population == nil {
165
-		ct.population = make(map[string]ThrottleDetails)
166
-	}
167
-
168
-	ct.enabled = config.Enabled
169
-	ct.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
170
-	ct.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
171
-	ct.subnetLimit = config.ConnectionsPerCidr
172
-	ct.duration = config.Duration
173
-	ct.banDuration = config.BanDuration
174
-	ct.banMessage = config.BanMessage
175
-	ct.exemptedNets = exemptedNets
176
-
177
-	return nil
178
-}

+ 17
- 13
irc/connection_limits/throttler_test.go Ver fichero

@@ -62,19 +62,23 @@ func TestGenericThrottleDisabled(t *testing.T) {
62 62
 	}
63 63
 }
64 64
 
65
-func makeTestThrottler(v4len, v6len int) *Throttler {
65
+func makeTestThrottler(v4len, v6len int) *Limiter {
66 66
 	minute, _ := time.ParseDuration("1m")
67 67
 	maxConnections := 3
68
-	config := ThrottlerConfig{
69
-		Enabled:            true,
70
-		CidrLenIPv4:        v4len,
71
-		CidrLenIPv6:        v6len,
72
-		ConnectionsPerCidr: maxConnections,
73
-		Duration:           minute,
68
+	config := LimiterConfig{
69
+		RawLimiterConfig: RawLimiterConfig{
70
+			Limit:        false,
71
+			Throttle:     true,
72
+			CidrLenIPv4:  v4len,
73
+			CidrLenIPv6:  v6len,
74
+			MaxPerWindow: maxConnections,
75
+			Window:       minute,
76
+		},
74 77
 	}
75
-	var throttler Throttler
76
-	throttler.ApplyConfig(config)
77
-	return &throttler
78
+	config.postprocess()
79
+	var limiter Limiter
80
+	limiter.ApplyConfig(&config)
81
+	return &limiter
78 82
 }
79 83
 
80 84
 func TestConnectionThrottle(t *testing.T) {
@@ -86,7 +90,7 @@ func TestConnectionThrottle(t *testing.T) {
86 90
 		assertEqual(err, nil, t)
87 91
 	}
88 92
 	err := throttler.AddClient(addr)
89
-	assertEqual(err, errTooManyClients, t)
93
+	assertEqual(err, ErrThrottleExceeded, t)
90 94
 }
91 95
 
92 96
 func TestConnectionThrottleIPv6(t *testing.T) {
@@ -101,7 +105,7 @@ func TestConnectionThrottleIPv6(t *testing.T) {
101 105
 	assertEqual(err, nil, t)
102 106
 
103 107
 	err = throttler.AddClient(net.ParseIP("2001:0db8::4"))
104
-	assertEqual(err, errTooManyClients, t)
108
+	assertEqual(err, ErrThrottleExceeded, t)
105 109
 }
106 110
 
107 111
 func TestConnectionThrottleIPv4(t *testing.T) {
@@ -116,5 +120,5 @@ func TestConnectionThrottleIPv4(t *testing.T) {
116 120
 	assertEqual(err, nil, t)
117 121
 
118 122
 	err = throttler.AddClient(net.ParseIP("192.168.1.104"))
119
-	assertEqual(err, errTooManyClients, t)
123
+	assertEqual(err, ErrThrottleExceeded, t)
120 124
 }

+ 0
- 6
irc/connection_limits/tor.go Ver fichero

@@ -4,16 +4,10 @@
4 4
 package connection_limits
5 5
 
6 6
 import (
7
-	"errors"
8 7
 	"sync"
9 8
 	"time"
10 9
 )
11 10
 
12
-var (
13
-	ErrLimitExceeded    = errors.New("too many concurrent connections")
14
-	ErrThrottleExceeded = errors.New("too many recent connection attempts")
15
-)
16
-
17 11
 // TorLimiter is a combined limiter and throttler for use on connections
18 12
 // proxied from a Tor hidden service (so we don't have meaningful IPs,
19 13
 // a notion of CIDR width, etc.)

+ 39
- 50
irc/server.go Ver fichero

@@ -46,6 +46,8 @@ var (
46 46
 	// we only have standard channels for now. TODO: any updates to this
47 47
 	// will also need to be reflected in CasefoldChannel
48 48
 	chanTypes = "#"
49
+
50
+	throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect."
49 51
 )
50 52
 
51 53
 // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
@@ -59,34 +61,33 @@ type ListenerWrapper struct {
59 61
 
60 62
 // Server is the main Oragono server.
61 63
 type Server struct {
62
-	accounts            AccountManager
63
-	channels            ChannelManager
64
-	channelRegistry     ChannelRegistry
65
-	clients             ClientManager
66
-	config              unsafe.Pointer
67
-	configFilename      string
68
-	connectionLimiter   connection_limits.Limiter
69
-	connectionThrottler connection_limits.Throttler
70
-	ctime               time.Time
71
-	dlines              *DLineManager
72
-	helpIndexManager    HelpIndexManager
73
-	klines              *KLineManager
74
-	listeners           map[string]*ListenerWrapper
75
-	logger              *logger.Manager
76
-	monitorManager      MonitorManager
77
-	name                string
78
-	nameCasefolded      string
79
-	rehashMutex         sync.Mutex // tier 4
80
-	rehashSignal        chan os.Signal
81
-	pprofServer         *http.Server
82
-	resumeManager       ResumeManager
83
-	signals             chan os.Signal
84
-	snomasks            SnoManager
85
-	store               *buntdb.DB
86
-	torLimiter          connection_limits.TorLimiter
87
-	whoWas              WhoWasList
88
-	stats               Stats
89
-	semaphores          ServerSemaphores
64
+	accounts          AccountManager
65
+	channels          ChannelManager
66
+	channelRegistry   ChannelRegistry
67
+	clients           ClientManager
68
+	config            unsafe.Pointer
69
+	configFilename    string
70
+	connectionLimiter connection_limits.Limiter
71
+	ctime             time.Time
72
+	dlines            *DLineManager
73
+	helpIndexManager  HelpIndexManager
74
+	klines            *KLineManager
75
+	listeners         map[string]*ListenerWrapper
76
+	logger            *logger.Manager
77
+	monitorManager    MonitorManager
78
+	name              string
79
+	nameCasefolded    string
80
+	rehashMutex       sync.Mutex // tier 4
81
+	rehashSignal      chan os.Signal
82
+	pprofServer       *http.Server
83
+	resumeManager     ResumeManager
84
+	signals           chan os.Signal
85
+	snomasks          SnoManager
86
+	store             *buntdb.DB
87
+	torLimiter        connection_limits.TorLimiter
88
+	whoWas            WhoWasList
89
+	stats             Stats
90
+	semaphores        ServerSemaphores
90 91
 }
91 92
 
92 93
 var (
@@ -214,32 +215,28 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
214 215
 	}
215 216
 
216 217
 	// check connection limits
217
-	err := server.connectionLimiter.AddClient(ipaddr, false)
218
-	if err != nil {
218
+	err := server.connectionLimiter.AddClient(ipaddr)
219
+	if err == connection_limits.ErrLimitExceeded {
219 220
 		// too many connections from one client, tell the client and close the connection
220 221
 		server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr))
221 222
 		return true, "Too many clients from your network"
222
-	}
223
-
224
-	// check connection throttle
225
-	err = server.connectionThrottler.AddClient(ipaddr)
226
-	if err != nil {
227
-		// too many connections too quickly from client, tell them and close the connection
228
-		duration := server.connectionThrottler.BanDuration()
223
+	} else if err == connection_limits.ErrThrottleExceeded {
224
+		duration := server.Config().Server.IPLimits.BanDuration
229 225
 		if duration == 0 {
230 226
 			return false, ""
231 227
 		}
232
-		server.dlines.AddIP(ipaddr, duration, server.connectionThrottler.BanMessage(), "Exceeded automated connection throttle", "auto.connection.throttler")
233
-
228
+		server.dlines.AddIP(ipaddr, duration, throttleMessage, "Exceeded automated connection throttle", "auto.connection.throttler")
234 229
 		// they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now,
235 230
 		// and once their temporary DLINE is finished they can fill up the throttler again
236
-		server.connectionThrottler.ResetFor(ipaddr)
231
+		server.connectionLimiter.ResetThrottle(ipaddr)
237 232
 
238 233
 		// this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us
239 234
 		server.logger.Info(
240 235
 			"localconnect-ip",
241 236
 			fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration))
242
-		return true, server.connectionThrottler.BanMessage()
237
+		return true, throttleMessage
238
+	} else if err != nil {
239
+		server.logger.Warning("internal", "unexpected ban result", err.Error())
243 240
 	}
244 241
 
245 242
 	return false, ""
@@ -604,15 +601,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
604 601
 
605 602
 	// first, reload config sections for functionality implemented in subpackages:
606 603
 
607
-	err = server.connectionLimiter.ApplyConfig(config.Server.ConnectionLimiter)
608
-	if err != nil {
609
-		return err
610
-	}
611
-
612
-	err = server.connectionThrottler.ApplyConfig(config.Server.ConnectionThrottler)
613
-	if err != nil {
614
-		return err
615
-	}
604
+	server.connectionLimiter.ApplyConfig(&config.Server.IPLimits)
616 605
 
617 606
 	tlConf := &config.Server.TorListeners
618 607
 	server.torLimiter.Configure(tlConf.MaxConnections, tlConf.ThrottleDuration, tlConf.MaxConnectionsPerDuration)

+ 29
- 42
oragono.yaml Ver fichero

@@ -144,53 +144,42 @@ server:
144 144
         # this works around that bug, allowing them to use SASL.
145 145
         send-unprefixed-sasl: true
146 146
 
147
-    # maximum number of connections per subnet
148
-    connection-limits:
149
-        # whether to enforce connection limits or not
150
-        enabled: true
147
+    # IP-based DoS protection
148
+    ip-limits:
149
+        # whether to enforce limits on the total number of concurrent connections per IP / CIDR
150
+        limit: true
151
+        # maximum concurrent connections per subnet
152
+        max-concurrent-connections: 16
153
+
154
+        # whether to restrict the rate of new connections per IP / CIDR
155
+        throttle: true
156
+        # how long to keep track of connections for
157
+        window: 10m
158
+        # maximum number of connections, per subnet, within the given duration
159
+        max-connections-per-window: 32
160
+        # how long to ban offenders for
161
+        # after banning them, the number of connections is reset (which lets you use UNDLINE to unban people)
162
+        throttle-ban-duration: 10m
151 163
 
152
-        # how wide the cidr should be for IPv4
164
+        # how wide the CIDR should be for IPv4 (a /32 is a fully specified IPv4 address)
153 165
         cidr-len-ipv4: 32
154
-
155
-        # how wide the cidr should be for IPv6
166
+        # how wide the cidr should be for IPv6 (a /64 is the typical prefix assigned
167
+        # by an ISP to an individual customer for their LAN)
156 168
         cidr-len-ipv6: 64
157 169
 
158
-        # maximum concurrent connections per subnet (defined above by the cidr length)
159
-        connections-per-subnet: 16
160
-
161 170
         # IPs/networks which are exempted from connection limits
162 171
         exempted:
163 172
             - "localhost"
164 173
             # - "192.168.1.1"
165 174
             # - "2001:0db8::/32"
166 175
 
167
-    # automated connection throttling
168
-    connection-throttling:
169
-        # whether to throttle connections or not
170
-        enabled: true
171
-
172
-        # how wide the cidr should be for IPv4
173
-        cidr-len-ipv4: 32
174
-
175
-        # how wide the cidr should be for IPv6
176
-        cidr-len-ipv6: 64
177
-
178
-        # how long to keep track of connections for
179
-        duration: 10m
180
-
181
-        # maximum number of connections, per subnet, within the given duration
182
-        max-connections: 32
183
-
184
-        # how long to ban offenders for, and the message to use
185
-        # after banning them, the number of connections is reset (which lets you use UNDLINE to unban people)
186
-        ban-duration: 10m
187
-        ban-message: You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect.
188
-
189
-        # IPs/networks which are exempted from connection throttling
190
-        exempted:
191
-            - "localhost"
192
-            # - "192.168.1.1"
193
-            # - "2001:0db8::/32"
176
+        # custom connection limits for certain IPs/networks. Note that CIDR
177
+        # widths defined here override the default CIDR width --- the limit
178
+        # will apply to the entire CIDR no matter how large or small it is
179
+        custom-limits:
180
+            # "8.8.0.0/16":
181
+            #     max-concurrent-connections: 128
182
+            #     max-connections-per-window: 1024
194 183
 
195 184
     # IP cloaking hides users' IP addresses from other users and from channel admins
196 185
     # (but not from server admins), while still allowing channel admins to ban
@@ -214,13 +203,11 @@ server:
214 203
 
215 204
         # the cloaked hostname is derived only from the CIDR (most significant bits
216 205
         # of the IP address), up to a configurable number of bits. this is the
217
-        # granularity at which bans will take effect for ipv4 (a /32 is a fully
218
-        # specified IP address). note that changing this value will invalidate
219
-        # any stored bans.
206
+        # granularity at which bans will take effect for IPv4. Note that changing
207
+        # this value will invalidate any stored bans.
220 208
         cidr-len-ipv4: 32
221 209
 
222
-        # analogous value for ipv6 (an ipv6 /64 is the typical prefix assigned
223
-        # by an ISP to an individual customer for their LAN)
210
+        # analogous granularity for IPv6
224 211
         cidr-len-ipv6: 64
225 212
 
226 213
         # number of bits of hash output to include in the cloaked hostname.

Loading…
Cancelar
Guardar