Skip to content

Commit 600bf05

Browse files
committed
Keep active ciphers at the front
1 parent 31d9b0e commit 600bf05

File tree

9 files changed

+106
-155
lines changed

9 files changed

+106
-155
lines changed

server.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func init() {
5454
type SSPort struct {
5555
tcpService shadowsocks.TCPService
5656
udpService shadowsocks.UDPService
57-
keys map[string]shadowaead.Cipher
57+
cipherList shadowsocks.CipherList
5858
}
5959

6060
type SSServer struct {
@@ -73,10 +73,10 @@ func (s *SSServer) startPort(portNum int) error {
7373
return fmt.Errorf("Failed to start UDP on port %v: %v", portNum, err)
7474
}
7575
logger.Infof("Listening TCP and UDP on port %v", portNum)
76-
port := &SSPort{keys: make(map[string]shadowaead.Cipher)}
76+
port := &SSPort{cipherList: shadowsocks.NewCipherList()}
7777
// TODO: Register initial data metrics at zero.
78-
port.tcpService = shadowsocks.NewTCPService(listener, &port.keys, s.m)
79-
port.udpService = shadowsocks.NewUDPService(packetConn, s.natTimeout, &port.keys, s.m)
78+
port.tcpService = shadowsocks.NewTCPService(listener, &port.cipherList, s.m)
79+
port.udpService = shadowsocks.NewUDPService(packetConn, s.natTimeout, &port.cipherList, s.m)
8080
s.ports[portNum] = port
8181
go port.udpService.Start()
8282
go port.tcpService.Start()
@@ -108,13 +108,13 @@ func (s *SSServer) loadConfig(filename string) error {
108108
}
109109

110110
portChanges := make(map[int]int)
111-
portKeys := make(map[int]map[string]shadowaead.Cipher)
111+
portCiphers := make(map[int]shadowsocks.CipherList)
112112
for _, keyConfig := range config.Keys {
113113
portChanges[keyConfig.Port] = 1
114-
keys, ok := portKeys[keyConfig.Port]
114+
cipherList, ok := portCiphers[keyConfig.Port]
115115
if !ok {
116-
keys = make(map[string]shadowaead.Cipher)
117-
portKeys[keyConfig.Port] = keys
116+
cipherList = shadowsocks.NewCipherList()
117+
portCiphers[keyConfig.Port] = cipherList
118118
}
119119
cipher, err := core.PickCipher(keyConfig.Cipher, nil, keyConfig.Secret)
120120
if err != nil {
@@ -127,7 +127,7 @@ func (s *SSServer) loadConfig(filename string) error {
127127
if !ok {
128128
return fmt.Errorf("Only AEAD ciphers are supported. Found %v", keyConfig.Cipher)
129129
}
130-
keys[keyConfig.ID] = aead
130+
cipherList.PushBack(keyConfig.ID, aead)
131131
}
132132
for port := range s.ports {
133133
portChanges[port] = portChanges[port] - 1
@@ -143,11 +143,11 @@ func (s *SSServer) loadConfig(filename string) error {
143143
}
144144
}
145145
}
146-
for portNum, keys := range portKeys {
147-
s.ports[portNum].keys = keys
146+
for portNum, cipherList := range portCiphers {
147+
s.ports[portNum].cipherList = cipherList
148148
}
149149
logger.Infof("Loaded %v access keys", len(config.Keys))
150-
s.m.SetNumAccessKeys(len(config.Keys), len(portKeys))
150+
s.m.SetNumAccessKeys(len(config.Keys), len(portCiphers))
151151
return nil
152152
}
153153

shadowsocks/cipher_cache.go

Lines changed: 0 additions & 81 deletions
This file was deleted.

shadowsocks/cipher_list.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2018 Jigsaw Operations LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package shadowsocks
16+
17+
import (
18+
"container/list"
19+
"sync"
20+
21+
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
22+
)
23+
24+
// CipherEntry holds a Cipher with an identifier.
25+
type CipherEntry struct {
26+
ID string
27+
Cipher shadowaead.Cipher
28+
}
29+
30+
// CipherList is a list of CipherEntry elements that allows for thread-safe snapshotting and
31+
// moving to front.
32+
type CipherList interface {
33+
PushBack(id string, cipher shadowaead.Cipher) *list.Element
34+
SafeSnapshot() []*list.Element
35+
SafeMoveToFront(e *list.Element)
36+
}
37+
38+
type cipherList struct {
39+
CipherList
40+
list *list.List
41+
mu sync.RWMutex
42+
}
43+
44+
// NewCipherList creates an empty CipherList
45+
func NewCipherList() CipherList {
46+
return &cipherList{list: list.New()}
47+
}
48+
49+
func (cl *cipherList) PushBack(id string, cipher shadowaead.Cipher) *list.Element {
50+
return cl.list.PushBack(&CipherEntry{ID: id, Cipher: cipher})
51+
}
52+
53+
func (cl *cipherList) SafeSnapshot() []*list.Element {
54+
cl.mu.RLock()
55+
defer cl.mu.RUnlock()
56+
cipherArray := make([]*list.Element, cl.list.Len())
57+
i := 0
58+
for e := cl.list.Front(); e != nil; e = e.Next() {
59+
cipherArray[i] = e
60+
i++
61+
}
62+
return cipherArray
63+
}
64+
65+
func (cl *cipherList) SafeMoveToFront(e *list.Element) {
66+
cl.mu.Lock()
67+
defer cl.mu.Unlock()
68+
cl.list.MoveToFront(e)
69+
}

shadowsocks/cipher_map.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

shadowsocks/testing/ciphers.go renamed to shadowsocks/cipher_testing.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package testing
15+
package shadowsocks
1616

1717
import (
1818
"fmt"
@@ -21,16 +21,16 @@ import (
2121
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
2222
)
2323

24-
func MakeTestCiphers(numCiphers int) (map[string]shadowaead.Cipher, error) {
25-
cipherList := make(map[string]shadowaead.Cipher)
24+
func MakeTestCiphers(numCiphers int) (CipherList, error) {
25+
cipherList := NewCipherList()
2626
for i := 0; i < numCiphers; i++ {
2727
cipherID := fmt.Sprintf("id-%v", i)
2828
secret := fmt.Sprintf("secret-%v", i)
2929
cipher, err := core.PickCipher("chacha20-ietf-poly1305", nil, secret)
3030
if err != nil {
3131
return nil, fmt.Errorf("Failed to create cipher %v: %v", i, err)
3232
}
33-
cipherList[cipherID] = cipher.(shadowaead.Cipher)
33+
cipherList.PushBack(cipherID, cipher.(shadowaead.Cipher))
3434
}
3535
return cipherList, nil
3636
}

shadowsocks/tcp.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/Jigsaw-Code/outline-ss-server/metrics"
2727
onet "github.com/Jigsaw-Code/outline-ss-server/net"
2828

29-
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
3029
"github.com/shadowsocks/go-shadowsocks2/socks"
3130
)
3231

@@ -48,7 +47,7 @@ func ensureBytes(reader io.Reader, buf []byte, bytesNeeded int) ([]byte, error)
4847
return buf, err
4948
}
5049

51-
func findAccessKey(clientConn onet.DuplexConn, cipherMap map[string]shadowaead.Cipher) (string, onet.DuplexConn, error) {
50+
func findAccessKey(clientConn onet.DuplexConn, cipherList CipherList) (string, onet.DuplexConn, error) {
5251
// This must have enough space to hold the salt + 2 bytes chunk length + AEAD tag (Oeverhead) for any cipher
5352
replayBytes := make([]byte, 0, 32+2+16)
5453
// Constant of zeroes to use as the start chunk count. This must be as big as the max NonceSize() across all ciphers.
@@ -58,11 +57,10 @@ func findAccessKey(clientConn onet.DuplexConn, cipherMap map[string]shadowaead.C
5857
var err error
5958

6059
// Try each cipher until we find one that authenticates successfully. This assumes that all ciphers are AEAD.
61-
// We shuffle the cipher map so that every connection has the same expected time.
62-
// TODO: Reorder list to try previously successful ciphers first for the client IP.
60+
// We snapshot the list because it may be modified while we use it.
6361
// TODO: Ban and log client IPs with too many failures too quick to protect against DoS.
64-
for _, entry := range shuffleCipherMap(cipherMap) {
65-
id, cipher := entry.id, entry.cipher
62+
for _, entry := range cipherList.SafeSnapshot() {
63+
id, cipher := entry.Value.(*CipherEntry).ID, entry.Value.(*CipherEntry).Cipher
6664
replayBytes, err = ensureBytes(clientConn, replayBytes, cipher.SaltSize())
6765
if err != nil {
6866
if logger.IsEnabledFor(logging.DEBUG) {
@@ -90,6 +88,8 @@ func findAccessKey(clientConn onet.DuplexConn, cipherMap map[string]shadowaead.C
9088
if logger.IsEnabledFor(logging.DEBUG) {
9189
logger.Debugf("Selected TCP cipher %v", id)
9290
}
91+
// Move the active cipher to the front, so that the search is quicker next time.
92+
cipherList.SafeMoveToFront(entry)
9393
ssr := NewShadowsocksReader(io.MultiReader(bytes.NewReader(replayBytes), clientConn), cipher)
9494
ssw := NewShadowsocksWriter(clientConn, cipher)
9595
return id, onet.WrapConn(clientConn, ssr, ssw).(onet.DuplexConn), nil
@@ -99,13 +99,13 @@ func findAccessKey(clientConn onet.DuplexConn, cipherMap map[string]shadowaead.C
9999

100100
type tcpService struct {
101101
listener *net.TCPListener
102-
ciphers *map[string]shadowaead.Cipher
102+
ciphers *CipherList
103103
m metrics.ShadowsocksMetrics
104104
isRunning bool
105105
}
106106

107107
// NewTCPService creates a TCPService
108-
func NewTCPService(listener *net.TCPListener, ciphers *map[string]shadowaead.Cipher, m metrics.ShadowsocksMetrics) TCPService {
108+
func NewTCPService(listener *net.TCPListener, ciphers *CipherList, m metrics.ShadowsocksMetrics) TCPService {
109109
return &tcpService{listener: listener, ciphers: ciphers, m: m}
110110
}
111111

shadowsocks/tcp_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"net"
1919
"testing"
2020

21-
sstest "github.com/Jigsaw-Code/outline-ss-server/shadowsocks/testing"
2221
logging "github.com/op/go-logging"
2322
)
2423

@@ -32,11 +31,11 @@ func BenchmarkTCPFindCipher(b *testing.B) {
3231
b.Fatalf("ListenTCP failed: %v", err)
3332
}
3433

35-
cipherList, err := sstest.MakeTestCiphers(100)
34+
cipherList, err := MakeTestCiphers(100)
3635
if err != nil {
3736
b.Fatal(err)
3837
}
39-
testPayload := sstest.MakeTestPayload(50)
38+
testPayload := MakeTestPayload(50)
4039
for n := 0; n < b.N; n++ {
4140
go func() {
4241
conn, err := net.Dial("tcp", listener.Addr().String())

0 commit comments

Comments
 (0)