diff --git a/.gitignore b/.gitignore index c00df136..5966b0f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.deb +script/http diff --git a/.travis.yml b/.travis.yml index c68e325b..2cc770b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: go go: - - 1.3 + - 1.4.2 install: - go get golang.org/x/crypto/blowfish - go get golang.org/x/crypto/cast5 + - go get golang.org/x/crypto/salsa20 + - go get github.com/codahale/chacha20 - go install ./cmd/shadowsocks-local - go install ./cmd/shadowsocks-server script: 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 79f287a3..7cfc02e3 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=master)](https://travis-ci.org/shadowsocks/shadowsocks-go) +Current version: 1.1.4 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=master)](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/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 6962b0b1..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 } @@ -261,7 +258,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 @@ -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/script/test.sh b/script/test.sh index 1e2cf768..f9b46169 100755 --- a/script/test.sh +++ b/script/test.sh @@ -110,6 +110,8 @@ 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 } start_http_server @@ -119,7 +121,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 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/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/encrypt.go b/shadowsocks/encrypt.go index 3dbb6735..5a79a25f 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" @@ -12,6 +10,10 @@ import ( "crypto/rc4" "encoding/binary" "errors" + "github.com/codahale/chacha20" + "golang.org/x/crypto/blowfish" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/salsa20/salsa" "io" ) @@ -137,6 +139,50 @@ 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 salsaStreamCipher struct { + 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 + copy(subNonce[:], c.nonce[:]) + 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:]) + + 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 @@ -144,6 +190,8 @@ type cipherInfo struct { } var cipherMethod = map[string]*cipherInfo{ + "rc4": {16, 0, nil}, + "table": {16, 0, nil}, "aes-128-cfb": {16, 16, newAESStream}, "aes-192-cfb": {24, 16, newAESStream}, "aes-256-cfb": {32, 16, newAESStream}, @@ -151,8 +199,8 @@ var cipherMethod = map[string]*cipherInfo{ "bf-cfb": {16, 8, newBlowFishStream}, "cast5-cfb": {16, 8, newCast5Stream}, "rc4-md5": {16, 16, newRC4MD5Stream}, - "rc4": {16, 0, nil}, - "table": {16, 0, nil}, + "chacha20": {32, 8, newChaCha20Stream}, + "salsa20": {32, 8, newSalsa20Stream}, } func CheckCipherMethod(method string) error { diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index b2583d3a..24da305e 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -1,7 +1,9 @@ package shadowsocks import ( + "crypto/rand" "crypto/rc4" + "io" "reflect" "testing" ) @@ -139,12 +141,24 @@ func TestDES(t *testing.T) { testBlockCipher(t, "des-cfb") } +func TestRC4MD5(t *testing.T) { + testBlockCipher(t, "rc4-md5") +} + +func TestChaCha20(t *testing.T) { + testBlockCipher(t, "chacha20") +} + 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) { @@ -154,39 +168,156 @@ 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++ { - ci.newBlock(key) + ci.newStream(key, buf, Encrypt) } } 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) { + benchmarkCipherInit(b, "rc4-md5") +} + +func BenchmarkChaCha20Init(b *testing.B) { + benchmarkCipherInit(b, "chacha20") +} + +func BenchmarkSalsa20Init(b *testing.B) { + benchmarkCipherInit(b, "salsa20") +} + +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) + 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) { + benchmarkCipherEncrypt(b, "aes-128-cfb") +} + +func BenchmarkAES192Encrypt(b *testing.B) { + benchmarkCipherEncrypt(b, "aes-192-cfb") +} + +func BenchmarkAES256Encrypt(b *testing.B) { + benchmarkCipherEncrypt(b, "aes-256-cfb") +} + +func BenchmarkBlowFishEncrypt(b *testing.B) { + benchmarkCipherEncrypt(b, "bf-cfb") +} + +func BenchmarkCast5Encrypt(b *testing.B) { + benchmarkCipherEncrypt(b, "bf-cfb") +} + +func BenchmarkDESEncrypt(b *testing.B) { + benchmarkCipherEncrypt(b, "des-cfb") +} + +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") +} + +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) + 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) { + benchmarkCipherDecrypt(b, "aes-128-cfb") +} + +func BenchmarkAES192Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "aes-192-cfb") +} + +func BenchmarkAES256Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "aes-256-cfb") +} + +func BenchmarkBlowFishDecrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "bf-cfb") +} + +func BenchmarkCast5Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "bf-cfb") +} + +func BenchmarkDESDecrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "des-cfb") +} + +func BenchmarkRC4MD5Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "rc4-md5") +} + +func BenchmarkChaCha20Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "chacha20") +} + +func BenchmarkSalsa20Decrypt(b *testing.B) { + benchmarkCipherDecrypt(b, "salsa20") } diff --git a/shadowsocks/leakybuf.go b/shadowsocks/leakybuf.go index 3c7dd8c5..3b55832d 100644 --- a/shadowsocks/leakybuf.go +++ b/shadowsocks/leakybuf.go @@ -6,6 +6,11 @@ type LeakyBuf struct { freeList chan []byte } +const leakyBufSize = 4096 +const maxNBuf = 2048 + +var leakyBuf = NewLeakyBuf(maxNBuf, leakyBufSize) + // 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 35f356f6..515c99f8 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -6,36 +6,25 @@ import ( "time" ) -const ( - NO_TIMEOUT = iota - SET_TIMEOUT -) - func SetReadTimeout(c net.Conn) { if readTimeout != 0 { c.SetReadDeadline(time.Now().Add(readTimeout)) } } -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) { +func PipeThenClose(src, dst net.Conn) { defer dst.Close() - buf := pipeBuf.Get() - defer pipeBuf.Put(buf) + 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 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 } 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) }