diff --git a/shadowsocks/stream.go b/shadowsocks/stream.go index 3cce192d..8f126c8a 100644 --- a/shadowsocks/stream.go +++ b/shadowsocks/stream.go @@ -142,6 +142,7 @@ func NewShadowsocksReader(reader io.Reader, ssCipher shadowaead.Cipher) Shadowso // init reads the salt from the inner Reader and sets up the AEAD object func (sr *shadowsocksReader) init() (err error) { if sr.aead == nil { + // For chacha20-poly1305, SaltSize is 32, NonceSize is 12 and Overhead is 16. salt := make([]byte, sr.ssCipher.SaltSize()) if _, err := io.ReadFull(sr.reader, salt); err != nil { if err != io.EOF && err != io.ErrUnexpectedEOF { diff --git a/shadowsocks/tcp.go b/shadowsocks/tcp.go index c4d784cf..61fdeaeb 100644 --- a/shadowsocks/tcp.go +++ b/shadowsocks/tcp.go @@ -22,6 +22,8 @@ import ( "net" "time" + logging "github.com/op/go-logging" + "github.com/Jigsaw-Code/outline-ss-server/metrics" onet "github.com/Jigsaw-Code/outline-ss-server/net" @@ -40,7 +42,6 @@ func findAccessKey(clientConn onet.DuplexConn, cipherList map[string]shadowaead. // TODO: Reorder list to try previously successful ciphers first for the client IP. // TODO: Ban and log client IPs with too many failures too quick to protect against DoS. for id, cipher := range cipherList { - logger.Debugf("Trying key %v", id) // tmpReader reads first from the replayBuffer and then from clientConn if it needs more // bytes. All bytes read from clientConn are saved in replayBuffer for future replays. tmpReader := io.MultiReader(bytes.NewReader(replayBuffer.Bytes()), io.TeeReader(clientConn, &replayBuffer)) @@ -48,10 +49,14 @@ func findAccessKey(clientConn onet.DuplexConn, cipherList map[string]shadowaead. // Read should read just enough data to authenticate the payload size. _, err := cipherReader.Read(make([]byte, 0)) if err != nil { - logger.Debugf("Failed key %v: %v", id, err) + if logger.IsEnabledFor(logging.DEBUG) { + logger.Debugf("Failed TCP cipher %v: %v", id, err) + } continue } - logger.Debugf("Selected key %v", id) + if logger.IsEnabledFor(logging.DEBUG) { + logger.Debugf("Selected TCP cipher %v", id) + } // We don't need to keep storing and replaying the bytes anymore, but we don't want to drop // those already read into the replayBuffer. ssr := NewShadowsocksReader(io.MultiReader(&replayBuffer, clientConn), cipher) diff --git a/shadowsocks/tcp_test.go b/shadowsocks/tcp_test.go new file mode 100644 index 00000000..244b2e4f --- /dev/null +++ b/shadowsocks/tcp_test.go @@ -0,0 +1,57 @@ +// Copyright 2018 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shadowsocks + +import ( + "net" + "testing" + + sstest "github.com/Jigsaw-Code/outline-ss-server/shadowsocks/testing" + logging "github.com/op/go-logging" +) + +func BenchmarkTCPFindCipher(b *testing.B) { + b.StopTimer() + b.ResetTimer() + + logging.SetLevel(logging.INFO, "") + listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) + if err != nil { + b.Fatalf("ListenTCP failed: %v", err) + } + + cipherList, err := sstest.MakeTestCiphers(100) + if err != nil { + b.Fatal(err) + } + testPayload := sstest.MakeTestPayload(60) + for n := 0; n < b.N; n++ { + go func() { + conn, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + b.Fatalf("Failed to dial %v: %v", listener.Addr(), err) + } + conn.Write(testPayload) + conn.Close() + }() + clientConn, err := listener.AcceptTCP() + if err != nil { + b.Fatalf("AcceptTCP failed: %v", err) + } + b.StartTimer() + findAccessKey(clientConn, cipherList) + b.StopTimer() + } +} diff --git a/shadowsocks/testing/ciphers.go b/shadowsocks/testing/ciphers.go new file mode 100644 index 00000000..c8cbda3c --- /dev/null +++ b/shadowsocks/testing/ciphers.go @@ -0,0 +1,44 @@ +// Copyright 2018 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testing + +import ( + "fmt" + + "github.com/shadowsocks/go-shadowsocks2/core" + "github.com/shadowsocks/go-shadowsocks2/shadowaead" +) + +func MakeTestCiphers(numCiphers int) (map[string]shadowaead.Cipher, error) { + cipherList := make(map[string]shadowaead.Cipher) + for i := 0; i < numCiphers; i++ { + cipherID := fmt.Sprintf("id-%v", i) + secret := fmt.Sprintf("secret-%v", i) + cipher, err := core.PickCipher("chacha20-ietf-poly1305", nil, secret) + if err != nil { + return nil, fmt.Errorf("Failed to create cipher %v: %v", i, err) + } + cipherList[cipherID] = cipher.(shadowaead.Cipher) + } + return cipherList, nil +} + +func MakeTestPayload(size int) []byte { + payload := make([]byte, size) + for i := 0; i < size; i++ { + payload[i] = byte(i) + } + return payload +} diff --git a/shadowsocks/udp.go b/shadowsocks/udp.go index 70281bc1..5d697abf 100644 --- a/shadowsocks/udp.go +++ b/shadowsocks/udp.go @@ -23,6 +23,7 @@ import ( "github.com/Jigsaw-Code/outline-ss-server/metrics" onet "github.com/Jigsaw-Code/outline-ss-server/net" + logging "github.com/op/go-logging" "sync" @@ -36,13 +37,16 @@ const udpBufSize = 64 * 1024 // correctly. dst and src must not overlap. func unpack(dst, src []byte, ciphers map[string]shadowaead.Cipher) ([]byte, string, shadowaead.Cipher, error) { for id, cipher := range ciphers { - logger.Debugf("Trying UDP cipher %v", id) buf, err := shadowaead.Unpack(dst, src, cipher) if err != nil { - logger.Debugf("Failed UDP cipher %v: %v", id, err) + if logger.IsEnabledFor(logging.DEBUG) { + logger.Debugf("Failed UDP cipher %v: %v", id, err) + } continue } - logger.Debugf("Selected UDP cipher %v", id) + if logger.IsEnabledFor(logging.DEBUG) { + logger.Debugf("Selected UDP cipher %v", id) + } return buf, id, cipher, nil } return nil, "", nil, errors.New("could not find valid cipher") diff --git a/shadowsocks/udp_test.go b/shadowsocks/udp_test.go new file mode 100644 index 00000000..70b92ced --- /dev/null +++ b/shadowsocks/udp_test.go @@ -0,0 +1,37 @@ +// Copyright 2018 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shadowsocks + +import ( + "testing" + + sstest "github.com/Jigsaw-Code/outline-ss-server/shadowsocks/testing" + logging "github.com/op/go-logging" +) + +func BenchmarkUDPUnpack(b *testing.B) { + logging.SetLevel(logging.INFO, "") + + cipherList, err := sstest.MakeTestCiphers(100) + if err != nil { + b.Fatal(err) + } + testPayload := sstest.MakeTestPayload(60) + textBuf := make([]byte, udpBufSize) + b.ResetTimer() + for n := 0; n < b.N; n++ { + unpack(textBuf, testPayload, cipherList) + } +}