From 476b93cf9b5359e13b46bf498d1c420254769b6d Mon Sep 17 00:00:00 2001 From: orvice Date: Fri, 19 Aug 2016 00:22:24 +0800 Subject: [PATCH] add shadowsocks --- shadowsocks/config.go | 136 +++++++++ shadowsocks/config_test.go | 107 +++++++ shadowsocks/conn.go | 178 ++++++++++++ shadowsocks/encrypt.go | 261 ++++++++++++++++++ shadowsocks/encrypt_test.go | 254 +++++++++++++++++ shadowsocks/leakybuf.go | 45 +++ shadowsocks/log.go | 24 ++ shadowsocks/mergesort.go | 32 +++ shadowsocks/pipe.go | 102 +++++++ .../deprecated-client-multi-server.json | 7 + shadowsocks/testdata/noserver.json | 7 + shadowsocks/util.go | 60 ++++ 12 files changed, 1213 insertions(+) create mode 100644 shadowsocks/config.go create mode 100644 shadowsocks/config_test.go create mode 100644 shadowsocks/conn.go create mode 100644 shadowsocks/encrypt.go create mode 100644 shadowsocks/encrypt_test.go create mode 100644 shadowsocks/leakybuf.go create mode 100644 shadowsocks/log.go create mode 100644 shadowsocks/mergesort.go create mode 100644 shadowsocks/pipe.go create mode 100644 shadowsocks/testdata/deprecated-client-multi-server.json create mode 100644 shadowsocks/testdata/noserver.json create mode 100644 shadowsocks/util.go diff --git a/shadowsocks/config.go b/shadowsocks/config.go new file mode 100644 index 00000000..7794d9fa --- /dev/null +++ b/shadowsocks/config.go @@ -0,0 +1,136 @@ +/** + * Created with IntelliJ IDEA. + * User: clowwindy + * Date: 12-11-2 + * Time: 上午10:31 + * To change this template use File | Settings | File Templates. + */ +package shadowsocks + +import ( + "encoding/json" + "fmt" + "io/ioutil" + // "log" + "os" + "reflect" + "time" + "strings" +) + +type Config struct { + Server interface{} `json:"server"` + ServerPort int `json:"server_port"` + LocalPort int `json:"local_port"` + Password string `json:"password"` + Method string `json:"method"` // encryption method + Auth bool `json:"auth"` // one time auth + + // following options are only used by server + PortPassword map[string]string `json:"port_password"` + Timeout int `json:"timeout"` + + // following options are only used by client + + // The order of servers in the client config is significant, so use array + // instead of map to preserve the order. + ServerPassword [][]string `json:"server_password"` +} + +var readTimeout time.Duration + +func (config *Config) GetServerArray() []string { + // Specifying multiple servers in the "server" options is deprecated. + // But for backward compatiblity, keep this. + if config.Server == nil { + return nil + } + single, ok := config.Server.(string) + if ok { + return []string{single} + } + arr, ok := config.Server.([]interface{}) + if ok { + /* + if len(arr) > 1 { + log.Println("Multiple servers in \"server\" option is deprecated. " + + "Please use \"server_password\" instead.") + } + */ + serverArr := make([]string, len(arr), len(arr)) + for i, s := range arr { + serverArr[i], ok = s.(string) + if !ok { + goto typeError + } + } + return serverArr + } +typeError: + panic(fmt.Sprintf("Config.Server type error %v", reflect.TypeOf(config.Server))) +} + +func ParseConfig(path string) (config *Config, err error) { + file, err := os.Open(path) // For read access. + if err != nil { + return + } + defer file.Close() + + data, err := ioutil.ReadAll(file) + if err != nil { + return + } + + config = &Config{} + if err = json.Unmarshal(data, config); err != nil { + return nil, err + } + readTimeout = time.Duration(config.Timeout) * time.Second + if strings.HasSuffix(strings.ToLower(config.Method), "-ota") { + config.Method = config.Method[:len(config.Method) - 4] + config.Auth = true + } + return +} + +func SetDebug(d DebugLog) { + Debug = d +} + +// Useful for command line to override options specified in config file +// Debug is not updated. +func UpdateConfig(old, new *Config) { + // Using reflection here is not necessary, but it's a good exercise. + // For more information on reflections in Go, read "The Laws of Reflection" + // http://golang.org/doc/articles/laws_of_reflection.html + newVal := reflect.ValueOf(new).Elem() + oldVal := reflect.ValueOf(old).Elem() + + // typeOfT := newVal.Type() + for i := 0; i < newVal.NumField(); i++ { + newField := newVal.Field(i) + oldField := oldVal.Field(i) + // log.Printf("%d: %s %s = %v\n", i, + // typeOfT.Field(i).Name, newField.Type(), newField.Interface()) + switch newField.Kind() { + case reflect.Interface: + if fmt.Sprintf("%v", newField.Interface()) != "" { + oldField.Set(newField) + } + case reflect.String: + s := newField.String() + if s != "" { + oldField.SetString(s) + } + case reflect.Int: + i := newField.Int() + if i != 0 { + oldField.SetInt(i) + } + } + } + + old.Timeout = new.Timeout + readTimeout = time.Duration(old.Timeout) * time.Second +} diff --git a/shadowsocks/config_test.go b/shadowsocks/config_test.go new file mode 100644 index 00000000..1dcef0cf --- /dev/null +++ b/shadowsocks/config_test.go @@ -0,0 +1,107 @@ +package shadowsocks + +import ( + "testing" +) + +func TestConfigJson(t *testing.T) { + config, err := ParseConfig("../config.json") + if err != nil { + t.Fatal("error parsing config.json:", err) + } + + if config.Password != "barfoo!" { + t.Error("wrong password from config") + } + if config.Timeout != 600 { + t.Error("timeout should be 600") + } + if config.Method != "aes-128-cfb" { + t.Error("method should be aes-128-cfb") + } + srvArr := config.GetServerArray() + if len(srvArr) != 1 || srvArr[0] != "127.0.0.1" { + t.Error("server option is not set correctly") + } +} + +func TestServerMultiPort(t *testing.T) { + config, err := ParseConfig("../sample-config/server-multi-port.json") + if err != nil { + t.Fatal("error parsing ../sample-config/server-multi-port.json:", err) + } + + if config.PortPassword["8387"] != "foobar" { + t.Error("wrong multiple password for port 8387") + } + if config.PortPassword["8388"] != "barfoo" { + t.Error("wrong multiple password for port 8388") + } + if config.PortPassword["8389"] != "" { + t.Error("should have no password for port 8389") + } +} + +func TestDeprecatedClientMultiServerArray(t *testing.T) { + // This form of config is deprecated. Provided only for backward compatibility. + config, err := ParseConfig("testdata/deprecated-client-multi-server.json") + if err != nil { + t.Fatal("error parsing deprecated-client-multi-server.json:", err) + } + + srvArr := config.GetServerArray() + if len(srvArr) != 2 { + t.Error("server option is not set correctly") + } + if srvArr[0] != "127.0.0.1" { + t.Errorf("1st server wrong, got %v", srvArr[0]) + } + if srvArr[1] != "127.0.1.1" { + t.Errorf("2nd server wrong, got %v", srvArr[0]) + } +} + +func TestClientMultiServerArray(t *testing.T) { + config, err := ParseConfig("../sample-config/client-multi-server.json") + if err != nil { + t.Fatal("error parsing client-multi-server.json:", err) + } + + sv := config.ServerPassword[0] + if len(sv) != 2 { + t.Fatalf("server_password 1st server wrong, have %d items\n", len(sv[0])) + } + if sv[0] != "127.0.0.1:8387" { + t.Error("server_password 1st server wrong") + } + if sv[1] != "foobar" { + t.Error("server_password 1st server passwd wrong") + } + + sv = config.ServerPassword[1] + if len(sv) != 3 { + t.Fatalf("server_password 2nd server wrong, have %d items\n", len(sv[0])) + } + if sv[0] != "127.0.0.1:8388" { + t.Error("server_password 2nd server wrong") + } + if sv[1] != "barfoo" { + t.Error("server_password 2nd server passwd wrong") + } + if sv[2] != "aes-128-cfb" { + t.Error("server_password 2nd server enc method wrong") + } +} + +func TestParseConfigEmpty(t *testing.T) { + // make sure we will not crash + config, err := ParseConfig("testdata/noserver.json") + if err != nil { + t.Fatal("error parsing noserver config:", err) + } + + srvArr := config.GetServerArray() + if srvArr != nil { + t.Error("GetServerArray should return nil if no server option is given") + } +} diff --git a/shadowsocks/conn.go b/shadowsocks/conn.go new file mode 100644 index 00000000..2069f9f8 --- /dev/null +++ b/shadowsocks/conn.go @@ -0,0 +1,178 @@ +package shadowsocks + +import ( + "encoding/binary" + "fmt" + "io" + "net" + "strconv" +) + +const ( + OneTimeAuthMask byte = 0x10 + AddrMask byte = 0xf +) + +type Conn struct { + net.Conn + *Cipher + readBuf []byte + writeBuf []byte + chunkId uint32 +} + +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) { + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, fmt.Errorf("shadowsocks: address error %s %v", addr, err) + } + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("shadowsocks: invalid port %s", addr) + } + + hostLen := len(host) + l := 1 + 1 + hostLen + 2 // addrType + lenByte + address + port + buf = make([]byte, l) + buf[0] = 3 // 3 means the address is domain name + buf[1] = byte(hostLen) // host address length followed by host address + copy(buf[2:], host) + binary.BigEndian.PutUint16(buf[2+hostLen:2+hostLen+2], uint16(port)) + return +} + +// This is intended for use by users implementing a local socks proxy. +// rawaddr shoud contain part of the data in socks request, starting from the +// ATYP field. (Refer to rfc1928 for more information.) +func DialWithRawAddr(rawaddr []byte, server string, cipher *Cipher) (c *Conn, err error) { + conn, err := net.Dial("tcp", server) + if err != nil { + return + } + c = NewConn(conn, cipher) + if cipher.ota { + if c.enc == nil { + if _, err = c.initEncrypt(); err != nil { + return + } + } + // since we have initEncrypt, we must send iv manually + conn.Write(cipher.iv) + rawaddr[0] |= OneTimeAuthMask + rawaddr = otaConnectAuth(cipher.iv, cipher.key, rawaddr) + } + if _, err = c.write(rawaddr); err != nil { + c.Close() + return nil, err + } + return +} + +// addr should be in the form of host:port +func Dial(addr, server string, cipher *Cipher) (c *Conn, err error) { + ra, err := RawAddr(addr) + if err != nil { + return + } + return DialWithRawAddr(ra, server, cipher) +} + +func (c *Conn) GetIv() (iv []byte) { + iv = make([]byte, len(c.iv)) + copy(iv, c.iv) + return +} + +func (c *Conn) GetKey() (key []byte) { + key = make([]byte, len(c.key)) + copy(key, c.key) + return +} + +func (c *Conn) IsOta() bool { + return c.ota +} + +func (c *Conn) GetAndIncrChunkId() (chunkId uint32) { + chunkId = c.chunkId + c.chunkId += 1 + return +} + +func (c *Conn) Read(b []byte) (n int, err error) { + if c.dec == nil { + iv := make([]byte, c.info.ivLen) + if _, err = io.ReadFull(c.Conn, iv); err != nil { + return + } + if err = c.initDecrypt(iv); err != nil { + return + } + if len(c.iv) == 0 { + c.iv = iv + } + } + + 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]) + } + return +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.ota { + chunkId := c.GetAndIncrChunkId() + b = otaReqChunkAuth(c.iv, chunkId, b) + } + return c.write(b) +} + +func (c *Conn) write(b []byte) (n int, err error) { + var iv []byte + if c.enc == nil { + 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. + copy(cipherData, iv) + } + + c.encrypt(cipherData[len(iv):], b) + n, err = c.Conn.Write(cipherData) + return +} diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go new file mode 100644 index 00000000..ea23e759 --- /dev/null +++ b/shadowsocks/encrypt.go @@ -0,0 +1,261 @@ +package shadowsocks + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/md5" + "crypto/rand" + "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" + "strings" +) + +var errEmptyPassword = errors.New("empty key") + +func md5sum(d []byte) []byte { + h := md5.New() + h.Write(d) + return h.Sum(nil) +} + +func evpBytesToKey(password string, keyLen int) (key []byte) { + const md5Len = 16 + + cnt := (keyLen-1)/md5Len + 1 + m := make([]byte, cnt*md5Len) + copy(m, md5sum([]byte(password))) + + // Repeatedly call md5 until bytes generated is enough. + // Each call to md5 uses data: prev md5 sum + password. + d := make([]byte, md5Len+len(password)) + start := 0 + for i := 1; i < cnt; i++ { + start += md5Len + copy(d, m[start-md5Len:start]) + copy(d[md5Len:], password) + copy(m[start:], md5sum(d)) + } + return m[:keyLen] +} + +type DecOrEnc int + +const ( + Decrypt DecOrEnc = iota + Encrypt +) + +func newStream(block cipher.Block, err error, key, iv []byte, + doe DecOrEnc) (cipher.Stream, error) { + if err != nil { + return nil, err + } + if doe == Encrypt { + return cipher.NewCFBEncrypter(block, iv), nil + } else { + return cipher.NewCFBDecrypter(block, iv), nil + } +} + +func newAESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { + block, err := aes.NewCipher(key) + return newStream(block, err, key, iv, doe) +} + +func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { + block, err := des.NewCipher(key) + return newStream(block, err, key, iv, doe) +} + +func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { + block, err := blowfish.NewCipher(key) + return newStream(block, err, key, iv, doe) +} + +func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { + block, err := cast5.NewCipher(key) + return newStream(block, err, key, iv, doe) +} + +func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { + h := md5.New() + h.Write(key) + h.Write(iv) + rc4key := h.Sum(nil) + + 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 + newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) +} + +var cipherMethod = map[string]*cipherInfo{ + "aes-128-cfb": {16, 16, newAESStream}, + "aes-192-cfb": {24, 16, newAESStream}, + "aes-256-cfb": {32, 16, newAESStream}, + "des-cfb": {8, 8, newDESStream}, + "bf-cfb": {16, 8, newBlowFishStream}, + "cast5-cfb": {16, 8, newCast5Stream}, + "rc4-md5": {16, 16, newRC4MD5Stream}, + "chacha20": {32, 8, newChaCha20Stream}, + "salsa20": {32, 8, newSalsa20Stream}, +} + +func CheckCipherMethod(method string) error { + if method == "" { + method = "aes-256-cfb" + } + _, ok := cipherMethod[method] + if !ok { + return errors.New("Unsupported encryption method: " + method) + } + return nil +} + +type Cipher struct { + enc cipher.Stream + dec cipher.Stream + key []byte + info *cipherInfo + ota bool // one-time auth + iv []byte +} + +// NewCipher creates a cipher that can be used in Dial() etc. +// Use cipher.Copy() to create a new cipher with the same method and password +// to avoid the cost of repeated cipher initialization. +func NewCipher(method, password string) (c *Cipher, err error) { + if password == "" { + return nil, errEmptyPassword + } + var ota bool + if strings.HasSuffix(strings.ToLower(method), "-ota") { + method = method[:len(method) - 4] // len("-ota") = 4 + ota = true + } else { + ota = false + } + mi, ok := cipherMethod[method] + if !ok { + return nil, errors.New("Unsupported encryption method: " + method) + } + + key := evpBytesToKey(password, mi.keyLen) + + c = &Cipher{key: key, info: mi} + + if err != nil { + return nil, err + } + c.ota = ota + return c, nil +} + +// Initializes the block cipher with CFB mode, returns IV. +func (c *Cipher) initEncrypt() (iv []byte, err error) { + if c.iv == nil { + iv = make([]byte, c.info.ivLen) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + c.iv = iv + } else { + iv = c.iv + } + if c.enc == nil { + c.enc, err = c.info.newStream(c.key, iv, Encrypt) + if err != nil { + return nil, err + } + } + return +} + +func (c *Cipher) initDecrypt(iv []byte) (err error) { + c.dec, err = c.info.newStream(c.key, iv, Decrypt) + return +} + +func (c *Cipher) encrypt(dst, src []byte) { + c.enc.XORKeyStream(dst, src) +} + +func (c *Cipher) decrypt(dst, src []byte) { + c.dec.XORKeyStream(dst, src) +} + +// Copy creates a new cipher at it's initial state. +func (c *Cipher) Copy() *Cipher { + // This optimization maybe not necessary. But without this function, we + // need to maintain a table cache for newTableCipher and use lock to + // protect concurrent access to that cache. + + // AES and DES ciphers does not return specific types, so it's difficult + // to create copy. But their initizliation time is less than 4000ns on my + // 2.26 GHz Intel Core 2 Duo processor. So no need to worry. + + // Currently, blow-fish and cast5 initialization cost is an order of + // maganitude slower than other ciphers. (I'm not sure whether this is + // because the current implementation is not highly optimized, or this is + // the nature of the algorithm.) + + nc := *c + nc.enc = nil + nc.dec = nil + nc.ota = c.ota + return &nc +} diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go new file mode 100644 index 00000000..9ff24286 --- /dev/null +++ b/shadowsocks/encrypt_test.go @@ -0,0 +1,254 @@ +package shadowsocks + +import ( + "crypto/rand" + "io" + "reflect" + "testing" +) + +const text = "Don't tell me the moon is shining; show me the glint of light on broken glass." + +func testCiphter(t *testing.T, c *Cipher, msg string) { + n := len(text) + cipherBuf := make([]byte, n) + originTxt := make([]byte, n) + + c.encrypt(cipherBuf, []byte(text)) + c.decrypt(originTxt, cipherBuf) + + if string(originTxt) != text { + t.Error(msg, "encrypt then decrytp does not get original text") + } +} + +func TestEvpBytesToKey(t *testing.T) { + // key, iv := evpBytesToKey("foobar", 32, 16) + key := evpBytesToKey("foobar", 32) + keyTarget := []byte{0x38, 0x58, 0xf6, 0x22, 0x30, 0xac, 0x3c, 0x91, 0x5f, 0x30, 0x0c, 0x66, 0x43, 0x12, 0xc6, 0x3f, 0x56, 0x83, 0x78, 0x52, 0x96, 0x14, 0xd2, 0x2d, 0xdb, 0x49, 0x23, 0x7d, 0x2f, 0x60, 0xbf, 0xdf} + // ivTarget := []byte{0x0e, 0xbf, 0x58, 0x78, 0xe8, 0x2a, 0xf7, 0xda, 0x61, 0x8e, 0xd5, 0x6f, 0xc6, 0x7d, 0x4a, 0xb7} + if !reflect.DeepEqual(key, keyTarget) { + t.Errorf("key not correct\n\texpect: %v\n\tgot: %v\n", keyTarget, key) + } + // if !reflect.DeepEqual(iv, ivTarget) { + // t.Errorf("iv not correct\n\texpect: %v\n\tgot: %v\n", ivTarget, iv) + // } +} + +func testBlockCipher(t *testing.T, method string) { + var cipher *Cipher + var err error + + cipher, err = NewCipher(method, "foobar") + if err != nil { + t.Fatal(method, "NewCipher:", err) + } + cipherCopy := cipher.Copy() + iv, err := cipher.initEncrypt() + if err != nil { + t.Error(method, "initEncrypt:", err) + } + if err = cipher.initDecrypt(iv); err != nil { + t.Error(method, "initDecrypt:", err) + } + testCiphter(t, cipher, method) + + iv, err = cipherCopy.initEncrypt() + if err != nil { + t.Error(method, "copy initEncrypt:", err) + } + if err = cipherCopy.initDecrypt(iv); err != nil { + t.Error(method, "copy initDecrypt:", err) + } + testCiphter(t, cipherCopy, method+" copy") +} + +func TestAES128(t *testing.T) { + testBlockCipher(t, "aes-128-cfb") +} + +func TestAES192(t *testing.T) { + testBlockCipher(t, "aes-192-cfb") +} + +func TestAES256(t *testing.T) { + testBlockCipher(t, "aes-256-cfb") +} + +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 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.newStream(key, buf, Encrypt) + } +} + +func BenchmarkAES128Init(b *testing.B) { + benchmarkCipherInit(b, "aes-128-cfb") +} + +func BenchmarkAES192Init(b *testing.B) { + benchmarkCipherInit(b, "aes-192-cfb") +} + +func BenchmarkAES256Init(b *testing.B) { + benchmarkCipherInit(b, "aes-256-cfb") +} + +func BenchmarkBlowFishInit(b *testing.B) { + benchmarkCipherInit(b, "bf-cfb") +} + +func BenchmarkCast5Init(b *testing.B) { + benchmarkCipherInit(b, "cast5-cfb") +} + +func BenchmarkDESInit(b *testing.B) { + 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, "cast5-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, "cast5-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 new file mode 100644 index 00000000..b6922eb9 --- /dev/null +++ b/shadowsocks/leakybuf.go @@ -0,0 +1,45 @@ +// Provides leaky buffer, based on the example in Effective Go. +package shadowsocks + +type LeakyBuf struct { + bufSize int // size of each buffer + freeList chan []byte +} + +const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(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 { + return &LeakyBuf{ + bufSize: bufSize, + freeList: make(chan []byte, n), + } +} + +// Get returns a buffer from the leaky buffer or create a new buffer. +func (lb *LeakyBuf) Get() (b []byte) { + select { + case b = <-lb.freeList: + default: + b = make([]byte, lb.bufSize) + } + return +} + +// Put add the buffer into the free buffer pool for reuse. Panic if the buffer +// size is not the same with the leaky buffer's. This is intended to expose +// error usage of leaky buffer. +func (lb *LeakyBuf) Put(b []byte) { + if len(b) != lb.bufSize { + panic("invalid buffer size that's put into leaky buffer") + } + select { + case lb.freeList <- b: + default: + } + return +} diff --git a/shadowsocks/log.go b/shadowsocks/log.go new file mode 100644 index 00000000..5b1c960e --- /dev/null +++ b/shadowsocks/log.go @@ -0,0 +1,24 @@ +package shadowsocks + +import ( + "log" + "os" +) + +type DebugLog bool + +var Debug DebugLog + +var dbgLog = log.New(os.Stdout, "[DEBUG] ", log.Ltime) + +func (d DebugLog) Printf(format string, args ...interface{}) { + if d { + dbgLog.Printf(format, args...) + } +} + +func (d DebugLog) Println(args ...interface{}) { + if d { + dbgLog.Println(args...) + } +} diff --git a/shadowsocks/mergesort.go b/shadowsocks/mergesort.go new file mode 100644 index 00000000..606fd90a --- /dev/null +++ b/shadowsocks/mergesort.go @@ -0,0 +1,32 @@ +package shadowsocks + +func merge(left, right []uint64, comparison func (uint64, uint64) int64) []uint64 { + result := make([]uint64, len(left) + len(right)) + l, r := 0, 0 + for (l < len(left)) && (r < len(right)) { + if comparison(left[l], right[r]) <= 0 { + result[l + r] = left[l] + l++ + } else { + result[l + r] = right[r] + r++ + } + } + for (l < len(left)) { + result[l + r] = left[l] + l++ + } + for (r < len(right)) { + result[l + r] = right[r] + r++ + } + return result +} + +func Sort(arr []uint64, comparison func (uint64, uint64) int64) []uint64 { + if len(arr) < 2 { + return arr + } + var middle uint64 = uint64(len(arr)/2) + return merge(Sort(arr[0:middle], comparison), Sort(arr[middle:], comparison), comparison) +} diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go new file mode 100644 index 00000000..e0fa9756 --- /dev/null +++ b/shadowsocks/pipe.go @@ -0,0 +1,102 @@ +package shadowsocks + +import ( + "bytes" + "encoding/binary" + "io" + "net" + "time" +) + +func SetReadTimeout(c net.Conn) { + if readTimeout != 0 { + c.SetReadDeadline(time.Now().Add(readTimeout)) + } +} + +// PipeThenClose copies data from src to dst, closes dst when done. +func PipeThenClose(src, dst net.Conn) { + defer dst.Close() + buf := leakyBuf.Get() + defer leakyBuf.Put(buf) + for { + 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 { + // Note: avoid overwrite err returned by Read. + if _, err := dst.Write(buf[0:n]); err != nil { + Debug.Println("write:", err) + break + } + } + if err != nil { + // Always "use of closed network connection", but no easy way to + // identify this specific error. So just leave the error along for now. + // More info here: https://code.google.com/p/go/issues/detail?id=4373 + /* + if bool(Debug) && err != io.EOF { + Debug.Println("read:", err) + } + */ + break + } + } +} + +// PipeThenClose copies data from src to dst, closes dst when done, with ota verification. +func PipeThenCloseOta(src *Conn, dst net.Conn) { + const ( + dataLenLen = 2 + hmacSha1Len = 10 + idxData0 = dataLenLen + hmacSha1Len + ) + + defer func() { + dst.Close() + }() + // sometimes it have to fill large block + buf := leakyBuf.Get() + defer leakyBuf.Put(buf) + i := 0 + for { + i += 1 + SetReadTimeout(src) + if n, err := io.ReadFull(src, buf[:dataLenLen+hmacSha1Len]); err != nil { + if err == io.EOF { + break + } + Debug.Printf("conn=%p #%v read header error n=%v: %v", src, i, n, err) + break + } + dataLen := binary.BigEndian.Uint16(buf[:dataLenLen]) + expectedHmacSha1 := buf[dataLenLen:idxData0] + + var dataBuf []byte + if len(buf) < int(idxData0+dataLen) { + dataBuf = make([]byte, dataLen) + } else { + dataBuf = buf[idxData0:idxData0+dataLen] + } + if n, err := io.ReadFull(src, dataBuf); err != nil { + if err == io.EOF { + break + } + Debug.Printf("conn=%p #%v read data error n=%v: %v", src, i, n, err) + break + } + chunkIdBytes := make([]byte, 4) + chunkId := src.GetAndIncrChunkId() + binary.BigEndian.PutUint32(chunkIdBytes, chunkId) + actualHmacSha1 := HmacSha1(append(src.GetIv(), chunkIdBytes...), dataBuf) + if !bytes.Equal(expectedHmacSha1, actualHmacSha1) { + Debug.Printf("conn=%p #%v read data hmac-sha1 mismatch, iv=%v chunkId=%v src=%v dst=%v len=%v expeced=%v actual=%v", src, i, src.GetIv(), chunkId, src.RemoteAddr(), dst.RemoteAddr(), dataLen, expectedHmacSha1, actualHmacSha1) + break + } + if n, err := dst.Write(dataBuf); err != nil { + Debug.Printf("conn=%p #%v write data error n=%v: %v", dst, i, n, err) + break + } + } +} diff --git a/shadowsocks/testdata/deprecated-client-multi-server.json b/shadowsocks/testdata/deprecated-client-multi-server.json new file mode 100644 index 00000000..6ceb5756 --- /dev/null +++ b/shadowsocks/testdata/deprecated-client-multi-server.json @@ -0,0 +1,7 @@ +{ + "server":["127.0.0.1", "127.0.1.1"], + "server_port":8388, + "local_port":1081, + "password":"barfoo!", + "timeout":60 +} diff --git a/shadowsocks/testdata/noserver.json b/shadowsocks/testdata/noserver.json new file mode 100644 index 00000000..f12f3ee1 --- /dev/null +++ b/shadowsocks/testdata/noserver.json @@ -0,0 +1,7 @@ +{ + "server_port":8388, + "local_port":1081, + "password":"barfoo!", + "timeout":60, + "cache_enctable": true +} diff --git a/shadowsocks/util.go b/shadowsocks/util.go new file mode 100644 index 00000000..378f24da --- /dev/null +++ b/shadowsocks/util.go @@ -0,0 +1,60 @@ +package shadowsocks + +import ( + "errors" + "fmt" + "os" + "crypto/hmac" + "crypto/sha1" + "encoding/binary" +) + +func PrintVersion() { + const version = "1.1.4" + fmt.Println("shadowsocks-go version", version) +} + +func IsFileExists(path string) (bool, error) { + stat, err := os.Stat(path) + if err == nil { + if stat.Mode()&os.ModeType == 0 { + return true, nil + } + return false, errors.New(path + " exists but is not regular file") + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func HmacSha1(key []byte, data []byte) []byte { + hmacSha1 := hmac.New(sha1.New, key) + hmacSha1.Write(data) + return hmacSha1.Sum(nil)[:10] +} + +func otaConnectAuth(iv, key, data []byte) []byte { + return append(data, HmacSha1(append(iv, key...), data)...) +} + +func otaReqChunkAuth(iv []byte, chunkId uint32, data []byte) []byte { + nb := make([]byte, 2) + binary.BigEndian.PutUint16(nb, uint16(len(data))) + chunkIdBytes := make([]byte, 4) + binary.BigEndian.PutUint32(chunkIdBytes, chunkId) + header := append(nb, HmacSha1(append(iv, chunkIdBytes...), data)...) + return append(header, data...) +} + +type ClosedFlag struct { + flag bool +} + +func (flag *ClosedFlag) SetClosed() { + flag.flag = true +} + +func (flag *ClosedFlag) IsClosed() bool { + return flag.flag +} \ No newline at end of file