Skip to content

Commit 3986229

Browse files
committed
Add rate limiting capabilities
1 parent c6485a6 commit 3986229

File tree

12 files changed

+576
-48
lines changed

12 files changed

+576
-48
lines changed

config_example.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ keys:
33
port: 9000
44
cipher: chacha20-ietf-poly1305
55
secret: Secret0
6+
rate_limit: 128000
67

78
- id: user-1
89
port: 9000
910
cipher: chacha20-ietf-poly1305
1011
secret: Secret1
12+
rate_limit: 128000
1113

1214
- id: user-2
1315
port: 9001
1416
cipher: chacha20-ietf-poly1305
1517
secret: Secret2
18+
rate_limit: 128000

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/prometheus/procfs v0.1.3 // indirect
2424
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
2525
golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect
26+
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
2627
google.golang.org/protobuf v1.23.0 // indirect
2728
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
2829
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
110110
golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4=
111111
golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
112112
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
113+
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
114+
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
113115
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
114116
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
115117
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

integration_test/integration_test.go

Lines changed: 198 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ func startUDPEchoServer(t testing.TB) (*net.UDPConn, *sync.WaitGroup) {
9797
return conn, &running
9898
}
9999

100+
func makeLimiter(cipherList service.CipherList) service.TrafficLimiter {
101+
c := service.MakeTestTrafficLimiterConfig(cipherList)
102+
return service.NewTrafficLimiter(&c)
103+
}
104+
100105
func TestTCPEcho(t *testing.T) {
101106
echoListener, echoRunning := startTCPEchoServer(t)
102107

@@ -111,7 +116,7 @@ func TestTCPEcho(t *testing.T) {
111116
}
112117
replayCache := service.NewReplayCache(5)
113118
const testTimeout = 200 * time.Millisecond
114-
proxy := service.NewTCPService(cipherList, &replayCache, &metrics.NoOpMetrics{}, testTimeout)
119+
proxy := service.NewTCPService(cipherList, &replayCache, &metrics.NoOpMetrics{}, testTimeout, makeLimiter(cipherList))
115120
proxy.SetTargetIPValidator(allowAll)
116121
go proxy.Serve(proxyListener)
117122

@@ -164,6 +169,192 @@ func TestTCPEcho(t *testing.T) {
164169
echoRunning.Wait()
165170
}
166171

172+
func TestTrafficLimiterTCP(t *testing.T) {
173+
echoListener, echoRunning := startTCPEchoServer(t)
174+
175+
proxyListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
176+
if err != nil {
177+
t.Fatalf("ListenTCP failed: %v", err)
178+
}
179+
secrets := ss.MakeTestSecrets(1)
180+
cipherList, err := service.MakeTestCiphers(secrets)
181+
if err != nil {
182+
t.Fatal(err)
183+
}
184+
replayCache := service.NewReplayCache(5)
185+
const testTimeout = 5 * time.Second
186+
187+
key := cipherList.SnapshotForClientIP(net.IP{})[0].Value.(*service.CipherEntry).ID
188+
trafficLimiter := service.NewTrafficLimiter(&service.TrafficLimiterConfig{
189+
KeyToRateLimit: map[string]int{
190+
key: 1000,
191+
},
192+
})
193+
194+
proxy := service.NewTCPService(cipherList, &replayCache, &metrics.NoOpMetrics{}, testTimeout, trafficLimiter)
195+
proxy.SetTargetIPValidator(allowAll)
196+
go proxy.Serve(proxyListener)
197+
198+
proxyHost, proxyPort, err := net.SplitHostPort(proxyListener.Addr().String())
199+
if err != nil {
200+
t.Fatal(err)
201+
}
202+
portNum, err := strconv.Atoi(proxyPort)
203+
if err != nil {
204+
t.Fatal(err)
205+
}
206+
client, err := client.NewClient(proxyHost, portNum, secrets[0], ss.TestCipher)
207+
if err != nil {
208+
t.Fatalf("Failed to create ShadowsocksClient: %v", err)
209+
}
210+
211+
doWriteRead := func(N int, repeats int) time.Duration {
212+
up := ss.MakeTestPayload(N)
213+
conn, err := client.DialTCP(nil, echoListener.Addr().String())
214+
defer conn.Close()
215+
if err != nil {
216+
t.Fatalf("ShadowsocksClient.DialTCP failed: %v", err)
217+
}
218+
start := time.Now()
219+
down := make([]byte, N)
220+
221+
for i := 0; i < repeats; i++ {
222+
n, err := conn.Write(up)
223+
if err != nil {
224+
t.Fatal(err)
225+
}
226+
if n != N {
227+
t.Fatalf("Tried to upload %d bytes, but only sent %d", N, n)
228+
}
229+
230+
n, err = io.ReadFull(conn, down)
231+
if err != nil && err != io.EOF {
232+
t.Fatal(err)
233+
}
234+
if n != N {
235+
t.Fatalf("Expected to download %d bytes, but only received %d", N, n)
236+
}
237+
238+
if !bytes.Equal(up, down) {
239+
t.Fatal("Echo mismatch")
240+
}
241+
}
242+
243+
return time.Now().Sub(start)
244+
}
245+
246+
period1 := doWriteRead(200, 4)
247+
if period1 < 500*time.Millisecond {
248+
t.Fatalf("Write-read loop is too fast")
249+
}
250+
251+
time.Sleep(1 * time.Second)
252+
253+
period2 := doWriteRead(200, 2)
254+
if period2 > 100*time.Millisecond {
255+
t.Fatalf("Write-read loop is too slow")
256+
}
257+
258+
period3 := doWriteRead(500, 2)
259+
if period3 < 500*time.Millisecond {
260+
t.Fatalf("Write-read loop is too fast")
261+
}
262+
263+
proxy.Stop()
264+
echoListener.Close()
265+
echoRunning.Wait()
266+
}
267+
268+
func TestTrafficLimiterUDP(t *testing.T) {
269+
echoConn, echoRunning := startUDPEchoServer(t)
270+
271+
proxyConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
272+
if err != nil {
273+
t.Fatalf("ListenTCP failed: %v", err)
274+
}
275+
secrets := ss.MakeTestSecrets(1)
276+
cipherList, err := service.MakeTestCiphers(secrets)
277+
if err != nil {
278+
t.Fatal(err)
279+
}
280+
testMetrics := &fakeUDPMetrics{fakeLocation: "QQ"}
281+
282+
key := cipherList.SnapshotForClientIP(net.IP{})[0].Value.(*service.CipherEntry).ID
283+
trafficLimiter := service.NewTrafficLimiter(&service.TrafficLimiterConfig{
284+
KeyToRateLimit: map[string]int{
285+
key: 1000,
286+
},
287+
})
288+
289+
proxy := service.NewUDPService(time.Hour, cipherList, testMetrics, trafficLimiter)
290+
proxy.SetTargetIPValidator(allowAll)
291+
go proxy.Serve(proxyConn)
292+
293+
proxyHost, proxyPort, err := net.SplitHostPort(proxyConn.LocalAddr().String())
294+
if err != nil {
295+
t.Fatal(err)
296+
}
297+
portNum, err := strconv.Atoi(proxyPort)
298+
if err != nil {
299+
t.Fatal(err)
300+
}
301+
client, err := client.NewClient(proxyHost, portNum, secrets[0], ss.TestCipher)
302+
if err != nil {
303+
t.Fatalf("Failed to create ShadowsocksClient: %v", err)
304+
}
305+
conn, err := client.ListenUDP(nil)
306+
if err != nil {
307+
t.Fatalf("ShadowsocksClient.ListenUDP failed: %v", err)
308+
}
309+
310+
run := func(N int, expectReadError bool) {
311+
up := ss.MakeTestPayload(N)
312+
n, err := conn.WriteTo(up, echoConn.LocalAddr())
313+
if err != nil {
314+
t.Fatal(err)
315+
}
316+
if n != N {
317+
t.Fatalf("Tried to upload %d bytes, but only sent %d", N, n)
318+
}
319+
320+
conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
321+
322+
down := make([]byte, N)
323+
n, addr, err := conn.ReadFrom(down)
324+
if err != nil {
325+
if !expectReadError {
326+
t.Fatalf("Unexpected read error: %v", err)
327+
}
328+
return
329+
} else {
330+
if expectReadError {
331+
t.Fatalf("Expected read error")
332+
}
333+
}
334+
if n != N {
335+
t.Fatalf("Tried to download %d bytes, but only sent %d", N, n)
336+
}
337+
if addr.String() != echoConn.LocalAddr().String() {
338+
t.Errorf("Reported address mismatch: %s != %s", addr.String(), echoConn.LocalAddr().String())
339+
}
340+
341+
if !bytes.Equal(up, down) {
342+
t.Fatal("Echo mismatch")
343+
}
344+
}
345+
346+
for i := 0; i < 3; i++ {
347+
run(300, false)
348+
run(300, true)
349+
time.Sleep(time.Second)
350+
}
351+
352+
conn.Close()
353+
echoConn.Close()
354+
echoRunning.Wait()
355+
proxy.GracefulStop()
356+
}
357+
167358
type statusMetrics struct {
168359
metrics.NoOpMetrics
169360
sync.Mutex
@@ -184,7 +375,7 @@ func TestRestrictedAddresses(t *testing.T) {
184375
require.NoError(t, err)
185376
const testTimeout = 200 * time.Millisecond
186377
testMetrics := &statusMetrics{}
187-
proxy := service.NewTCPService(cipherList, nil, testMetrics, testTimeout)
378+
proxy := service.NewTCPService(cipherList, nil, testMetrics, testTimeout, makeLimiter(cipherList))
188379
go proxy.Serve(proxyListener)
189380

190381
proxyHost, proxyPort, err := net.SplitHostPort(proxyListener.Addr().String())
@@ -266,7 +457,7 @@ func TestUDPEcho(t *testing.T) {
266457
t.Fatal(err)
267458
}
268459
testMetrics := &fakeUDPMetrics{fakeLocation: "QQ"}
269-
proxy := service.NewUDPService(time.Hour, cipherList, testMetrics)
460+
proxy := service.NewUDPService(time.Hour, cipherList, testMetrics, makeLimiter(cipherList))
270461
proxy.SetTargetIPValidator(allowAll)
271462
go proxy.Serve(proxyConn)
272463

@@ -363,7 +554,7 @@ func BenchmarkTCPThroughput(b *testing.B) {
363554
b.Fatal(err)
364555
}
365556
const testTimeout = 200 * time.Millisecond
366-
proxy := service.NewTCPService(cipherList, nil, &metrics.NoOpMetrics{}, testTimeout)
557+
proxy := service.NewTCPService(cipherList, nil, &metrics.NoOpMetrics{}, testTimeout, makeLimiter(cipherList))
367558
proxy.SetTargetIPValidator(allowAll)
368559
go proxy.Serve(proxyListener)
369560

@@ -430,7 +621,7 @@ func BenchmarkTCPMultiplexing(b *testing.B) {
430621
}
431622
replayCache := service.NewReplayCache(service.MaxCapacity)
432623
const testTimeout = 200 * time.Millisecond
433-
proxy := service.NewTCPService(cipherList, &replayCache, &metrics.NoOpMetrics{}, testTimeout)
624+
proxy := service.NewTCPService(cipherList, &replayCache, &metrics.NoOpMetrics{}, testTimeout, makeLimiter(cipherList))
434625
proxy.SetTargetIPValidator(allowAll)
435626
go proxy.Serve(proxyListener)
436627

@@ -505,7 +696,7 @@ func BenchmarkUDPEcho(b *testing.B) {
505696
if err != nil {
506697
b.Fatal(err)
507698
}
508-
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{})
699+
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{}, makeLimiter(cipherList))
509700
proxy.SetTargetIPValidator(allowAll)
510701
go proxy.Serve(proxyConn)
511702

@@ -554,7 +745,7 @@ func BenchmarkUDPManyKeys(b *testing.B) {
554745
if err != nil {
555746
b.Fatal(err)
556747
}
557-
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{})
748+
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{}, makeLimiter(cipherList))
558749
proxy.SetTargetIPValidator(allowAll)
559750
go proxy.Serve(proxyConn)
560751

server.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ type SSServer struct {
7474
ports map[int]*ssPort
7575
}
7676

77-
func (s *SSServer) startPort(portNum int) error {
77+
func (s *SSServer) startPort(portNum int, trafficLimiterConfig *service.TrafficLimiterConfig) error {
7878
listener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: portNum})
7979
if err != nil {
8080
return fmt.Errorf("Failed to start TCP on port %v: %v", portNum, err)
@@ -85,9 +85,11 @@ func (s *SSServer) startPort(portNum int) error {
8585
}
8686
logger.Infof("Listening TCP and UDP on port %v", portNum)
8787
port := &ssPort{cipherList: service.NewCipherList()}
88+
89+
limiter := service.NewTrafficLimiter(trafficLimiterConfig)
8890
// TODO: Register initial data metrics at zero.
89-
port.tcpService = service.NewTCPService(port.cipherList, &s.replayCache, s.m, tcpReadTimeout)
90-
port.udpService = service.NewUDPService(s.natTimeout, port.cipherList, s.m)
91+
port.tcpService = service.NewTCPService(port.cipherList, &s.replayCache, s.m, tcpReadTimeout, limiter)
92+
port.udpService = service.NewUDPService(s.natTimeout, port.cipherList, s.m, limiter)
9193
s.ports[portNum] = port
9294
go port.tcpService.Serve(listener)
9395
go port.udpService.Serve(packetConn)
@@ -120,6 +122,7 @@ func (s *SSServer) loadConfig(filename string) error {
120122

121123
portChanges := make(map[int]int)
122124
portCiphers := make(map[int]*list.List) // Values are *List of *CipherEntry.
125+
portKeyLimits := make(map[int]map[string]int)
123126
for _, keyConfig := range config.Keys {
124127
portChanges[keyConfig.Port] = 1
125128
cipherList, ok := portCiphers[keyConfig.Port]
@@ -133,6 +136,13 @@ func (s *SSServer) loadConfig(filename string) error {
133136
}
134137
entry := service.MakeCipherEntry(keyConfig.ID, cipher, keyConfig.Secret)
135138
cipherList.PushBack(&entry)
139+
var keyLimits map[string]int
140+
keyLimits, ok = portKeyLimits[keyConfig.Port]
141+
if !ok {
142+
keyLimits = make(map[string]int)
143+
portKeyLimits[keyConfig.Port] = keyLimits
144+
}
145+
keyLimits[keyConfig.ID] = keyConfig.RateLimit
136146
}
137147
for port := range s.ports {
138148
portChanges[port] = portChanges[port] - 1
@@ -143,7 +153,8 @@ func (s *SSServer) loadConfig(filename string) error {
143153
return fmt.Errorf("Failed to remove port %v: %v", portNum, err)
144154
}
145155
} else if count == +1 {
146-
if err := s.startPort(portNum); err != nil {
156+
trafficLimiterConfig := &service.TrafficLimiterConfig{KeyToRateLimit: portKeyLimits[portNum]}
157+
if err := s.startPort(portNum, trafficLimiterConfig); err != nil {
147158
return fmt.Errorf("Failed to start port %v: %v", portNum, err)
148159
}
149160
}
@@ -193,10 +204,11 @@ func RunSSServer(filename string, natTimeout time.Duration, sm metrics.Shadowsoc
193204

194205
type Config struct {
195206
Keys []struct {
196-
ID string
197-
Port int
198-
Cipher string
199-
Secret string
207+
ID string
208+
Port int
209+
Cipher string
210+
Secret string
211+
RateLimit int
200212
}
201213
}
202214

@@ -207,6 +219,9 @@ func readConfig(filename string) (*Config, error) {
207219
return nil, err
208220
}
209221
err = yaml.Unmarshal(configData, &config)
222+
if err != nil {
223+
return nil, err
224+
}
210225
return &config, err
211226
}
212227

0 commit comments

Comments
 (0)