1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-12-30 05:56:54 -05:00

Add SOCKS4 support

This commit is contained in:
V2Ray 2015-09-17 17:37:04 +02:00
parent 2b7fdef203
commit 8e8dbbcfd0
5 changed files with 159 additions and 68 deletions

View File

@ -3,19 +3,29 @@ package socks
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"github.com/v2ray/v2ray-core/log"
v2net "github.com/v2ray/v2ray-core/net" v2net "github.com/v2ray/v2ray-core/net"
) )
const ( const (
socksVersion = uint8(5) socksVersion = byte(0x05)
socks4Version = byte(0x04)
AuthNotRequired = byte(0x00) AuthNotRequired = byte(0x00)
AuthGssApi = byte(0x01) AuthGssApi = byte(0x01)
AuthUserPass = byte(0x02) AuthUserPass = byte(0x02)
AuthNoMatchingMethod = byte(0xFF) AuthNoMatchingMethod = byte(0xFF)
Socks4RequestGranted = byte(90)
Socks4RequestRejected = byte(91)
)
var (
ErrorSocksVersion4 = errors.New("Using SOCKS version 4.")
) )
// Authentication request header of Socks5 protocol // Authentication request header of Socks5 protocol
@ -25,6 +35,13 @@ type Socks5AuthenticationRequest struct {
authMethods [256]byte authMethods [256]byte
} }
type Socks4AuthenticationRequest struct {
Version byte
Command byte
Port uint16
IP [4]byte
}
func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool { func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
for i := 0; i < int(request.nMethods); i++ { for i := 0; i < int(request.nMethods); i++ {
if request.authMethods[i] == method { if request.authMethods[i] == method {
@ -34,10 +51,11 @@ func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
return false return false
} }
func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, err error) { func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, auth4 Socks4AuthenticationRequest, err error) {
buffer := make([]byte, 256) buffer := make([]byte, 256)
nBytes, err := reader.Read(buffer) nBytes, err := reader.Read(buffer)
if err != nil { if err != nil {
log.Error("Failed to read socks authentication: %v", err)
return return
} }
if nBytes < 2 { if nBytes < 2 {
@ -45,6 +63,15 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, err
return return
} }
if buffer[0] == socks4Version {
auth4.Version = buffer[0]
auth4.Command = buffer[1]
auth4.Port = binary.BigEndian.Uint16(buffer[2:4])
copy(auth4.IP[:], buffer[4:8])
err = ErrorSocksVersion4
return
}
auth.version = buffer[0] auth.version = buffer[0]
if auth.version != socksVersion { if auth.version != socksVersion {
err = fmt.Errorf("Unknown SOCKS version %d", auth.version) err = fmt.Errorf("Unknown SOCKS version %d", auth.version)
@ -70,6 +97,12 @@ type Socks5AuthenticationResponse struct {
authMethod byte authMethod byte
} }
type Socks4AuthenticationResponse struct {
result byte
port uint16
ip []byte
}
func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse { func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
return &Socks5AuthenticationResponse{ return &Socks5AuthenticationResponse{
version: socksVersion, version: socksVersion,
@ -77,11 +110,29 @@ func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
} }
} }
func NewSocks4AuthenticationResponse(result byte, port uint16, ip []byte) *Socks4AuthenticationResponse {
return &Socks4AuthenticationResponse{
result: result,
port: port,
ip: ip,
}
}
func WriteAuthentication(writer io.Writer, r *Socks5AuthenticationResponse) error { func WriteAuthentication(writer io.Writer, r *Socks5AuthenticationResponse) error {
_, err := writer.Write([]byte{r.version, r.authMethod}) _, err := writer.Write([]byte{r.version, r.authMethod})
return err return err
} }
func WriteSocks4AuthenticationResponse(writer io.Writer, r *Socks4AuthenticationResponse) error {
buffer := make([]byte, 8)
// buffer[0] is always 0
buffer[1] = r.result
binary.BigEndian.PutUint16(buffer[2:4], r.port)
copy(buffer[4:], r.ip)
_, err := writer.Write(buffer)
return err
}
type Socks5UserPassRequest struct { type Socks5UserPassRequest struct {
version byte version byte
username string username string

View File

@ -30,13 +30,30 @@ func TestAuthenticationRequestRead(t *testing.T) {
0x01, // nMethods 0x01, // nMethods
0x02, // methods 0x02, // methods
} }
request, err := ReadAuthentication(bytes.NewReader(rawRequest)) request, _, err := ReadAuthentication(bytes.NewReader(rawRequest))
assert.Error(err).IsNil() assert.Error(err).IsNil()
assert.Byte(request.version).Named("Version").Equals(0x05) assert.Byte(request.version).Named("Version").Equals(0x05)
assert.Byte(request.nMethods).Named("#Methods").Equals(0x01) assert.Byte(request.nMethods).Named("#Methods").Equals(0x01)
assert.Byte(request.authMethods[0]).Named("Auth Method").Equals(0x02) assert.Byte(request.authMethods[0]).Named("Auth Method").Equals(0x02)
} }
func TestAuthentication4RequestRead(t *testing.T) {
assert := unit.Assert(t)
rawRequest := []byte{
0x04, // version
0x01, // command
0x00, 0x35,
0x72, 0x72, 0x72, 0x72,
}
_, request4, err := ReadAuthentication(bytes.NewReader(rawRequest))
assert.Error(err).Equals(ErrorSocksVersion4)
assert.Byte(request4.Version).Named("Version").Equals(0x04)
assert.Byte(request4.Command).Named("Command").Equals(0x01)
assert.Uint16(request4.Port).Named("Port").Equals(53)
assert.Bytes(request4.IP[:]).Named("IP").Equals([]byte{0x72, 0x72, 0x72, 0x72})
}
func TestAuthenticationResponseWrite(t *testing.T) { func TestAuthenticationResponseWrite(t *testing.T) {
assert := unit.Assert(t) assert := unit.Assert(t)

View File

@ -18,10 +18,12 @@ type Address struct {
} }
func IPAddress(ip []byte, port uint16) Address { func IPAddress(ip []byte, port uint16) Address {
ipCopy := make([]byte, 4)
copy(ipCopy, ip)
// TODO: check IP length // TODO: check IP length
return Address{ return Address{
Type: AddrTypeIP, Type: AddrTypeIP,
IP: net.IP(ip), IP: net.IP(ipCopy),
Domain: "", Domain: "",
Port: port, Port: port,
} }

View File

@ -23,7 +23,7 @@ func (vconn *FreedomConnection) Start(ray core.OutboundRay) error {
output := ray.OutboundOutput() output := ray.OutboundOutput()
conn, err := net.Dial("tcp", vconn.dest.String()) conn, err := net.Dial("tcp", vconn.dest.String())
if err != nil { if err != nil {
return log.Error("Failed to open tcp: %s", vconn.dest.String()) return log.Error("Failed to open tcp: %s : %v", vconn.dest.String(), err)
} }
log.Debug("Sending outbound tcp: %s", vconn.dest.String()) log.Debug("Sending outbound tcp: %s", vconn.dest.String())

View File

@ -64,92 +64,113 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
reader := bufio.NewReader(connection) reader := bufio.NewReader(connection)
auth, err := socksio.ReadAuthentication(reader) auth, auth4, err := socksio.ReadAuthentication(reader)
if err != nil { if err != nil && err != socksio.ErrorSocksVersion4 {
log.Error("Error on reading authentication: %v", err) log.Error("Error on reading authentication: %v", err)
return err return err
} }
expectedAuthMethod := socksio.AuthNotRequired var dest v2net.Address
if server.config.AuthMethod == JsonAuthMethodUserPass {
expectedAuthMethod = socksio.AuthUserPass
}
if !auth.HasAuthMethod(expectedAuthMethod) { // TODO refactor this part
authResponse := socksio.NewAuthenticationResponse(socksio.AuthNoMatchingMethod) if err == socksio.ErrorSocksVersion4 {
result := socksio.Socks4RequestGranted
if auth4.Command == socksio.CmdBind {
result = socksio.Socks4RequestRejected
}
socks4Response := socksio.NewSocks4AuthenticationResponse(result, auth4.Port, auth4.IP[:])
socksio.WriteSocks4AuthenticationResponse(connection, socks4Response)
if result == socksio.Socks4RequestRejected {
return ErrorCommandNotSupported
}
dest = v2net.IPAddress(auth4.IP[:], auth4.Port)
} else {
expectedAuthMethod := socksio.AuthNotRequired
if server.config.AuthMethod == JsonAuthMethodUserPass {
expectedAuthMethod = socksio.AuthUserPass
}
if !auth.HasAuthMethod(expectedAuthMethod) {
authResponse := socksio.NewAuthenticationResponse(socksio.AuthNoMatchingMethod)
err = socksio.WriteAuthentication(connection, authResponse)
if err != nil {
log.Error("Error on socksio write authentication: %v", err)
return err
}
log.Warning("Client doesn't support allowed any auth methods.")
return ErrorAuthenticationFailed
}
authResponse := socksio.NewAuthenticationResponse(expectedAuthMethod)
err = socksio.WriteAuthentication(connection, authResponse) err = socksio.WriteAuthentication(connection, authResponse)
if err != nil { if err != nil {
log.Error("Error on socksio write authentication: %v", err) log.Error("Error on socksio write authentication: %v", err)
return err return err
} }
log.Warning("Client doesn't support allowed any auth methods.") if server.config.AuthMethod == JsonAuthMethodUserPass {
return ErrorAuthenticationFailed upRequest, err := socksio.ReadUserPassRequest(reader)
} if err != nil {
log.Error("Failed to read username and password: %v", err)
return err
}
status := byte(0)
if !upRequest.IsValid(server.config.Username, server.config.Password) {
status = byte(0xFF)
}
upResponse := socksio.NewSocks5UserPassResponse(status)
err = socksio.WriteUserPassResponse(connection, upResponse)
if err != nil {
log.Error("Error on socksio write user pass response: %v", err)
return err
}
if status != byte(0) {
return ErrorInvalidUser
}
}
authResponse := socksio.NewAuthenticationResponse(expectedAuthMethod) request, err := socksio.ReadRequest(reader)
err = socksio.WriteAuthentication(connection, authResponse)
if err != nil {
log.Error("Error on socksio write authentication: %v", err)
return err
}
if server.config.AuthMethod == JsonAuthMethodUserPass {
upRequest, err := socksio.ReadUserPassRequest(reader)
if err != nil { if err != nil {
log.Error("Failed to read username and password: %v", err) log.Error("Error on reading socks request: %v", err)
return err return err
} }
status := byte(0)
if !upRequest.IsValid(server.config.Username, server.config.Password) {
status = byte(0xFF)
}
upResponse := socksio.NewSocks5UserPassResponse(status)
err = socksio.WriteUserPassResponse(connection, upResponse)
if err != nil {
log.Error("Error on socksio write user pass response: %v", err)
return err
}
if status != byte(0) {
return ErrorInvalidUser
}
}
request, err := socksio.ReadRequest(reader)
if err != nil {
log.Error("Error on reading socks request: %v", err)
return err
}
response := socksio.NewSocks5Response()
if request.Command == socksio.CmdBind || request.Command == socksio.CmdUdpAssociate {
response := socksio.NewSocks5Response() response := socksio.NewSocks5Response()
response.Error = socksio.ErrorCommandNotSupported
if request.Command == socksio.CmdBind || request.Command == socksio.CmdUdpAssociate {
response := socksio.NewSocks5Response()
response.Error = socksio.ErrorCommandNotSupported
err = socksio.WriteResponse(connection, response)
if err != nil {
log.Error("Error on socksio write response: %v", err)
return err
}
log.Warning("Unsupported socks command %d", request.Command)
return ErrorCommandNotSupported
}
response.Error = socksio.ErrorSuccess
response.Port = request.Port
response.AddrType = request.AddrType
switch response.AddrType {
case socksio.AddrTypeIPv4:
copy(response.IPv4[:], request.IPv4[:])
case socksio.AddrTypeIPv6:
copy(response.IPv6[:], request.IPv6[:])
case socksio.AddrTypeDomain:
response.Domain = request.Domain
}
err = socksio.WriteResponse(connection, response) err = socksio.WriteResponse(connection, response)
if err != nil { if err != nil {
log.Error("Error on socksio write response: %v", err) log.Error("Error on socksio write response: %v", err)
return err return err
} }
log.Warning("Unsupported socks command %d", request.Command)
return ErrorCommandNotSupported dest = request.Destination()
} }
response.Error = socksio.ErrorSuccess ray := server.vPoint.NewInboundConnectionAccepted(dest)
response.Port = request.Port
response.AddrType = request.AddrType
switch response.AddrType {
case socksio.AddrTypeIPv4:
copy(response.IPv4[:], request.IPv4[:])
case socksio.AddrTypeIPv6:
copy(response.IPv6[:], request.IPv6[:])
case socksio.AddrTypeDomain:
response.Domain = request.Domain
}
err = socksio.WriteResponse(connection, response)
if err != nil {
log.Error("Error on socksio write response: %v", err)
return err
}
ray := server.vPoint.NewInboundConnectionAccepted(request.Destination())
input := ray.InboundInput() input := ray.InboundInput()
output := ray.InboundOutput() output := ray.InboundOutput()
readFinish := make(chan bool) readFinish := make(chan bool)