Skip to content

Commit 600da79

Browse files
authored
Merge pull request #9 from Jigsaw-Code/fortuna-opt
Shuffle ciphers
2 parents 7be8e4e + 47a08be commit 600da79

File tree

3 files changed

+85
-36
lines changed

3 files changed

+85
-36
lines changed

shadowsocks/cipher_map.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
"math/rand"
19+
20+
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
21+
)
22+
23+
type cipherEntry struct {
24+
id string
25+
cipher shadowaead.Cipher
26+
}
27+
28+
func shuffleCipherMap(cipherMap map[string]shadowaead.Cipher) []cipherEntry {
29+
cipherArray := make([]cipherEntry, len(cipherMap))
30+
perm := rand.Perm(len(cipherMap))
31+
i := 0
32+
for id, cipher := range cipherMap {
33+
cipherArray[perm[i]] = cipherEntry{id, cipher}
34+
i++
35+
}
36+
return cipherArray
37+
}

shadowsocks/tcp.go

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func ensureBytes(reader io.Reader, buf []byte, bytesNeeded int) ([]byte, error)
4848
return buf, err
4949
}
5050

51-
func findAccessKey(clientConn onet.DuplexConn, cipherList map[string]shadowaead.Cipher) (string, onet.DuplexConn, error) {
51+
func findAccessKey(clientConn onet.DuplexConn, cipherMap map[string]shadowaead.Cipher) (string, onet.DuplexConn, error) {
5252
// This must have enough space to hold the salt + 2 bytes chunk length + AEAD tag (Oeverhead) for any cipher
5353
replayBytes := make([]byte, 0, 32+2+16)
5454
// Constant of zeroes to use as the start chunk count. This must be as big as the max NonceSize() across all ciphers.
@@ -57,11 +57,12 @@ func findAccessKey(clientConn onet.DuplexConn, cipherList map[string]shadowaead.
5757
chunkLenBuf := [2]byte{}
5858
var err error
5959

60-
// Try each cipher until we find one that authenticates successfully.
61-
// This assumes that all ciphers are AEAD.
60+
// 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.
6262
// TODO: Reorder list to try previously successful ciphers first for the client IP.
6363
// TODO: Ban and log client IPs with too many failures too quick to protect against DoS.
64-
for id, cipher := range cipherList {
64+
for _, entry := range shuffleCipherMap(cipherMap) {
65+
id, cipher := entry.id, entry.cipher
6566
replayBytes, err = ensureBytes(clientConn, replayBytes, cipher.SaltSize())
6667
if err != nil {
6768
if logger.IsEnabledFor(logging.DEBUG) {
@@ -103,15 +104,47 @@ type tcpService struct {
103104
isRunning bool
104105
}
105106

107+
// NewTCPService creates a TCPService
106108
func NewTCPService(listener *net.TCPListener, ciphers *map[string]shadowaead.Cipher, m metrics.ShadowsocksMetrics) TCPService {
107109
return &tcpService{listener: listener, ciphers: ciphers, m: m}
108110
}
109111

112+
// TCPService is a Shadowsocks TCP service that can be started and stopped.
110113
type TCPService interface {
111114
Start()
112115
Stop() error
113116
}
114117

118+
// proxyConnection will route the clientConn according to the address read from the connection.
119+
func proxyConnection(clientConn onet.DuplexConn, proxyMetrics *metrics.ProxyMetrics) *onet.ConnectionError {
120+
tgtAddr, err := socks.ReadAddr(clientConn)
121+
if err != nil {
122+
return onet.NewConnectionError("ERR_READ_ADDRESS", "Failed to get target address", err)
123+
}
124+
tgtTCPAddr, err := net.ResolveTCPAddr("tcp", tgtAddr.String())
125+
if err != nil {
126+
return onet.NewConnectionError("ERR_RESOLVE_ADDRESS", fmt.Sprintf("Failed to resolve target address %v", tgtAddr.String()), err)
127+
}
128+
if !tgtTCPAddr.IP.IsGlobalUnicast() {
129+
return onet.NewConnectionError("ERR_ADDRESS_INVALID", fmt.Sprintf("Target address is not global unicast: %v", tgtAddr.String()), err)
130+
}
131+
132+
tgtTCPConn, err := net.DialTCP("tcp", nil, tgtTCPAddr)
133+
if err != nil {
134+
return onet.NewConnectionError("ERR_CONNECT", "Failed to connect to target", err)
135+
}
136+
defer tgtTCPConn.Close()
137+
tgtTCPConn.SetKeepAlive(true)
138+
tgtConn := metrics.MeasureConn(tgtTCPConn, &proxyMetrics.ProxyTarget, &proxyMetrics.TargetProxy)
139+
140+
logger.Debugf("proxy %s <-> %s", clientConn.RemoteAddr().String(), tgtConn.RemoteAddr().String())
141+
_, _, err = onet.Relay(clientConn, tgtConn)
142+
if err != nil {
143+
return onet.NewConnectionError("ERR_RELAY", "Failed to relay traffic", err)
144+
}
145+
return nil
146+
}
147+
115148
func (s *tcpService) Start() {
116149
s.isRunning = true
117150
for s.isRunning {
@@ -156,36 +189,10 @@ func (s *tcpService) Start() {
156189

157190
keyID, clientConn, err := findAccessKey(clientConn, *s.ciphers)
158191
if err != nil {
159-
return &onet.ConnectionError{"ERR_CIPHER", "Failed to find a valid cipher", err}
192+
return onet.NewConnectionError("ERR_CIPHER", "Failed to find a valid cipher", err)
160193
}
161194

162-
tgtAddr, err := socks.ReadAddr(clientConn)
163-
if err != nil {
164-
return &onet.ConnectionError{"ERR_READ_ADDRESS", "Failed to get target address", err}
165-
}
166-
tgtTCPAddr, err := net.ResolveTCPAddr("tcp", tgtAddr.String())
167-
if err != nil {
168-
return &onet.ConnectionError{"ERR_RESOLVE_ADDRESS", fmt.Sprintf("Failed to resolve target address %v", tgtAddr.String()), err}
169-
}
170-
if !tgtTCPAddr.IP.IsGlobalUnicast() {
171-
return &onet.ConnectionError{"ERR_ADDRESS_INVALID", fmt.Sprintf("Target address is not global unicast: %v", tgtAddr.String()), err}
172-
}
173-
174-
tgtTCPConn, err := net.DialTCP("tcp", nil, tgtTCPAddr)
175-
if err != nil {
176-
return &onet.ConnectionError{"ERR_CONNECT", "Failed to connect to target", err}
177-
}
178-
defer tgtTCPConn.Close()
179-
tgtTCPConn.SetKeepAlive(true)
180-
tgtConn := metrics.MeasureConn(tgtTCPConn, &proxyMetrics.ProxyTarget, &proxyMetrics.TargetProxy)
181-
182-
// TODO: Disable logging in production. This is sensitive.
183-
logger.Debugf("proxy %s <-> %s", clientConn.RemoteAddr().String(), tgtConn.RemoteAddr().String())
184-
_, _, err = onet.Relay(clientConn, tgtConn)
185-
if err != nil {
186-
return &onet.ConnectionError{"ERR_RELAY", "Failed to relay traffic", err}
187-
}
188-
return nil
195+
return proxyConnection(clientConn, &proxyMetrics)
189196
}()
190197
}
191198
}

shadowsocks/udp.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ const udpBufSize = 64 * 1024
3535

3636
// upack decrypts src into dst. It tries each cipher until it finds one that authenticates
3737
// correctly. dst and src must not overlap.
38-
func unpack(dst, src []byte, ciphers map[string]shadowaead.Cipher) ([]byte, string, shadowaead.Cipher, error) {
39-
for id, cipher := range ciphers {
38+
func unpack(dst, src []byte, cipherMap map[string]shadowaead.Cipher) ([]byte, string, shadowaead.Cipher, error) {
39+
// Try each cipher until we find one that authenticates successfully. This assumes that all ciphers are AEAD.
40+
// We shuffle the cipher map so that every connection has the same expected time.
41+
for _, entry := range shuffleCipherMap(cipherMap) {
42+
id, cipher := entry.id, entry.cipher
4043
buf, err := shadowaead.Unpack(dst, src, cipher)
4144
if err != nil {
4245
if logger.IsEnabledFor(logging.DEBUG) {
@@ -60,10 +63,12 @@ type udpService struct {
6063
isRunning bool
6164
}
6265

63-
func NewUDPService(clientConn net.PacketConn, natTimeout time.Duration, ciphers *map[string]shadowaead.Cipher, m metrics.ShadowsocksMetrics) UDPService {
64-
return &udpService{clientConn: clientConn, natTimeout: natTimeout, ciphers: ciphers, m: m}
66+
// NewUDPService creates a UDPService
67+
func NewUDPService(clientConn net.PacketConn, natTimeout time.Duration, cipherMap *map[string]shadowaead.Cipher, m metrics.ShadowsocksMetrics) UDPService {
68+
return &udpService{clientConn: clientConn, natTimeout: natTimeout, ciphers: cipherMap, m: m}
6569
}
6670

71+
// UDPService is a UDP shadowsocks service that can be started and stopped.
6772
type UDPService interface {
6873
Start()
6974
Stop() error

0 commit comments

Comments
 (0)