From ac0380509f47684b92d019628a27ee6e025b1be6 Mon Sep 17 00:00:00 2001 From: defia Date: Wed, 14 Jan 2015 22:32:14 +0800 Subject: [PATCH 01/18] fix test and benchmark. add test and benchmark for rc4-md5 --- shadowsocks/encrypt_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index b2583d3a..e9ff06d7 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -139,6 +139,10 @@ func TestDES(t *testing.T) { testBlockCipher(t, "des-cfb") } +func TestRC4MD5(t *testing.T) { + testBlockCipher(t, "rc4-md5") +} + var cipherKey = make([]byte, 64) func init() { @@ -156,8 +160,9 @@ func BenchmarkRC4Init(b *testing.B) { func benchmarkCipherInit(b *testing.B, ci *cipherInfo) { key := cipherKey[:ci.keyLen] + buf := make([]byte, ci.ivLen) for i := 0; i < b.N; i++ { - ci.newBlock(key) + ci.newStream(key, buf, Encrypt) } } @@ -190,3 +195,8 @@ func BenchmarkDESInit(b *testing.B) { ci := cipherMethod["des-cfb"] benchmarkCipherInit(b, ci) } + +func BenchmarkRC4MD5Init(b *testing.B) { + ci := cipherMethod["rc4-md5"] + benchmarkCipherInit(b, ci) +} From 45e1575108688eeab34e97a1b313e4974d692eb8 Mon Sep 17 00:00:00 2001 From: defia Date: Tue, 3 Feb 2015 12:22:18 +0800 Subject: [PATCH 02/18] add support and test for chacha20 encrypt --- shadowsocks/encrypt.go | 11 +++++++++-- shadowsocks/encrypt_test.go | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index 3dbb6735..e9a563b5 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -2,8 +2,6 @@ package shadowsocks import ( "bytes" - "golang.org/x/crypto/blowfish" - "golang.org/x/crypto/cast5" "crypto/aes" "crypto/cipher" "crypto/des" @@ -13,6 +11,10 @@ import ( "encoding/binary" "errors" "io" + + "github.com/codahale/chacha20" + "golang.org/x/crypto/blowfish" + "golang.org/x/crypto/cast5" ) var errEmptyPassword = errors.New("empty key") @@ -137,6 +139,10 @@ func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { return rc4.NewCipher(rc4key) } +func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { + return chacha20.New(key, iv) +} + type cipherInfo struct { keyLen int ivLen int @@ -153,6 +159,7 @@ var cipherMethod = map[string]*cipherInfo{ "rc4-md5": {16, 16, newRC4MD5Stream}, "rc4": {16, 0, nil}, "table": {16, 0, nil}, + "chacha20": {32, 8, newChaCha20Stream}, } func CheckCipherMethod(method string) error { diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index e9ff06d7..e135f645 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -143,6 +143,10 @@ func TestRC4MD5(t *testing.T) { testBlockCipher(t, "rc4-md5") } +func TestChaCha20(t *testing.T) { + testBlockCipher(t, "chacha20") +} + var cipherKey = make([]byte, 64) func init() { @@ -200,3 +204,8 @@ func BenchmarkRC4MD5Init(b *testing.B) { ci := cipherMethod["rc4-md5"] benchmarkCipherInit(b, ci) } + +func BenchmarkChaCha20Init(b *testing.B) { + ci := cipherMethod["chacha20"] + benchmarkCipherInit(b, ci) +} From dcb3b78febd9ee3b54b1a90620c64aad9f119135 Mon Sep 17 00:00:00 2001 From: defia Date: Tue, 3 Feb 2015 18:37:59 +0800 Subject: [PATCH 03/18] fix travis build failed --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c68e325b..646aed19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: install: - go get golang.org/x/crypto/blowfish - go get golang.org/x/crypto/cast5 + - go get github.com/codahale/chacha20 - go install ./cmd/shadowsocks-local - go install ./cmd/shadowsocks-server script: From 08ad3023b032bc50c1e0566703899c4004837d2d Mon Sep 17 00:00:00 2001 From: defia Date: Wed, 11 Feb 2015 16:42:16 +0800 Subject: [PATCH 04/18] fix PipeThenClose will read once more when eof occurs --- shadowsocks/pipe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 35f356f6..9e53bb72 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -35,7 +35,7 @@ func PipeThenClose(src, dst net.Conn, timeoutOpt int) { // read may return EOF with n > 0 // should always process n > 0 bytes before handling error if n > 0 { - if _, err = dst.Write(buf[0:n]); err != nil { + if _, err := dst.Write(buf[0:n]); err != nil { Debug.Println("write:", err) break } From 55f57aa9ac7a4144caa1a9bebc1354c01b3c9699 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Thu, 12 Feb 2015 23:44:30 +0800 Subject: [PATCH 05/18] Fix #76: Write call overwrite err returned by Read. Thanks for defia pointing out this bug. --- shadowsocks/pipe.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 35f356f6..7801a8fb 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -35,7 +35,8 @@ func PipeThenClose(src, dst net.Conn, timeoutOpt int) { // read may return EOF with n > 0 // should always process n > 0 bytes before handling error if n > 0 { - if _, err = dst.Write(buf[0:n]); err != nil { + // Note: avoid overwrite err returned by Read. + if _, err := dst.Write(buf[0:n]); err != nil { Debug.Println("write:", err) break } From a84c46b1ac23feba0edb8526f59bbe8ea66ec6e1 Mon Sep 17 00:00:00 2001 From: genzj Date: Sun, 8 Mar 2015 02:16:50 +0800 Subject: [PATCH 06/18] support salsa20 crypto --- shadowsocks/encrypt.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index 3dbb6735..6ddf131c 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -4,6 +4,7 @@ import ( "bytes" "golang.org/x/crypto/blowfish" "golang.org/x/crypto/cast5" + "golang.org/x/crypto/salsa20/salsa" "crypto/aes" "crypto/cipher" "crypto/des" @@ -137,6 +138,31 @@ func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { return rc4.NewCipher(rc4key) } +type salsaStreamCipher struct { + nonce [8]byte + key [32]byte + counter int +} + +func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) { + var subNonce [16]byte + padlen := c.counter % 64 + buf := make([]byte, padlen+len(src)) + copy(buf[padlen:], src[:]) + copy(subNonce[:], c.nonce[:]) + binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter / 64)) + salsa.XORKeyStream(buf, buf, &subNonce, &c.key) + copy(dst, buf[padlen:]) + c.counter += len(src) +} + +func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { + var c salsaStreamCipher + copy(c.nonce[:], iv[:8]) + copy(c.key[:], key[:32]) + return &c, nil +} + type cipherInfo struct { keyLen int ivLen int @@ -153,6 +179,7 @@ var cipherMethod = map[string]*cipherInfo{ "rc4-md5": {16, 16, newRC4MD5Stream}, "rc4": {16, 0, nil}, "table": {16, 0, nil}, + "salsa20": {32, 8, newSalsa20Stream}, } func CheckCipherMethod(method string) error { From 7d1eff995b30aff34066ab2f00ce58f5fb941c1f Mon Sep 17 00:00:00 2001 From: genzj Date: Sun, 8 Mar 2015 02:37:34 +0800 Subject: [PATCH 07/18] add salsa20 dependency to travis installation --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c68e325b..7eb7ebc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: install: - go get golang.org/x/crypto/blowfish - go get golang.org/x/crypto/cast5 + - go get golang.org/x/crypto/salsa20 - go install ./cmd/shadowsocks-local - go install ./cmd/shadowsocks-server script: From ec0eca979c99f691779b82f54339ec7c39fe99ca Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Mon, 16 Mar 2015 23:00:49 +0800 Subject: [PATCH 08/18] Add --forbidden-ip= for test with shadowsocks python. --- script/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/test.sh b/script/test.sh index 1e2cf768..6a0aafd2 100755 --- a/script/test.sh +++ b/script/test.sh @@ -119,7 +119,7 @@ LOCAL="shadowsocks-local" test_server_local_pair if [[ -n $SS_PYTHON ]]; then - SERVER="$SS_PYTHON/server.py" + SERVER="$SS_PYTHON/server.py --forbidden-ip=" LOCAL="shadowsocks-local" test_server_local_pair From 5fcd2fdce2d9335609803d50d9c8ca0cbae5a227 Mon Sep 17 00:00:00 2001 From: genzj Date: Mon, 30 Mar 2015 03:22:09 +0000 Subject: [PATCH 09/18] add en-/decrypt tests and salsa20 init test --- shadowsocks/encrypt_test.go | 127 ++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index e9ff06d7..ced22477 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -3,6 +3,8 @@ package shadowsocks import ( "crypto/rc4" "reflect" + "io" + "crypto/rand" "testing" ) @@ -144,11 +146,15 @@ func TestRC4MD5(t *testing.T) { } var cipherKey = make([]byte, 64) +var cipherIv = make([]byte, 64) + +const CIPHER_BENCHMARK_BUFFER_LEN = 4096 func init() { for i := 0; i < len(cipherKey); i++ { cipherKey[i] = byte(i) } + io.ReadFull(rand.Reader, cipherIv) } func BenchmarkRC4Init(b *testing.B) { @@ -200,3 +206,124 @@ func BenchmarkRC4MD5Init(b *testing.B) { ci := cipherMethod["rc4-md5"] benchmarkCipherInit(b, ci) } + +func BenchmarkSalsa20Init(b *testing.B) { + ci := cipherMethod["salsa20"] + benchmarkCipherInit(b, ci) +} + +func benchmarkCipherEncrypt(b *testing.B, ci *cipherInfo) { + key := cipherKey[:ci.keyLen] + iv := cipherIv[:ci.ivLen] + enc, err := ci.newStream(key, iv, Encrypt) + if err != nil { + b.Error(err) + } + src := make([]byte, CIPHER_BENCHMARK_BUFFER_LEN) + dst := make([]byte, CIPHER_BENCHMARK_BUFFER_LEN) + io.ReadFull(rand.Reader, src) + for i := 0; i < b.N; i++ { + enc.XORKeyStream(dst, src) + } +} + +func BenchmarkAES128Encrypt(b *testing.B) { + ci := cipherMethod["aes-128-cfb"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkAES192Encrypt(b *testing.B) { + ci := cipherMethod["aes-192-cfb"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkAES256Encrypt(b *testing.B) { + ci := cipherMethod["aes-256-cfb"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkBlowFishEncrypt(b *testing.B) { + ci := cipherMethod["bf-cfb"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkCast5Encrypt(b *testing.B) { + ci := cipherMethod["bf-cfb"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkDESEncrypt(b *testing.B) { + ci := cipherMethod["des-cfb"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkRC4MD5Encrypt(b *testing.B) { + ci := cipherMethod["rc4-md5"] + benchmarkCipherEncrypt(b, ci) +} + +func BenchmarkSalsa20Encrypt(b *testing.B) { + ci := cipherMethod["salsa20"] + benchmarkCipherEncrypt(b, ci) +} + +func benchmarkCipherDecrypt(b *testing.B, ci *cipherInfo) { + key := cipherKey[:ci.keyLen] + iv := cipherIv[:ci.ivLen] + enc, err := ci.newStream(key, iv, Encrypt) + if err != nil { + b.Error(err) + } + dec, err := ci.newStream(key, iv, Decrypt) + if err != nil { + b.Error(err) + } + src := make([]byte, CIPHER_BENCHMARK_BUFFER_LEN) + dst := make([]byte, CIPHER_BENCHMARK_BUFFER_LEN) + io.ReadFull(rand.Reader, src) + enc.XORKeyStream(dst, src) + for i := 0; i < b.N; i++ { + dec.XORKeyStream(src, dst) + } +} + +func BenchmarkAES128Decrypt(b *testing.B) { + ci := cipherMethod["aes-128-cfb"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkAES192Decrypt(b *testing.B) { + ci := cipherMethod["aes-192-cfb"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkAES256Decrypt(b *testing.B) { + ci := cipherMethod["aes-256-cfb"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkBlowFishDecrypt(b *testing.B) { + ci := cipherMethod["bf-cfb"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkCast5Decrypt(b *testing.B) { + ci := cipherMethod["bf-cfb"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkDESDecrypt(b *testing.B) { + ci := cipherMethod["des-cfb"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkRC4MD5Decrypt(b *testing.B) { + ci := cipherMethod["rc4-md5"] + benchmarkCipherDecrypt(b, ci) +} + +func BenchmarkSalsa20Decrypt(b *testing.B) { + ci := cipherMethod["salsa20"] + benchmarkCipherDecrypt(b, ci) +} + From 0f58b477fa0068dc3fd845acf071aa8b36e4a685 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sat, 25 Apr 2015 22:34:10 +0800 Subject: [PATCH 10/18] Tweak encrypt benchmark code. --- shadowsocks/encrypt_test.go | 86 ++++++++++++++----------------------- 1 file changed, 32 insertions(+), 54 deletions(-) diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index ced22477..3be50540 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -1,10 +1,10 @@ package shadowsocks import ( + "crypto/rand" "crypto/rc4" - "reflect" "io" - "crypto/rand" + "reflect" "testing" ) @@ -164,7 +164,8 @@ func BenchmarkRC4Init(b *testing.B) { } } -func benchmarkCipherInit(b *testing.B, ci *cipherInfo) { +func benchmarkCipherInit(b *testing.B, method string) { + ci := cipherMethod[method] key := cipherKey[:ci.keyLen] buf := make([]byte, ci.ivLen) for i := 0; i < b.N; i++ { @@ -173,46 +174,39 @@ func benchmarkCipherInit(b *testing.B, ci *cipherInfo) { } func BenchmarkAES128Init(b *testing.B) { - ci := cipherMethod["aes-128-cfb"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "aes-128-cfb") } func BenchmarkAES192Init(b *testing.B) { - ci := cipherMethod["aes-192-cfb"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "aes-192-cfb") } func BenchmarkAES256Init(b *testing.B) { - ci := cipherMethod["aes-256-cfb"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "aes-256-cfb") } func BenchmarkBlowFishInit(b *testing.B) { - ci := cipherMethod["bf-cfb"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "bf-cfb") } func BenchmarkCast5Init(b *testing.B) { - ci := cipherMethod["bf-cfb"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "cast5-cfb") } func BenchmarkDESInit(b *testing.B) { - ci := cipherMethod["des-cfb"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "des-cfb") } func BenchmarkRC4MD5Init(b *testing.B) { - ci := cipherMethod["rc4-md5"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "rc4-md5") } func BenchmarkSalsa20Init(b *testing.B) { - ci := cipherMethod["salsa20"] - benchmarkCipherInit(b, ci) + benchmarkCipherInit(b, "salsa20") } -func benchmarkCipherEncrypt(b *testing.B, ci *cipherInfo) { +func benchmarkCipherEncrypt(b *testing.B, method string) { + ci := cipherMethod[method] key := cipherKey[:ci.keyLen] iv := cipherIv[:ci.ivLen] enc, err := ci.newStream(key, iv, Encrypt) @@ -228,46 +222,39 @@ func benchmarkCipherEncrypt(b *testing.B, ci *cipherInfo) { } func BenchmarkAES128Encrypt(b *testing.B) { - ci := cipherMethod["aes-128-cfb"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "aes-128-cfb") } func BenchmarkAES192Encrypt(b *testing.B) { - ci := cipherMethod["aes-192-cfb"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "aes-192-cfb") } func BenchmarkAES256Encrypt(b *testing.B) { - ci := cipherMethod["aes-256-cfb"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "aes-256-cfb") } func BenchmarkBlowFishEncrypt(b *testing.B) { - ci := cipherMethod["bf-cfb"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "bf-cfb") } func BenchmarkCast5Encrypt(b *testing.B) { - ci := cipherMethod["bf-cfb"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "bf-cfb") } func BenchmarkDESEncrypt(b *testing.B) { - ci := cipherMethod["des-cfb"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "des-cfb") } func BenchmarkRC4MD5Encrypt(b *testing.B) { - ci := cipherMethod["rc4-md5"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "rc4-md5") } func BenchmarkSalsa20Encrypt(b *testing.B) { - ci := cipherMethod["salsa20"] - benchmarkCipherEncrypt(b, ci) + benchmarkCipherEncrypt(b, "salsa20") } -func benchmarkCipherDecrypt(b *testing.B, ci *cipherInfo) { +func benchmarkCipherDecrypt(b *testing.B, method string) { + ci := cipherMethod[method] key := cipherKey[:ci.keyLen] iv := cipherIv[:ci.ivLen] enc, err := ci.newStream(key, iv, Encrypt) @@ -288,42 +275,33 @@ func benchmarkCipherDecrypt(b *testing.B, ci *cipherInfo) { } func BenchmarkAES128Decrypt(b *testing.B) { - ci := cipherMethod["aes-128-cfb"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "aes-128-cfb") } func BenchmarkAES192Decrypt(b *testing.B) { - ci := cipherMethod["aes-192-cfb"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "aes-192-cfb") } func BenchmarkAES256Decrypt(b *testing.B) { - ci := cipherMethod["aes-256-cfb"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "aes-256-cfb") } func BenchmarkBlowFishDecrypt(b *testing.B) { - ci := cipherMethod["bf-cfb"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "bf-cfb") } func BenchmarkCast5Decrypt(b *testing.B) { - ci := cipherMethod["bf-cfb"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "bf-cfb") } func BenchmarkDESDecrypt(b *testing.B) { - ci := cipherMethod["des-cfb"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "des-cfb") } func BenchmarkRC4MD5Decrypt(b *testing.B) { - ci := cipherMethod["rc4-md5"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "rc4-md5") } func BenchmarkSalsa20Decrypt(b *testing.B) { - ci := cipherMethod["salsa20"] - benchmarkCipherDecrypt(b, ci) + benchmarkCipherDecrypt(b, "salsa20") } - From 2241f28c578925fbc060ed931ca8c4bf0da60de4 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 26 Apr 2015 00:18:34 +0800 Subject: [PATCH 11/18] Add salsa20 encryption in test script. --- script/test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/script/test.sh b/script/test.sh index 6a0aafd2..021644d9 100755 --- a/script/test.sh +++ b/script/test.sh @@ -110,6 +110,7 @@ test_server_local_pair() { test_shadowsocks $url bf-cfb test_shadowsocks $url des-cfb test_shadowsocks $url cast5-cfb + test_shadowsocks $url salsa20 } start_http_server From a457853cd09a89eca41cd97d57f2f38a75728fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Fr=C3=B6ssman?= Date: Fri, 13 Feb 2015 12:42:21 +0100 Subject: [PATCH 12/18] Exit if port is not bindable --- cmd/shadowsocks-server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 6962b0b1..7214d956 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -261,7 +261,7 @@ func run(port, password string) { ln, err := net.Listen("tcp", ":"+port) if err != nil { log.Printf("error listening port %v: %v\n", port, err) - return + os.Exit(1) } passwdManager.add(port, password, ln) var cipher *ss.Cipher From c045c82e7ee3b7fc260b44abeb2544806407b64f Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 26 Apr 2015 00:44:50 +0800 Subject: [PATCH 13/18] Avoid create new slice for each Read and Write. --- shadowsocks/conn.go | 47 +++++++++++++++++++++++++++++++---------- shadowsocks/leakybuf.go | 5 +++++ shadowsocks/pipe.go | 9 ++------ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/shadowsocks/conn.go b/shadowsocks/conn.go index b0b58e75..eacb41fb 100644 --- a/shadowsocks/conn.go +++ b/shadowsocks/conn.go @@ -11,10 +11,22 @@ import ( type Conn struct { net.Conn *Cipher + readBuf []byte + writeBuf []byte } -func NewConn(cn net.Conn, cipher *Cipher) *Conn { - return &Conn{cn, cipher} +func NewConn(c net.Conn, cipher *Cipher) *Conn { + return &Conn{ + Conn: c, + Cipher: cipher, + readBuf: leakyBuf.Get(), + writeBuf: leakyBuf.Get()} +} + +func (c *Conn) Close() error { + leakyBuf.Put(c.readBuf) + leakyBuf.Put(c.writeBuf) + return c.Conn.Close() } func RawAddr(addr string) (buf []byte, err error) { @@ -72,7 +84,14 @@ func (c *Conn) Read(b []byte) (n int, err error) { return } } - cipherData := make([]byte, len(b)) + + cipherData := c.readBuf + if len(b) > len(cipherData) { + cipherData = make([]byte, len(b)) + } else { + cipherData = cipherData[:len(b)] + } + n, err = c.Conn.Read(cipherData) if n > 0 { c.decrypt(b[0:n], cipherData[0:n]) @@ -81,23 +100,29 @@ func (c *Conn) Read(b []byte) (n int, err error) { } func (c *Conn) Write(b []byte) (n int, err error) { - var cipherData []byte - dataStart := 0 + var iv []byte if c.enc == nil { - var iv []byte iv, err = c.initEncrypt() if err != nil { return } + } + + cipherData := c.writeBuf + dataSize := len(b) + len(iv) + if dataSize > len(cipherData) { + cipherData = make([]byte, dataSize) + } else { + cipherData = cipherData[:dataSize] + } + + if iv != nil { // Put initialization vector in buffer, do a single write to send both // iv and data. - cipherData = make([]byte, len(b)+len(iv)) copy(cipherData, iv) - dataStart = len(iv) - } else { - cipherData = make([]byte, len(b)) } - c.encrypt(cipherData[dataStart:], b) + + c.encrypt(cipherData[len(iv):], b) n, err = c.Conn.Write(cipherData) return } diff --git a/shadowsocks/leakybuf.go b/shadowsocks/leakybuf.go index 3c7dd8c5..f673cc3b 100644 --- a/shadowsocks/leakybuf.go +++ b/shadowsocks/leakybuf.go @@ -6,6 +6,11 @@ type LeakyBuf struct { freeList chan []byte } +const bufSize = 4096 +const maxNBuf = 2048 + +var leakyBuf = NewLeakyBuf(maxNBuf, bufSize) + // NewLeakyBuf creates a leaky buffer which can hold at most n buffer, each // with bufSize bytes. func NewLeakyBuf(n, bufSize int) *LeakyBuf { diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 7801a8fb..877853b9 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -17,16 +17,11 @@ func SetReadTimeout(c net.Conn) { } } -const bufSize = 4096 -const nBuf = 2048 - -var pipeBuf = NewLeakyBuf(nBuf, bufSize) - // PipeThenClose copies data from src to dst, closes dst when done. func PipeThenClose(src, dst net.Conn, timeoutOpt int) { defer dst.Close() - buf := pipeBuf.Get() - defer pipeBuf.Put(buf) + buf := leakyBuf.Get() + defer leakyBuf.Put(buf) for { if timeoutOpt == SET_TIMEOUT { SetReadTimeout(src) From 67ddbe40371a486c21666b7f2564d34f7c29aab7 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 26 Apr 2015 22:11:54 +0800 Subject: [PATCH 14/18] Reuse buffer when possible for salsa20 cipher. --- shadowsocks/encrypt.go | 35 +++++++++++++++++++++++++---------- shadowsocks/leakybuf.go | 4 ++-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index 6ddf131c..e0bdc145 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -2,9 +2,6 @@ package shadowsocks import ( "bytes" - "golang.org/x/crypto/blowfish" - "golang.org/x/crypto/cast5" - "golang.org/x/crypto/salsa20/salsa" "crypto/aes" "crypto/cipher" "crypto/des" @@ -13,6 +10,9 @@ import ( "crypto/rc4" "encoding/binary" "errors" + "golang.org/x/crypto/blowfish" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/salsa20/salsa" "io" ) @@ -139,20 +139,35 @@ func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { } type salsaStreamCipher struct { - nonce [8]byte - key [32]byte + nonce [8]byte + key [32]byte counter int } func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) { + var buf []byte + padLen := c.counter % 64 + dataSize := len(src) + padLen + if cap(dst) >= dataSize { + buf = dst[:dataSize] + } else if leakyBufSize >= dataSize { + buf = leakyBuf.Get() + defer leakyBuf.Put(buf) + buf = buf[:dataSize] + } else { + buf = make([]byte, dataSize) + } + var subNonce [16]byte - padlen := c.counter % 64 - buf := make([]byte, padlen+len(src)) - copy(buf[padlen:], src[:]) copy(subNonce[:], c.nonce[:]) - binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter / 64)) + binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64)) + + // It's difficult to avoid data copy here. src or dst maybe slice from + // Conn.Read/Write, which can't have padding. + copy(buf[padLen:], src[:]) salsa.XORKeyStream(buf, buf, &subNonce, &c.key) - copy(dst, buf[padlen:]) + copy(dst, buf[padLen:]) + c.counter += len(src) } diff --git a/shadowsocks/leakybuf.go b/shadowsocks/leakybuf.go index f673cc3b..3b55832d 100644 --- a/shadowsocks/leakybuf.go +++ b/shadowsocks/leakybuf.go @@ -6,10 +6,10 @@ type LeakyBuf struct { freeList chan []byte } -const bufSize = 4096 +const leakyBufSize = 4096 const maxNBuf = 2048 -var leakyBuf = NewLeakyBuf(maxNBuf, bufSize) +var leakyBuf = NewLeakyBuf(maxNBuf, leakyBufSize) // NewLeakyBuf creates a leaky buffer which can hold at most n buffer, each // with bufSize bytes. From 7aed8e85ef555067e861c5e542b665eca92193a4 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 26 Apr 2015 22:57:15 +0800 Subject: [PATCH 15/18] Add -t for local, always set timeout for data transfer. --- cmd/shadowsocks-local/local.go | 7 +++++-- cmd/shadowsocks-server/server.go | 9 +++------ shadowsocks/config.go | 3 +++ shadowsocks/pipe.go | 11 ++--------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 78d09957..03bd8efa 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -49,6 +49,7 @@ func handShake(conn net.Conn) (err error) { buf := make([]byte, 258) var n int + ss.SetReadTimeout(conn) // make sure we get the nmethod field if n, err = io.ReadAtLeast(conn, buf, idNmethod+1); err != nil { return @@ -92,6 +93,7 @@ func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) { // refer to getRequest in server.go for why set buffer size to 263 buf := make([]byte, 263) var n int + ss.SetReadTimeout(conn) // read till we get possible domain length field if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { return @@ -314,8 +316,8 @@ func handleConnection(conn net.Conn) { } }() - go ss.PipeThenClose(conn, remote, ss.NO_TIMEOUT) - ss.PipeThenClose(remote, conn, ss.NO_TIMEOUT) + go ss.PipeThenClose(conn, remote) + ss.PipeThenClose(remote, conn) closed = true debug.Println("closed connection to", addr) } @@ -354,6 +356,7 @@ func main() { flag.StringVar(&cmdLocal, "b", "", "local address, listen only to this address if specified") flag.StringVar(&cmdConfig.Password, "k", "", "password") flag.IntVar(&cmdConfig.ServerPort, "p", 0, "server port") + flag.IntVar(&cmdConfig.Timeout, "t", 300, "timeout in seconds") flag.IntVar(&cmdConfig.LocalPort, "l", 0, "local socks5 proxy port") flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb") flag.BoolVar((*bool)(&debug), "d", false, "print debug message") diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 7214d956..982fcaf3 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -19,8 +19,6 @@ import ( var debug ss.DebugLog -const dnsGoroutineNum = 64 - func getRequest(conn *ss.Conn) (host string, extra []byte, err error) { const ( idType = 0 // address type index @@ -62,7 +60,6 @@ func getRequest(conn *ss.Conn) (host string, extra []byte, err error) { } if n < reqLen { // rare case - ss.SetReadTimeout(conn) if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { return } @@ -154,8 +151,8 @@ func handleConnection(conn *ss.Conn) { if debug { debug.Printf("piping %s<->%s", conn.RemoteAddr(), host) } - go ss.PipeThenClose(conn, remote, ss.SET_TIMEOUT) - ss.PipeThenClose(remote, conn, ss.NO_TIMEOUT) + go ss.PipeThenClose(conn, remote) + ss.PipeThenClose(remote, conn) closed = true return } @@ -321,7 +318,7 @@ func main() { flag.StringVar(&configFile, "c", "config.json", "specify config file") flag.StringVar(&cmdConfig.Password, "k", "", "password") flag.IntVar(&cmdConfig.ServerPort, "p", 0, "server port") - flag.IntVar(&cmdConfig.Timeout, "t", 60, "connection timeout (in seconds)") + flag.IntVar(&cmdConfig.Timeout, "t", 300, "timeout in seconds") flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb") flag.IntVar(&core, "core", 0, "maximum number of CPU cores to use, default is determinied by Go runtime") flag.BoolVar((*bool)(&debug), "d", false, "print debug message") diff --git a/shadowsocks/config.go b/shadowsocks/config.go index f96ff504..9d3696b4 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -127,4 +127,7 @@ func UpdateConfig(old, new *Config) { if old.Method == "table" { old.Method = "" } + + old.Timeout = new.Timeout + readTimeout = time.Duration(old.Timeout) * time.Second } diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 877853b9..515c99f8 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -6,11 +6,6 @@ import ( "time" ) -const ( - NO_TIMEOUT = iota - SET_TIMEOUT -) - func SetReadTimeout(c net.Conn) { if readTimeout != 0 { c.SetReadDeadline(time.Now().Add(readTimeout)) @@ -18,14 +13,12 @@ func SetReadTimeout(c net.Conn) { } // PipeThenClose copies data from src to dst, closes dst when done. -func PipeThenClose(src, dst net.Conn, timeoutOpt int) { +func PipeThenClose(src, dst net.Conn) { defer dst.Close() buf := leakyBuf.Get() defer leakyBuf.Put(buf) for { - if timeoutOpt == SET_TIMEOUT { - SetReadTimeout(src) - } + SetReadTimeout(src) n, err := src.Read(buf) // read may return EOF with n > 0 // should always process n > 0 bytes before handling error From 6b502a4abe6d3a9ff0ef17b08586bf3e5763aefb Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 10 May 2015 22:23:12 +0800 Subject: [PATCH 16/18] Add compatibity test for chacha20. --- script/test.sh | 1 + shadowsocks/encrypt_test.go | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/script/test.sh b/script/test.sh index 021644d9..f9b46169 100755 --- a/script/test.sh +++ b/script/test.sh @@ -110,6 +110,7 @@ test_server_local_pair() { test_shadowsocks $url bf-cfb test_shadowsocks $url des-cfb test_shadowsocks $url cast5-cfb + test_shadowsocks $url chacha20 test_shadowsocks $url salsa20 } diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index 58f7e8c7..24da305e 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -205,6 +205,10 @@ func BenchmarkRC4MD5Init(b *testing.B) { benchmarkCipherInit(b, "rc4-md5") } +func BenchmarkChaCha20Init(b *testing.B) { + benchmarkCipherInit(b, "chacha20") +} + func BenchmarkSalsa20Init(b *testing.B) { benchmarkCipherInit(b, "salsa20") } @@ -253,6 +257,10 @@ func BenchmarkRC4MD5Encrypt(b *testing.B) { benchmarkCipherEncrypt(b, "rc4-md5") } +func BenchmarkChacha20Encrypt(b *testing.B) { + benchmarkCipherEncrypt(b, "chacha20") +} + func BenchmarkSalsa20Encrypt(b *testing.B) { benchmarkCipherEncrypt(b, "salsa20") } @@ -306,11 +314,10 @@ func BenchmarkRC4MD5Decrypt(b *testing.B) { benchmarkCipherDecrypt(b, "rc4-md5") } -func BenchmarkSalsa20Decrypt(b *testing.B) { - benchmarkCipherDecrypt(b, "salsa20") +func BenchmarkChaCha20Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "chacha20") } -func BenchmarkChaCha20Init(b *testing.B) { - ci := cipherMethod["chacha20"] - benchmarkCipherInit(b, ci) +func BenchmarkSalsa20Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "salsa20") } From 0cb0001fa237d6b1b4970c589863be6fb92b07eb Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 10 May 2015 22:27:26 +0800 Subject: [PATCH 17/18] Use go 1.4.2 in travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 06381d15..2cc770b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.3 + - 1.4.2 install: - go get golang.org/x/crypto/blowfish - go get golang.org/x/crypto/cast5 From d4a6a1223c87e2b8f1e6568b187af5d0bd93d5b3 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Sun, 10 May 2015 20:40:26 +0800 Subject: [PATCH 18/18] Bump version to 1.1.4 --- .gitignore | 1 + CHANGELOG | 7 +++++++ README.md | 4 ++-- shadowsocks/util.go | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c00df136..5966b0f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.deb +script/http diff --git a/CHANGELOG b/CHANGELOG index 07fb962e..9558b98c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +1.1.4 (2015-05-10) + * Support "chacha20" encryption method, thanks to @defia + * Support "salsa20" encryption method, thanks to @genzj + * Fix go 1.4 canonical import paths, thanks to @ddatsh + * Exit if port not bindable, thanks to @thomasf + * More buffer reuse + 1.1.3 (2014-09-28) * Fix can't specify encryption method in config file diff --git a/README.md b/README.md index 4939dd0b..f3736a38 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # shadowsocks-go -Current version: 1.1.3 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=develop)](https://travis-ci.org/shadowsocks/shadowsocks-go) +Current version: 1.1.4 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=develop)](https://travis-ci.org/shadowsocks/shadowsocks-go) shadowsocks-go is a lightweight tunnel proxy which can help you get through firewalls. It is a port of [shadowsocks](https://github.com/clowwindy/shadowsocks). @@ -36,7 +36,7 @@ server your server ip or hostname server_port server port local_port local socks5 proxy port method encryption method, null by default (table), the following methods are supported: - aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, rc4, table + aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, chacha20, salsa20, rc4, table password a password used to encrypt transfer timeout server option, in seconds ``` diff --git a/shadowsocks/util.go b/shadowsocks/util.go index 732a5a57..2c5198d2 100644 --- a/shadowsocks/util.go +++ b/shadowsocks/util.go @@ -7,7 +7,7 @@ import ( ) func PrintVersion() { - const version = "1.1.3" + const version = "1.1.4" fmt.Println("shadowsocks-go version", version) }