diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index df663d292..d5852c337 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -1,7 +1,11 @@ package shadowsocks import ( + "errors" + + "sync" "v2ray.com/core/common/alloc" + v2io "v2ray.com/core/common/io" "v2ray.com/core/common/log" v2net "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" @@ -39,10 +43,96 @@ func (this *Client) Dispatch(destination v2net.Destination, payload *alloc.Buffe return nil }) if err != nil { - log.Error("Shadowsocks|Client: Failed to find an available destination:", err) - return err + return errors.New("Shadowsocks|Client: Failed to find an available destination:" + err.Error()) } log.Info("Shadowsocks|Client: Tunneling request to ", destination, " via ", server.Destination()) + request := &protocol.RequestHeader{ + Version: Version, + Address: destination.Address, + Port: destination.Port, + } + if destination.Network == v2net.Network_TCP { + request.Command = protocol.RequestCommandTCP + } else { + request.Command = protocol.RequestCommandUDP + } + + user := server.PickUser() + rawAccount, err := user.GetTypedAccount() + if err != nil { + return errors.New("Shadowsocks|Client: Failed to get a valid user account: " + err.Error()) + } + account := rawAccount.(*ShadowsocksAccount) + request.User = user + + if account.OneTimeAuth { + request.Option |= RequestOptionOneTimeAuth + } + + if request.Command == protocol.RequestCommandTCP { + bufferedWriter := v2io.NewBufferedWriter(conn) + defer bufferedWriter.Release() + + bodyWriter, err := WriteTCPRequest(request, bufferedWriter) + defer bodyWriter.Release() + + if err != nil { + return errors.New("Shadowsock|Client: Failed to write request: " + err.Error()) + } + + if err := bodyWriter.Write(payload); err != nil { + return errors.New("Shadowsocks|Client: Failed to write payload: " + err.Error()) + } + + bufferedWriter.SetCached(false) + v2io.Pipe(ray.OutboundInput(), bodyWriter) + + var responseMutex sync.Mutex + responseMutex.Lock() + + go func() { + defer responseMutex.Unlock() + + responseReader, err := ReadTCPResponse(user, conn) + if err != nil { + log.Warning("Shadowsocks|Client: Failed to read response: " + err.Error()) + return + } + + v2io.Pipe(responseReader, ray.OutboundOutput()) + }() + + responseMutex.Lock() + } + + if request.Command == protocol.RequestCommandUDP { + writer := &UDPWriter{ + Writer: conn, + Request: request, + } + if err := writer.Write(payload); err != nil { + return errors.New("Shadowsocks|Client: Failed to write payload: " + err.Error()) + } + v2io.Pipe(ray.OutboundInput(), writer) + + timedReader := v2net.NewTimeOutReader(16, conn) + var responseMutex sync.Mutex + responseMutex.Lock() + + go func() { + defer responseMutex.Unlock() + + reader := &UDPReader{ + Reader: timedReader, + User: user, + } + + v2io.Pipe(reader, ray.OutboundOutput()) + }() + + responseMutex.Lock() + } + return nil } diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go index d59e2039b..9cce42296 100644 --- a/proxy/shadowsocks/config.go +++ b/proxy/shadowsocks/config.go @@ -11,8 +11,9 @@ import ( ) type ShadowsocksAccount struct { - Cipher Cipher - Key []byte + Cipher Cipher + Key []byte + OneTimeAuth bool } func (this *ShadowsocksAccount) Equals(another protocol.Account) bool { @@ -43,8 +44,9 @@ func (this *Account) AsAccount() (protocol.Account, error) { return nil, err } return &ShadowsocksAccount{ - Cipher: cipher, - Key: this.GetCipherKey(), + Cipher: cipher, + Key: this.GetCipherKey(), + OneTimeAuth: this.Ota == Account_Auto || this.Ota == Account_Enabled, }, nil } diff --git a/proxy/shadowsocks/protocol.go b/proxy/shadowsocks/protocol.go index d54987a5f..8ad71fc5e 100644 --- a/proxy/shadowsocks/protocol.go +++ b/proxy/shadowsocks/protocol.go @@ -342,3 +342,46 @@ func DecodeUDPPacket(user *protocol.User, payload *alloc.Buffer) (*protocol.Requ return request, payload, nil } + +type UDPReader struct { + Reader io.Reader + User *protocol.User +} + +func (this *UDPReader) Read() (*alloc.Buffer, error) { + buffer := alloc.NewLocalBuffer(2048) + nBytes, err := this.Reader.Read(buffer.Value) + if err != nil { + buffer.Release() + return nil, err + } + buffer.Slice(0, nBytes) + _, payload, err := DecodeUDPPacket(this.User, buffer) + if err != nil { + buffer.Release() + return nil, err + } + return payload, nil +} + +func (this *UDPReader) Release() { +} + +type UDPWriter struct { + Writer io.Writer + Request *protocol.RequestHeader +} + +func (this *UDPWriter) Write(buffer *alloc.Buffer) error { + payload, err := EncodeUDPPacket(this.Request, buffer) + if err != nil { + return err + } + _, err = this.Writer.Write(payload.Value) + payload.Release() + return err +} + +func (this *UDPWriter) Release() { + +} diff --git a/proxy/shadowsocks/shadowsocks_test.go b/proxy/shadowsocks/shadowsocks_test.go new file mode 100644 index 000000000..ceb97f45b --- /dev/null +++ b/proxy/shadowsocks/shadowsocks_test.go @@ -0,0 +1,5 @@ +package shadowsocks_test + +import ( +// . "v2ray.com/core/proxy/shadowsocks" +) diff --git a/tools/conf/shadowsocks_test.go b/tools/conf/shadowsocks_test.go index 595c9a5ab..3eb55fe19 100644 --- a/tools/conf/shadowsocks_test.go +++ b/tools/conf/shadowsocks_test.go @@ -30,11 +30,9 @@ func TestShadowsocksServerConfigParsing(t *testing.T) { rawAccount, err := config.User.GetTypedAccount() assert.Error(err).IsNil() - account, ok := rawAccount.(*shadowsocks.Account) + account, ok := rawAccount.(*shadowsocks.ShadowsocksAccount) assert.Bool(ok).IsTrue() - cipher, err := account.GetCipher() - assert.Error(err).IsNil() - assert.Int(cipher.KeySize()).Equals(16) - assert.Bytes(account.GetCipherKey()).Equals([]byte{160, 224, 26, 2, 22, 110, 9, 80, 65, 52, 80, 20, 38, 243, 224, 241}) + assert.Int(account.Cipher.KeySize()).Equals(16) + assert.Bytes(account.Key).Equals([]byte{160, 224, 26, 2, 22, 110, 9, 80, 65, 52, 80, 20, 38, 243, 224, 241}) }