From 08f85fc9b7cedd3e84458bb1121164c6d3334488 Mon Sep 17 00:00:00 2001 From: V2Ray Date: Fri, 25 Sep 2015 00:17:44 +0200 Subject: [PATCH] Unify error checking by introducing error codes --- common/errors/errors.go | 45 +++++++++++++++++++---- common/errors/errors_test.go | 23 ++++++++++++ proxy/socks/protocol/socks.go | 57 ++++++++++++------------------ proxy/socks/protocol/socks4.go | 29 +++++++++++++++ proxy/socks/protocol/socks_test.go | 2 +- proxy/socks/socks.go | 15 ++++---- testing/unit/errorsubject.go | 6 ++-- 7 files changed, 124 insertions(+), 53 deletions(-) create mode 100644 common/errors/errors_test.go create mode 100644 proxy/socks/protocol/socks4.go diff --git a/common/errors/errors.go b/common/errors/errors.go index 88450c5e..44daf98e 100644 --- a/common/errors/errors.go +++ b/common/errors/errors.go @@ -4,55 +4,86 @@ import ( "fmt" ) +func HasCode(err error, code int) bool { + if errWithCode, ok := err.(ErrorWithCode); ok { + return errWithCode.Code() == code + } + return false +} + +type ErrorWithCode interface { + error + Code() int +} + +type ErrorCode int + +func (code ErrorCode) Code() int { + return int(code) +} + +func (code ErrorCode) Prefix() string { + return fmt.Sprintf("[Error 0x%04X] ", code.Code()) +} + type AuthenticationError struct { + ErrorCode AuthDetail interface{} } func NewAuthenticationError(detail interface{}) AuthenticationError { return AuthenticationError{ + ErrorCode: 1, AuthDetail: detail, } } func (err AuthenticationError) Error() string { - return fmt.Sprintf("[Error 0x0001] Invalid auth %v", err.AuthDetail) + return fmt.Sprintf("%sInvalid auth %v", err.Prefix(), err.AuthDetail) } type ProtocolVersionError struct { + ErrorCode Version int } func NewProtocolVersionError(version int) ProtocolVersionError { return ProtocolVersionError{ - Version: version, + ErrorCode: 2, + Version: version, } } func (err ProtocolVersionError) Error() string { - return fmt.Sprintf("[Error 0x0002] Invalid version %d", err.Version) + return fmt.Sprintf("%sInvalid version %d", err.Prefix(), err.Version) } type CorruptedPacketError struct { + ErrorCode } +var corruptedPacketErrorInstance = CorruptedPacketError{ErrorCode: 3} + func NewCorruptedPacketError() CorruptedPacketError { - return CorruptedPacketError{} + return corruptedPacketErrorInstance } func (err CorruptedPacketError) Error() string { - return "[Error 0x0003] Corrupted packet." + return err.Prefix() + "Corrupted packet." } type IPFormatError struct { + ErrorCode IP []byte } func NewIPFormatError(ip []byte) IPFormatError { return IPFormatError{ - IP: ip, + ErrorCode: 4, + IP: ip, } } func (err IPFormatError) Error() string { - return fmt.Sprintf("[Error 0x0004] Invalid IP %v", err.IP) + return fmt.Sprintf("%sInvalid IP %v", err.Prefix(), err.IP) } diff --git a/common/errors/errors_test.go b/common/errors/errors_test.go new file mode 100644 index 00000000..5228f8bc --- /dev/null +++ b/common/errors/errors_test.go @@ -0,0 +1,23 @@ +package errors_test + +import ( + "testing" + + "github.com/v2ray/v2ray-core/common/errors" + "github.com/v2ray/v2ray-core/testing/unit" +) + +type MockError struct { + errors.ErrorCode +} + +func (err MockError) Error() string { + return "This is a fake error." +} + +func TestHasCode(t *testing.T) { + assert := unit.Assert(t) + + err := MockError{ErrorCode: 101} + assert.Error(err).HasCode(101) +} diff --git a/proxy/socks/protocol/socks.go b/proxy/socks/protocol/socks.go index 1b5caa39..dd6d4047 100644 --- a/proxy/socks/protocol/socks.go +++ b/proxy/socks/protocol/socks.go @@ -2,10 +2,9 @@ package protocol import ( "encoding/binary" - "errors" - "fmt" "io" + "github.com/v2ray/v2ray-core/common/errors" "github.com/v2ray/v2ray-core/common/log" v2net "github.com/v2ray/v2ray-core/common/net" ) @@ -23,10 +22,6 @@ const ( Socks4RequestRejected = byte(91) ) -var ( - ErrorSocksVersion4 = errors.New("Using SOCKS version 4.") -) - // Authentication request header of Socks5 protocol type Socks5AuthenticationRequest struct { version byte @@ -34,13 +29,6 @@ type Socks5AuthenticationRequest struct { authMethods [256]byte } -type Socks4AuthenticationRequest struct { - Version byte - Command byte - Port uint16 - IP [4]byte -} - func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool { for i := 0; i < int(request.nMethods); i++ { if request.authMethods[i] == method { @@ -54,11 +42,11 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, aut buffer := make([]byte, 256) nBytes, err := reader.Read(buffer) if err != nil { - log.Error("Failed to read socks authentication: %v", err) return } if nBytes < 2 { - err = fmt.Errorf("Expected 2 bytes read, but actaully %d bytes read", nBytes) + log.Info("Socks expected 2 bytes read, but only %d bytes read", nBytes) + err = errors.NewCorruptedPacketError() return } @@ -67,24 +55,26 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, aut auth4.Command = buffer[1] auth4.Port = binary.BigEndian.Uint16(buffer[2:4]) copy(auth4.IP[:], buffer[4:8]) - err = ErrorSocksVersion4 + err = NewSocksVersion4Error() return } auth.version = buffer[0] if auth.version != socksVersion { - err = fmt.Errorf("Unknown SOCKS version %d", auth.version) + err = errors.NewProtocolVersionError(int(auth.version)) return } auth.nMethods = buffer[1] if auth.nMethods <= 0 { - err = fmt.Errorf("Zero length of authentication methods") + log.Info("Zero length of authentication methods") + err = errors.NewCorruptedPacketError() return } if nBytes-2 != int(auth.nMethods) { - err = fmt.Errorf("Unmatching number of auth methods, expecting %d, but got %d", auth.nMethods, nBytes) + log.Info("Unmatching number of auth methods, expecting %d, but got %d", auth.nMethods, nBytes) + err = errors.NewCorruptedPacketError() return } copy(auth.authMethods[:], buffer[2:nBytes]) @@ -207,14 +197,13 @@ type Socks5Request struct { } func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { - - buffer := make([]byte, 4) - nBytes, err := reader.Read(buffer) + buffer := make([]byte, 256) + nBytes, err := reader.Read(buffer[:4]) if err != nil { return } - if nBytes < len(buffer) { - err = fmt.Errorf("Unable to read request.") + if nBytes < 4 { + err = errors.NewCorruptedPacketError() return } request = &Socks5Request{ @@ -230,11 +219,10 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { return } if nBytes != 4 { - err = fmt.Errorf("Unable to read IPv4 address.") + err = errors.NewCorruptedPacketError() return } case AddrTypeDomain: - buffer = make([]byte, 256) nBytes, err = reader.Read(buffer[0:1]) if err != nil { return @@ -246,7 +234,8 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { } if nBytes != int(domainLength) { - err = fmt.Errorf("Unable to read domain with %d bytes, expecting %d bytes", nBytes, domainLength) + log.Info("Unable to read domain with %d bytes, expecting %d bytes", nBytes, domainLength) + err = errors.NewCorruptedPacketError() return } request.Domain = string(buffer[:domainLength]) @@ -256,21 +245,21 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { return } if nBytes != 16 { - err = fmt.Errorf("Unable to read IPv4 address.") + err = errors.NewCorruptedPacketError() return } default: - err = fmt.Errorf("Unexpected address type %d", request.AddrType) + log.Info("Unexpected address type %d", request.AddrType) + err = errors.NewCorruptedPacketError() return } - buffer = make([]byte, 2) - nBytes, err = reader.Read(buffer) + nBytes, err = reader.Read(buffer[:2]) if err != nil { return } if nBytes != 2 { - err = fmt.Errorf("Unable to read port.") + err = errors.NewCorruptedPacketError() return } @@ -351,9 +340,7 @@ func (r *Socks5Response) toBytes() []byte { case 0x04: buffer = append(buffer, r.IPv6[:]...) } - portBuffer := make([]byte, 2) - binary.BigEndian.PutUint16(portBuffer, r.Port) - buffer = append(buffer, portBuffer...) + buffer = append(buffer, byte(r.Port>>8), byte(r.Port)) return buffer } diff --git a/proxy/socks/protocol/socks4.go b/proxy/socks/protocol/socks4.go new file mode 100644 index 00000000..c28615f8 --- /dev/null +++ b/proxy/socks/protocol/socks4.go @@ -0,0 +1,29 @@ +package protocol + +import ( + _ "fmt" + + "github.com/v2ray/v2ray-core/common/errors" + _ "github.com/v2ray/v2ray-core/common/log" +) + +type SocksVersion4Error struct { + errors.ErrorCode +} + +var socksVersion4ErrorInstance = SocksVersion4Error{ErrorCode: 1000} + +func NewSocksVersion4Error() SocksVersion4Error { + return socksVersion4ErrorInstance +} + +func (err SocksVersion4Error) Error() string { + return err.Prefix() + "Request is socks version 4." +} + +type Socks4AuthenticationRequest struct { + Version byte + Command byte + Port uint16 + IP [4]byte +} diff --git a/proxy/socks/protocol/socks_test.go b/proxy/socks/protocol/socks_test.go index 5551b973..be4f2d2a 100644 --- a/proxy/socks/protocol/socks_test.go +++ b/proxy/socks/protocol/socks_test.go @@ -47,7 +47,7 @@ func TestAuthentication4RequestRead(t *testing.T) { 0x72, 0x72, 0x72, 0x72, } _, request4, err := ReadAuthentication(bytes.NewReader(rawRequest)) - assert.Error(err).Equals(ErrorSocksVersion4) + assert.Error(err).HasCode(1000) assert.Byte(request4.Version).Named("Version").Equals(0x04) assert.Byte(request4.Command).Named("Command").Equals(0x01) assert.Uint16(request4.Port).Named("Port").Equals(53) diff --git a/proxy/socks/socks.go b/proxy/socks/socks.go index 540fb728..5129e1e2 100644 --- a/proxy/socks/socks.go +++ b/proxy/socks/socks.go @@ -2,22 +2,23 @@ package socks import ( _ "bufio" - "errors" + e2 "errors" "io" "net" "strconv" "sync" "github.com/v2ray/v2ray-core" + "github.com/v2ray/v2ray-core/common/errors" "github.com/v2ray/v2ray-core/common/log" v2net "github.com/v2ray/v2ray-core/common/net" - protocol "github.com/v2ray/v2ray-core/proxy/socks/protocol" + "github.com/v2ray/v2ray-core/proxy/socks/protocol" ) var ( - ErrorAuthenticationFailed = errors.New("None of the authentication methods is allowed.") - ErrorCommandNotSupported = errors.New("Client requested an unsupported command.") - ErrorInvalidUser = errors.New("Invalid username or password.") + ErrorAuthenticationFailed = e2.New("None of the authentication methods is allowed.") + ErrorCommandNotSupported = e2.New("Client requested an unsupported command.") + ErrorInvalidUser = e2.New("Invalid username or password.") ) // SocksServer is a SOCKS 5 proxy server @@ -66,7 +67,7 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error { reader := v2net.NewTimeOutReader(4, connection) auth, auth4, err := protocol.ReadAuthentication(reader) - if err != nil && err != protocol.ErrorSocksVersion4 { + if err != nil && !errors.HasCode(err, 1000) { log.Error("Error on reading authentication: %v", err) return err } @@ -74,7 +75,7 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error { var dest v2net.Destination // TODO refactor this part - if err == protocol.ErrorSocksVersion4 { + if errors.HasCode(err, 1000) { result := protocol.Socks4RequestGranted if auth4.Command == protocol.CmdBind { result = protocol.Socks4RequestRejected diff --git a/testing/unit/errorsubject.go b/testing/unit/errorsubject.go index 271af1be..7c6dae54 100644 --- a/testing/unit/errorsubject.go +++ b/testing/unit/errorsubject.go @@ -2,7 +2,8 @@ package unit import ( "fmt" - "strings" + + "github.com/v2ray/v2ray-core/common/errors" ) type ErrorSubject struct { @@ -43,8 +44,7 @@ func (subject *ErrorSubject) IsNil() { } func (subject *ErrorSubject) HasCode(code int) { - errorPrefix := fmt.Sprintf("[Error 0x%04X]", code) - if !strings.Contains(subject.value.Error(), errorPrefix) { + if !errors.HasCode(subject.value, code) { subject.FailWithMessage(fmt.Sprintf("Not ture that %s has error code 0x%04X.", subject.DisplayString(), code)) } }