gfwproxyshadowsocksdocker-imagegogolanggvisornatnetworksocks4socks5tcpip-stacktortun-devicetun2sockstunneludpwireguard
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
9.9 KiB
401 lines
9.9 KiB
// Package socks5 provides SOCKS5 client functionalities.
|
|
package socks5
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
)
|
|
|
|
// AuthMethod is the authentication method as defined in RFC 1928 section 3.
|
|
type AuthMethod = uint8
|
|
|
|
// SOCKS authentication methods as defined in RFC 1928 section 3.
|
|
const (
|
|
MethodNoAuth AuthMethod = 0x00
|
|
MethodUserPass AuthMethod = 0x02
|
|
)
|
|
|
|
// 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
|
|
)
|
|
|
|
func (c Command) String() string {
|
|
switch c {
|
|
case CmdConnect:
|
|
return "CONNECT"
|
|
case CmdBind:
|
|
return "BIND"
|
|
case CmdUDPAssociate:
|
|
return "UDP ASSOCIATE"
|
|
default:
|
|
return "UNDEFINED"
|
|
}
|
|
}
|
|
|
|
type Atyp = uint8
|
|
|
|
// SOCKS address types as defined in RFC 1928 section 5.
|
|
const (
|
|
AtypIPv4 Atyp = 0x01
|
|
AtypDomainName Atyp = 0x03
|
|
AtypIPv6 Atyp = 0x04
|
|
)
|
|
|
|
// 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 fmt.Sprintf("unassigned <%#02x>", uint8(r))
|
|
}
|
|
}
|
|
|
|
// 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 method uint8
|
|
if user != nil {
|
|
method = MethodUserPass /* USERNAME/PASSWORD */
|
|
} else {
|
|
method = MethodNoAuth /* NO AUTHENTICATION REQUIRED */
|
|
}
|
|
|
|
// VER, NMETHODS, METHODS
|
|
if _, err := rw.Write([]byte{Version, 0x01 /* NMETHODS */, method}); 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] == MethodUserPass /* USERNAME/PASSWORD */ {
|
|
if user == nil {
|
|
return nil, errors.New("auth required")
|
|
}
|
|
|
|
authMsgLen := 1 + 1 + len(user.Username) + 1 + len(user.Password)
|
|
if authMsgLen > MaxAuthLen {
|
|
return nil, errors.New("auth message too long")
|
|
}
|
|
|
|
// password protocol version
|
|
authMsg := bytes.NewBuffer(make([]byte, 0, authMsgLen))
|
|
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 _, 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] != MethodNoAuth /* NO AUTHENTICATION REQUIRED */ {
|
|
return nil, errors.New("unsupported method")
|
|
}
|
|
|
|
// VER, CMD, RSV, ADDR
|
|
if _, err := rw.Write(bytes.Join([][]byte{{Version, byte(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("%s: %s", command, rep)
|
|
}
|
|
|
|
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]
|
|
}
|
|
|
|
// SerializeAddr serializes destination address and port to Addr.
|
|
// If a domain name is provided, AtypDomainName would be used first.
|
|
func SerializeAddr(domainName string, dstIP net.IP, dstPort uint16) Addr {
|
|
var (
|
|
buf [][]byte
|
|
port [2]byte
|
|
)
|
|
binary.BigEndian.PutUint16(port[:], dstPort)
|
|
|
|
if domainName != "" /* Domain Name */ {
|
|
length := len(domainName)
|
|
buf = [][]byte{{AtypDomainName, uint8(length)}, []byte(domainName), port[:]}
|
|
} else if dstIP.To4() != nil /* IPv4 */ {
|
|
buf = [][]byte{{AtypIPv4}, dstIP.To4(), port[:]}
|
|
} else /* IPv6 */ {
|
|
buf = [][]byte{{AtypIPv6}, dstIP.To16(), port[:]}
|
|
}
|
|
return bytes.Join(buf, nil)
|
|
}
|
|
|
|
// ParseAddr parses a socks addr from net.Addr.
|
|
// This is a fast path of ParseAddrString(addr.String())
|
|
func ParseAddr(addr net.Addr) Addr {
|
|
switch v := addr.(type) {
|
|
case *net.TCPAddr:
|
|
return SerializeAddr("", v.IP, uint16(v.Port))
|
|
case *net.UDPAddr:
|
|
return SerializeAddr("", v.IP, uint16(v.Port))
|
|
default:
|
|
return ParseAddrString(addr.String())
|
|
}
|
|
}
|
|
|
|
// ParseAddrString parses the address in string s to Addr. Returns nil if failed.
|
|
func ParseAddrString(s string) Addr {
|
|
host, port, err := net.SplitHostPort(s)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
dstPort, err := strconv.ParseUint(port, 10, 16)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if ip := net.ParseIP(host); ip != nil {
|
|
return SerializeAddr("", ip, uint16(dstPort))
|
|
}
|
|
return SerializeAddr(host, nil, uint16(dstPort))
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// The FRAG field indicates whether or not this datagram is one of a
|
|
// number of fragments. If implemented, the high-order bit indicates
|
|
// end-of-fragment sequence, while a value of X'00' indicates that this
|
|
// datagram is standalone. Values between 1 and 127 indicate the
|
|
// fragment position within a fragment sequence. Each receiver will
|
|
// have a REASSEMBLY QUEUE and a REASSEMBLY TIMER associated with these
|
|
// fragments. The reassembly queue must be reinitialized and the
|
|
// associated fragments abandoned whenever the REASSEMBLY TIMER expires,
|
|
// or a new datagram arrives carrying a FRAG field whose value is less
|
|
// than the highest FRAG value processed for this fragment sequence.
|
|
// The reassembly timer MUST be no less than 5 seconds. It is
|
|
// recommended that fragmentation be avoided by applications wherever
|
|
// possible.
|
|
//
|
|
// Ref: https://datatracker.ietf.org/doc/html/rfc1928#section-7
|
|
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
|
|
}
|
|
|