mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2025-10-07 09:41:09 +08:00
384 lines
8.6 KiB
Go
Executable File
384 lines
8.6 KiB
Go
Executable File
// Package socks5 provides SOCKS5 client functionalities.
|
|
package socks5
|
|
|
|
// Ref: github.com/Dreamacro/clash/component/socks5
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
)
|
|
|
|
// Version is the protocol version as defined in RFC 1928 section 4.
|
|
const Version = 0x05
|
|
|
|
// Command is request commands as defined in RFC 1928 section 4.
|
|
type Command = uint8
|
|
|
|
// SOCKS request commands as defined in RFC 1928 section 4.
|
|
const (
|
|
CmdConnect Command = 0x01
|
|
CmdBind Command = 0x02
|
|
CmdUDPAssociate Command = 0x03
|
|
)
|
|
|
|
type Atyp = uint8
|
|
|
|
// SOCKS address types as defined in RFC 1928 section 5.
|
|
const (
|
|
AtypIPv4 Atyp = 0x01
|
|
AtypDomainName Atyp = 0x03
|
|
AtypIPv6 Atyp = 0x04
|
|
)
|
|
|
|
// SOCKS reply field as defined in RFC 1928 section 6.
|
|
type Reply uint8
|
|
|
|
func (r Reply) String() string {
|
|
switch r {
|
|
case 0x00:
|
|
return "succeeded"
|
|
case 0x01:
|
|
return "general SOCKS server failure"
|
|
case 0x02:
|
|
return "connection not allowed by ruleset"
|
|
case 0x03:
|
|
return "network unreachable"
|
|
case 0x04:
|
|
return "host unreachable"
|
|
case 0x05:
|
|
return "connection refused"
|
|
case 0x06:
|
|
return "TTL expired"
|
|
case 0x07:
|
|
return "command not supported"
|
|
case 0x08:
|
|
return "address type not supported"
|
|
default:
|
|
return "unassigned"
|
|
}
|
|
}
|
|
|
|
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
|
const MaxAddrLen = 1 + 1 + 255 + 2
|
|
|
|
// MaxAuthLen is the maximum size of user/password field in SOCKS auth.
|
|
const MaxAuthLen = 255
|
|
|
|
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
|
|
type Addr []byte
|
|
|
|
func (a Addr) Valid() bool {
|
|
if len(a) < 1+1+2 /* minimum length */ {
|
|
return false
|
|
}
|
|
|
|
switch a[0] {
|
|
case AtypDomainName:
|
|
if len(a) < 1+1+int(a[1])+2 {
|
|
return false
|
|
}
|
|
case AtypIPv4:
|
|
if len(a) < 1+net.IPv4len+2 {
|
|
return false
|
|
}
|
|
case AtypIPv6:
|
|
if len(a) < 1+net.IPv6len+2 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// String returns string of socks5.Addr.
|
|
func (a Addr) String() string {
|
|
if !a.Valid() {
|
|
return ""
|
|
}
|
|
|
|
var host, port string
|
|
switch a[0] {
|
|
case AtypDomainName:
|
|
hostLen := int(a[1])
|
|
host = string(a[2 : 2+hostLen])
|
|
port = strconv.Itoa(int(binary.BigEndian.Uint16(a[2+hostLen:])))
|
|
case AtypIPv4:
|
|
host = net.IP(a[1 : 1+net.IPv4len]).String()
|
|
port = strconv.Itoa(int(binary.BigEndian.Uint16(a[1+net.IPv4len:])))
|
|
case AtypIPv6:
|
|
host = net.IP(a[1 : 1+net.IPv6len]).String()
|
|
port = strconv.Itoa(int(binary.BigEndian.Uint16(a[1+net.IPv6len:])))
|
|
}
|
|
return net.JoinHostPort(host, port)
|
|
}
|
|
|
|
// UDPAddr converts a socks5.Addr to *net.UDPAddr.
|
|
func (a Addr) UDPAddr() *net.UDPAddr {
|
|
if !a.Valid() {
|
|
return nil
|
|
}
|
|
|
|
var ip []byte
|
|
var port int
|
|
switch a[0] {
|
|
case AtypDomainName /* unsupported */ :
|
|
return nil
|
|
case AtypIPv4:
|
|
ip = make([]byte, net.IPv4len)
|
|
copy(ip, a[1:1+net.IPv4len])
|
|
port = int(binary.BigEndian.Uint16(a[1+net.IPv4len:]))
|
|
case AtypIPv6:
|
|
ip = make([]byte, net.IPv6len)
|
|
copy(ip, a[1:1+net.IPv6len])
|
|
port = int(binary.BigEndian.Uint16(a[1+net.IPv6len:]))
|
|
}
|
|
return &net.UDPAddr{IP: ip, Port: port}
|
|
}
|
|
|
|
// User provides basic socks5 auth functionality.
|
|
type User struct {
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
// ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side.
|
|
func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (Addr, error) {
|
|
buf := make([]byte, MaxAddrLen)
|
|
var err error
|
|
|
|
// VER, NMETHODS, METHODS
|
|
if user != nil {
|
|
_, err = rw.Write([]byte{Version, 0x01 /* NMETHODS */, 0x02 /* USERNAME/PASSWORD */})
|
|
} else {
|
|
_, err = rw.Write([]byte{Version, 0x01 /* NMETHODS */, 0x00 /* NO AUTHENTICATION REQUIRED */})
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// VER, METHOD
|
|
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if buf[0] != Version {
|
|
return nil, errors.New("socks version mismatched")
|
|
}
|
|
|
|
if buf[1] == 0x02 /* USERNAME/PASSWORD */ {
|
|
if user == nil {
|
|
return nil, errors.New("auth required")
|
|
}
|
|
|
|
// password protocol version
|
|
authMsg := &bytes.Buffer{}
|
|
authMsg.WriteByte(0x01 /* VER */)
|
|
authMsg.WriteByte(byte(len(user.Username)) /* ULEN */)
|
|
authMsg.WriteString(user.Username /* UNAME */)
|
|
authMsg.WriteByte(byte(len(user.Password)) /* PLEN */)
|
|
authMsg.WriteString(user.Password /* PASSWD */)
|
|
|
|
if len(authMsg.Bytes()) > MaxAuthLen {
|
|
return nil, errors.New("auth message too long")
|
|
}
|
|
|
|
if _, err := rw.Write(authMsg.Bytes()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if buf[1] != 0x00 /* STATUS of SUCCESS */ {
|
|
return nil, errors.New("rejected username/password")
|
|
}
|
|
|
|
} else if buf[1] != 0x00 /* NO AUTHENTICATION REQUIRED */ {
|
|
return nil, errors.New("unsupported method")
|
|
}
|
|
|
|
// VER, CMD, RSV, ADDR
|
|
if _, err := rw.Write(bytes.Join([][]byte{{Version, command, 0x00 /* RSV */}, addr}, nil)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// VER, REP, RSV
|
|
if _, err := io.ReadFull(rw, buf[:3]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rep := Reply(buf[1]); rep != 0x00 /* SUCCEEDED */ {
|
|
return nil, fmt.Errorf("%#X: %s", rep, rep.String())
|
|
}
|
|
|
|
return ReadAddr(rw, buf)
|
|
}
|
|
|
|
func ReadAddr(r io.Reader, b []byte) (Addr, error) {
|
|
if len(b) < MaxAddrLen {
|
|
return nil, io.ErrShortBuffer
|
|
}
|
|
|
|
// read 1st byte for address type
|
|
if _, err := io.ReadFull(r, b[:1]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch b[0] /* ATYP */ {
|
|
case AtypDomainName:
|
|
// read 2nd byte for domain length
|
|
if _, err := io.ReadFull(r, b[1:2]); err != nil {
|
|
return nil, err
|
|
}
|
|
domainLength := uint16(b[1])
|
|
_, err := io.ReadFull(r, b[2:2+domainLength+2])
|
|
return b[:1+1+domainLength+2], err
|
|
case AtypIPv4:
|
|
_, err := io.ReadFull(r, b[1:1+net.IPv4len+2])
|
|
return b[:1+net.IPv4len+2], err
|
|
case AtypIPv6:
|
|
_, err := io.ReadFull(r, b[1:1+net.IPv6len+2])
|
|
return b[:1+net.IPv6len+2], err
|
|
default:
|
|
return nil, errors.New("invalid address type")
|
|
}
|
|
}
|
|
|
|
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
|
func SplitAddr(b []byte) Addr {
|
|
addrLen := 1
|
|
if len(b) < addrLen {
|
|
return nil
|
|
}
|
|
|
|
switch b[0] {
|
|
case AtypDomainName:
|
|
if len(b) < 2 {
|
|
return nil
|
|
}
|
|
addrLen = 1 + 1 + int(b[1]) + 2
|
|
case AtypIPv4:
|
|
addrLen = 1 + net.IPv4len + 2
|
|
case AtypIPv6:
|
|
addrLen = 1 + net.IPv6len + 2
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
if len(b) < addrLen {
|
|
return nil
|
|
}
|
|
|
|
return b[:addrLen]
|
|
}
|
|
|
|
// ParseAddr parses the address in string s. Returns nil if failed.
|
|
func ParseAddr(s string) Addr {
|
|
host, port, err := net.SplitHostPort(s)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var addr Addr
|
|
if ip := net.ParseIP(host); ip != nil {
|
|
if ip4 := ip.To4(); ip4 != nil {
|
|
addr = make([]byte, 1+net.IPv4len+2)
|
|
addr[0] = AtypIPv4
|
|
copy(addr[1:], ip4)
|
|
} else {
|
|
addr = make([]byte, 1+net.IPv6len+2)
|
|
addr[0] = AtypIPv6
|
|
copy(addr[1:], ip)
|
|
}
|
|
} else {
|
|
if len(host) > 255 {
|
|
return nil
|
|
}
|
|
addr = make([]byte, 1+1+len(host)+2)
|
|
addr[0] = AtypDomainName
|
|
addr[1] = byte(len(host))
|
|
copy(addr[2:], host)
|
|
}
|
|
|
|
p, err := strconv.ParseUint(port, 10, 16)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
binary.BigEndian.PutUint16(addr[len(addr)-2:], uint16(p))
|
|
|
|
return addr
|
|
}
|
|
|
|
// ParseAddrToSocksAddr parse a socks addr from net.addr
|
|
// This is a fast path of ParseAddr(addr.String())
|
|
func ParseAddrToSocksAddr(addr net.Addr) Addr {
|
|
var ip net.IP
|
|
var port int
|
|
if udpAddr, ok := addr.(*net.UDPAddr); ok {
|
|
ip = udpAddr.IP
|
|
port = udpAddr.Port
|
|
} else if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
|
ip = tcpAddr.IP
|
|
port = tcpAddr.Port
|
|
}
|
|
|
|
// fallback parse
|
|
if ip == nil {
|
|
return ParseAddr(addr.String())
|
|
}
|
|
|
|
var parsed Addr
|
|
if ip4 := ip.To4(); ip4 != nil {
|
|
parsed = make([]byte, 1+net.IPv4len+2)
|
|
parsed[0] = AtypIPv4
|
|
copy(parsed[1:], ip4)
|
|
binary.BigEndian.PutUint16(parsed[1+net.IPv4len:], uint16(port))
|
|
} else {
|
|
parsed = make([]byte, 1+net.IPv6len+2)
|
|
parsed[0] = AtypIPv6
|
|
copy(parsed[1:], ip)
|
|
binary.BigEndian.PutUint16(parsed[1+net.IPv6len:], uint16(port))
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
|
|
func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
|
|
if len(packet) < 5 {
|
|
err = errors.New("insufficient length of packet")
|
|
return
|
|
}
|
|
|
|
// packet[0] and packet[1] are reserved
|
|
if !bytes.Equal(packet[:2], []byte{0x00, 0x00}) {
|
|
err = errors.New("reserved fields should be zero")
|
|
return
|
|
}
|
|
|
|
if packet[2] != 0x00 /* fragments */ {
|
|
err = errors.New("discarding fragmented payload")
|
|
return
|
|
}
|
|
|
|
addr = SplitAddr(packet[3:])
|
|
if addr == nil {
|
|
err = errors.New("socks5 UDP addr is nil")
|
|
}
|
|
|
|
payload = packet[3+len(addr):]
|
|
return
|
|
}
|
|
|
|
func EncodeUDPPacket(addr Addr, payload []byte) (packet []byte, err error) {
|
|
if addr == nil {
|
|
return nil, errors.New("address is invalid")
|
|
}
|
|
packet = bytes.Join([][]byte{{0x00, 0x00, 0x00}, addr, payload}, nil)
|
|
return
|
|
}
|