diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go index 46b21a4e1..ffd137bfd 100644 --- a/proxy/shadowsocks/config.go +++ b/proxy/shadowsocks/config.go @@ -1,8 +1,8 @@ package shadowsocks import ( + "crypto/cipher" "crypto/md5" - "io" "github.com/v2ray/v2ray-core/common/crypto" "github.com/v2ray/v2ray-core/common/protocol" @@ -11,8 +11,8 @@ import ( type Cipher interface { KeySize() int IVSize() int - NewEncodingStream(key []byte, iv []byte, writer io.Writer) (io.Writer, error) - NewDecodingStream(key []byte, iv []byte, reader io.Reader) (io.Reader, error) + NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) + NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) } type AesCfb struct { @@ -27,22 +27,40 @@ func (this *AesCfb) IVSize() int { return 16 } -func (this *AesCfb) NewEncodingStream(key []byte, iv []byte, writer io.Writer) (io.Writer, error) { +func (this *AesCfb) NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) { stream, err := crypto.NewAesEncryptionStream(key, iv) if err != nil { return nil, err } - aesWriter := crypto.NewCryptionWriter(stream, writer) - return aesWriter, nil + return stream, nil } -func (this *AesCfb) NewDecodingStream(key []byte, iv []byte, reader io.Reader) (io.Reader, error) { +func (this *AesCfb) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) { stream, err := crypto.NewAesDecryptionStream(key, iv) if err != nil { return nil, err } - aesReader := crypto.NewCryptionReader(stream, reader) - return aesReader, nil + return stream, nil +} + +type ChaCha20 struct { + IVBytes int +} + +func (this *ChaCha20) KeySize() int { + return 32 +} + +func (this *ChaCha20) IVSize() int { + return this.IVBytes +} + +func (this *ChaCha20) NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) { + return crypto.NewChaCha20Stream(key, iv), nil +} + +func (this *ChaCha20) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) { + return crypto.NewChaCha20Stream(key, iv), nil } type Config struct { diff --git a/proxy/shadowsocks/config_json.go b/proxy/shadowsocks/config_json.go index 81cce0647..744615c96 100644 --- a/proxy/shadowsocks/config_json.go +++ b/proxy/shadowsocks/config_json.go @@ -35,6 +35,14 @@ func (this *Config) UnmarshalJSON(data []byte) error { this.Cipher = &AesCfb{ KeyBytes: 16, } + case "chacha20": + this.Cipher = &ChaCha20{ + IVBytes: 8, + } + case "chacha20-ietf": + this.Cipher = &ChaCha20{ + IVBytes: 12, + } default: log.Error("Shadowsocks: Unknown cipher method: ", jsonConfig.Cipher) return internal.ErrorBadConfiguration diff --git a/proxy/shadowsocks/shadowsocks.go b/proxy/shadowsocks/shadowsocks.go index 369076986..be91b9bb5 100644 --- a/proxy/shadowsocks/shadowsocks.go +++ b/proxy/shadowsocks/shadowsocks.go @@ -9,6 +9,7 @@ import ( "github.com/v2ray/v2ray-core/app" "github.com/v2ray/v2ray-core/app/dispatcher" "github.com/v2ray/v2ray-core/common/alloc" + "github.com/v2ray/v2ray-core/common/crypto" v2io "github.com/v2ray/v2ray-core/common/io" "github.com/v2ray/v2ray-core/common/log" v2net "github.com/v2ray/v2ray-core/common/net" @@ -90,16 +91,19 @@ func (this *Shadowsocks) Listen(port v2net.Port) error { func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.Destination) { defer payload.Release() - iv := payload.Value[:this.config.Cipher.IVSize()] + ivLen := this.config.Cipher.IVSize() + iv := payload.Value[:ivLen] key := this.config.Key - payload.SliceFrom(this.config.Cipher.IVSize()) + payload.SliceFrom(ivLen) - reader, err := this.config.Cipher.NewDecodingStream(key, iv, payload) + stream, err := this.config.Cipher.NewDecodingStream(key, iv) if err != nil { log.Error("Shadowsocks: Failed to create decoding stream: ", err) return } + reader := crypto.NewCryptionReader(stream, payload) + request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(key, iv)), true) if err != nil { log.Access(source, serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error())) @@ -115,18 +119,20 @@ func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.D this.udpServer.Dispatch(source, packet, func(packet v2net.Packet) { defer packet.Chunk().Release() - response := alloc.NewBuffer().Slice(0, this.config.Cipher.IVSize()) + response := alloc.NewBuffer().Slice(0, ivLen) defer response.Release() rand.Read(response.Value) respIv := response.Value - writer, err := this.config.Cipher.NewEncodingStream(key, respIv, response) + stream, err := this.config.Cipher.NewEncodingStream(key, respIv) if err != nil { log.Error("Shadowsocks: Failed to create encoding stream: ", err) return } + writer := crypto.NewCryptionWriter(stream, response) + switch { case request.Address.IsIPv4(): writer.Write([]byte{AddrTypeIPv4}) @@ -144,7 +150,7 @@ func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.D if request.OTA { respAuth := NewAuthenticator(HeaderKeyGenerator(key, respIv)) - respAuth.Authenticate(response.Value, response.Value[this.config.Cipher.IVSize():]) + respAuth.Authenticate(response.Value, response.Value[ivLen:]) } this.udpHub.WriteTo(response.Value, source) @@ -159,22 +165,25 @@ func (this *Shadowsocks) handleConnection(conn *hub.TCPConn) { timedReader := v2net.NewTimeOutReader(16, conn) - _, err := io.ReadFull(timedReader, buffer.Value[:this.config.Cipher.IVSize()]) + ivLen := this.config.Cipher.IVSize() + _, err := io.ReadFull(timedReader, buffer.Value[:ivLen]) if err != nil { log.Access(conn.RemoteAddr(), serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error())) log.Error("Shadowsocks: Failed to read IV: ", err) return } - iv := buffer.Value[:this.config.Cipher.IVSize()] + iv := buffer.Value[:ivLen] key := this.config.Key - reader, err := this.config.Cipher.NewDecodingStream(key, iv, timedReader) + stream, err := this.config.Cipher.NewDecodingStream(key, iv) if err != nil { log.Error("Shadowsocks: Failed to create decoding stream: ", err) return } + reader := crypto.NewCryptionReader(stream, timedReader) + request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(iv, key)), false) if err != nil { log.Access(conn.RemoteAddr(), serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error())) @@ -196,17 +205,20 @@ func (this *Shadowsocks) handleConnection(conn *hub.TCPConn) { writeFinish.Lock() go func() { if payload, ok := <-ray.InboundOutput(); ok { - payload.SliceBack(16) - rand.Read(payload.Value[:16]) + payload.SliceBack(ivLen) + rand.Read(payload.Value[:ivLen]) - writer, err := this.config.Cipher.NewEncodingStream(key, payload.Value[:16], conn) + stream, err := this.config.Cipher.NewEncodingStream(key, payload.Value[:ivLen]) if err != nil { log.Error("Shadowsocks: Failed to create encoding stream: ", err) return } + stream.XORKeyStream(payload.Value[ivLen:], payload.Value[ivLen:]) - writer.Write(payload.Value) + conn.Write(payload.Value) payload.Release() + + writer := crypto.NewCryptionWriter(stream, conn) v2io.ChanToRawWriter(writer, ray.InboundOutput()) } writeFinish.Unlock() diff --git a/testing/scenarios/data/test_6_server.json b/testing/scenarios/data/test_6_server.json index d1993375c..25c4088e6 100644 --- a/testing/scenarios/data/test_6_server.json +++ b/testing/scenarios/data/test_6_server.json @@ -16,6 +16,15 @@ "password": "v2ray-another", "udp": true } + }, + { + "protocol": "shadowsocks", + "port": 50056, + "settings": { + "method": "chacha20", + "password": "new-password", + "udp": true + } } ], "outbound": { diff --git a/testing/scenarios/shadowsocks_test.go b/testing/scenarios/shadowsocks_test.go index fa1ab3245..498e87b68 100644 --- a/testing/scenarios/shadowsocks_test.go +++ b/testing/scenarios/shadowsocks_test.go @@ -69,5 +69,24 @@ func TestShadowsocksTCP(t *testing.T) { assert.StringLiteral("Processed: " + payload).Equals(string(response[:nBytes])) conn.Close() + cipher, err = ssclient.NewCipher("chacha20", "new-password") + assert.Error(err).IsNil() + + conn, err = ssclient.DialWithRawAddr(rawAddr, "127.0.0.1:50056", cipher) + assert.Error(err).IsNil() + + payload = "shadowsocks request 3." + nBytes, err = conn.Write([]byte(payload)) + assert.Error(err).IsNil() + assert.Int(nBytes).Equals(len(payload)) + + conn.Conn.(*net.TCPConn).CloseWrite() + + response = make([]byte, 1024) + nBytes, err = conn.Read(response) + assert.Error(err).IsNil() + assert.StringLiteral("Processed: " + payload).Equals(string(response[:nBytes])) + conn.Close() + CloseAllServers() }