1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-12-21 01:27:03 -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 (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/v2ray/v2ray-core/log"
v2net "github.com/v2ray/v2ray-core/net"
)
const (
socksVersion = uint8(5)
socksVersion = byte(0x05)
socks4Version = byte(0x04)
AuthNotRequired = byte(0x00)
AuthGssApi = byte(0x01)
AuthUserPass = byte(0x02)
AuthNoMatchingMethod = byte(0xFF)
Socks4RequestGranted = byte(90)
Socks4RequestRejected = byte(91)
)
var (
ErrorSocksVersion4 = errors.New("Using SOCKS version 4.")
)
// Authentication request header of Socks5 protocol
@ -25,6 +35,13 @@ 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 {
@ -34,10 +51,11 @@ func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
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)
nBytes, err := reader.Read(buffer)
if err != nil {
log.Error("Failed to read socks authentication: %v", err)
return
}
if nBytes < 2 {
@ -45,6 +63,15 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, err
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]
if auth.version != socksVersion {
err = fmt.Errorf("Unknown SOCKS version %d", auth.version)
@ -70,6 +97,12 @@ type Socks5AuthenticationResponse struct {
authMethod byte
}
type Socks4AuthenticationResponse struct {
result byte
port uint16
ip []byte
}
func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
return &Socks5AuthenticationResponse{
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 {
_, err := writer.Write([]byte{r.version, r.authMethod})
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 {
version byte
username string

View File

@ -30,13 +30,30 @@ func TestAuthenticationRequestRead(t *testing.T) {
0x01, // nMethods
0x02, // methods
}
request, err := ReadAuthentication(bytes.NewReader(rawRequest))
request, _, err := ReadAuthentication(bytes.NewReader(rawRequest))
assert.Error(err).IsNil()
assert.Byte(request.version).Named("Version").Equals(0x05)
assert.Byte(request.nMethods).Named("#Methods").Equals(0x01)
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) {
assert := unit.Assert(t)

View File

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

View File

@ -23,7 +23,7 @@ func (vconn *FreedomConnection) Start(ray core.OutboundRay) error {
output := ray.OutboundOutput()
conn, err := net.Dial("tcp", vconn.dest.String())
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())

View File

@ -64,92 +64,113 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
reader := bufio.NewReader(connection)
auth, err := socksio.ReadAuthentication(reader)
if err != nil {
auth, auth4, err := socksio.ReadAuthentication(reader)
if err != nil && err != socksio.ErrorSocksVersion4 {
log.Error("Error on reading authentication: %v", err)
return err
}
expectedAuthMethod := socksio.AuthNotRequired
if server.config.AuthMethod == JsonAuthMethodUserPass {
expectedAuthMethod = socksio.AuthUserPass
}
var dest v2net.Address
if !auth.HasAuthMethod(expectedAuthMethod) {
authResponse := socksio.NewAuthenticationResponse(socksio.AuthNoMatchingMethod)
// TODO refactor this part
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)
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
}
if server.config.AuthMethod == JsonAuthMethodUserPass {
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)
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)
request, err := socksio.ReadRequest(reader)
if err != nil {
log.Error("Failed to read username and password: %v", err)
log.Error("Error on reading socks request: %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
}
}
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.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)
if err != nil {
log.Error("Error on socksio write response: %v", err)
return err
}
log.Warning("Unsupported socks command %d", request.Command)
return ErrorCommandNotSupported
dest = request.Destination()
}
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)
if err != nil {
log.Error("Error on socksio write response: %v", err)
return err
}
ray := server.vPoint.NewInboundConnectionAccepted(request.Destination())
ray := server.vPoint.NewInboundConnectionAccepted(dest)
input := ray.InboundInput()
output := ray.InboundOutput()
readFinish := make(chan bool)