From fb1ad8b81b0d38dffae40d10a68853438e1f7900 Mon Sep 17 00:00:00 2001 From: V2Ray Date: Tue, 8 Sep 2015 13:18:55 +0200 Subject: [PATCH] Full implementation of Socks5 protocol, proxying is on the way --- io/socks/socks.go | 59 +++++++++++++++++++++++-- io/socks/socks_test.go | 24 ++++++++++ net/{sockshandler.go => socks/socks.go} | 2 +- vconfig.go | 8 ++-- vid.go | 1 + vpoint.go | 1 + vuserset.go | 36 ++++++++++++++- 7 files changed, 121 insertions(+), 10 deletions(-) rename net/{sockshandler.go => socks/socks.go} (97%) diff --git a/io/socks/socks.go b/io/socks/socks.go index 20662d6b..5137cc87 100644 --- a/io/socks/socks.go +++ b/io/socks/socks.go @@ -68,12 +68,15 @@ func (r *Socks5AuthenticationResponse) ToBytes() []byte { func WriteAuthentication(writer io.Writer, response Socks5AuthenticationResponse) error { _, err := writer.Write(response.ToBytes()) - if err != nil { - return err - } - return nil + return err } +const ( + AddrTypeIPv4 = byte(0x01) + AddrTypeIPv6 = byte(0x04) + AddrTypeDomain = byte(0x03) +) + type Socks5Request struct { version byte command byte @@ -149,3 +152,51 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { request.port = binary.BigEndian.Uint16(buffer) return } + +const ( + ErrorSuccess = byte(0x00) + ErrorGeneralFailure = byte(0x01) + ErrorConnectionNotAllowed = byte(0x02) + ErrorNetworkUnreachable = byte(0x03) + ErrorHostUnUnreachable = byte(0x04) + ErrorConnectionRefused = byte(0x05) + ErrorTTLExpired = byte(0x06) + ErrorCommandNotSupported = byte(0x07) + ErrorAddressTypeNotSupported = byte(0x08) +) + +type Socks5Response struct { + Version byte + Error byte + AddrType byte + IPv4 [4]byte + Domain string + IPv6 [16]byte + Port uint16 +} + +func (r Socks5Response) toBytes() []byte { + buffer := make([]byte, 0, 300) + buffer = append(buffer, r.Version) + buffer = append(buffer, r.Error) + buffer = append(buffer, 0x00) // reserved + buffer = append(buffer, r.AddrType) + switch r.AddrType { + case 0x01: + buffer = append(buffer, r.IPv4[:]...) + case 0x03: + buffer = append(buffer, byte(len(r.Domain))) + buffer = append(buffer, []byte(r.Domain)...) + case 0x04: + buffer = append(buffer, r.IPv6[:]...) + } + portBuffer := make([]byte, 2) + binary.BigEndian.PutUint16(portBuffer, r.Port) + buffer = append(buffer, portBuffer...) + return buffer +} + +func WriteResponse(writer io.Writer, response Socks5Response) error { + _, err := writer.Write(response.toBytes()) + return err +} diff --git a/io/socks/socks_test.go b/io/socks/socks_test.go index eea2af90..c6eee2bb 100644 --- a/io/socks/socks_test.go +++ b/io/socks/socks_test.go @@ -68,3 +68,27 @@ func TestRequestRead(t *testing.T) { t.Errorf("Expected port 53, but got %d", request.port) } } + +func TestResponseToBytes(t *testing.T) { + response := Socks5Response{ + socksVersion, + ErrorSuccess, + AddrTypeIPv4, + [4]byte{0x72, 0x72, 0x72, 0x72}, + "", + [16]byte{}, + uint16(53), + } + rawResponse := response.toBytes() + expectedBytes := []byte{ + socksVersion, + ErrorSuccess, + byte(0x00), + AddrTypeIPv4, + 0x72, 0x72, 0x72, 0x72, + byte(0x00), byte(0x035), + } + if !bytes.Equal(rawResponse, expectedBytes) { + t.Errorf("Expected response %v, but got %v", expectedBytes, rawResponse) + } +} diff --git a/net/sockshandler.go b/net/socks/socks.go similarity index 97% rename from net/sockshandler.go rename to net/socks/socks.go index 758b431b..728d7d1e 100644 --- a/net/sockshandler.go +++ b/net/socks/socks.go @@ -1,4 +1,4 @@ -package net +package socks import ( "net" diff --git a/vconfig.go b/vconfig.go index a4deed17..d80f184a 100644 --- a/vconfig.go +++ b/vconfig.go @@ -1,17 +1,17 @@ package core -// User account that is used for connection to a VPoint +// VUser is the user account that is used for connection to a VPoint type VUser struct { - id VID // The ID of this VUser. + Id VID // The ID of this VUser. } -// The next VPoint server in the connection chain. +// VNext is the next VPoint server in the connection chain. type VNext struct { ServerAddress string // Address of VNext server, in the form of "IP:Port" User []VUser // User accounts for accessing VNext. } -// The config for VPoint server. +// VConfig is the config for VPoint server. type VConfig struct { Port uint16 // Port of this VPoint server. AllowedClients []VUser diff --git a/vid.go b/vid.go index 7d3cfd0c..0a5e959c 100644 --- a/vid.go +++ b/vid.go @@ -9,6 +9,7 @@ import ( // The ID of en entity, in the form of an UUID. type VID [16]byte +// Hash generates a MD5 hash based on current VID and a suffix string. func (v VID) Hash(suffix []byte) []byte { md5 := md5.New() md5.Write(v[:]) diff --git a/vpoint.go b/vpoint.go index b0c97c02..63b8c912 100644 --- a/vpoint.go +++ b/vpoint.go @@ -4,6 +4,7 @@ import ( "fmt" ) +// VPoint is an single server in V2Ray system. type VPoint struct { config VConfig connHandler ConnectionHandler diff --git a/vuserset.go b/vuserset.go index d8acae25..18ffb9ca 100644 --- a/vuserset.go +++ b/vuserset.go @@ -1,5 +1,39 @@ package core +import ( + "encoding/base64" +) + type VUserSet struct { - validUserIds [][]byte + validUserIds []VID + userIdsAskHash map[string]int +} + +func NewVUserSet() *VUserSet { + vuSet := new(VUserSet) + vuSet.validUserIds = make([]VID, 0, 16) + return vuSet +} + +func hashBytesToString(hash []byte) string { + return base64.StdEncoding.EncodeToString(hash) +} + +func (us *VUserSet) AddUser(user VUser) error { + id := user.Id + us.validUserIds = append(us.validUserIds, id) + + idBase64 := hashBytesToString(id.Hash([]byte("ASK"))) + us.userIdsAskHash[idBase64] = len(us.validUserIds) - 1 + + return nil +} + +func (us VUserSet) IsValidUserId(askHash []byte) (*VID, bool) { + askBase64 := hashBytesToString(askHash) + idIndex, found := us.userIdsAskHash[askBase64] + if found { + return &us.validUserIds[idIndex], true + } + return nil, false }