168 lines
3.1 KiB
Go
168 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
)
|
|
|
|
const (
|
|
Ver5 = 5
|
|
)
|
|
|
|
const (
|
|
MethodNoAuth uint8 = iota
|
|
MethodGSSAPI
|
|
MethodUserPass
|
|
// X'03' to X'7F' IANA ASSIGNED
|
|
// X'80' to X'FE' RESERVED FOR PRIVATE METHODS
|
|
MethodNoAcceptable = 0xFF
|
|
)
|
|
|
|
const (
|
|
CmdConnect uint8 = 1
|
|
CmdBind = 2
|
|
CmdUdp = 3
|
|
)
|
|
|
|
const (
|
|
AddrIPv4 uint8 = 1
|
|
AddrDomain = 3
|
|
AddrIPv6 = 4
|
|
)
|
|
|
|
const (
|
|
Succeeded uint8 = iota
|
|
Failure
|
|
NotAllowed
|
|
NetUnreachable
|
|
HostUnreachable
|
|
ConnRefused
|
|
TTLExpired
|
|
CmdUnsupported
|
|
AddrUnsupported
|
|
)
|
|
|
|
var (
|
|
ErrBadVersion = errors.New("Bad version")
|
|
ErrBadFormat = errors.New("Bad format")
|
|
ErrBadAddrType = errors.New("Bad address type")
|
|
ErrShortBuffer = errors.New("Short buffer")
|
|
|
|
cmdErrMap = map[uint8]error{
|
|
Failure: errors.New("General SOCKS server failure"),
|
|
NotAllowed: errors.New("Connection not allowed by ruleset"),
|
|
NetUnreachable: errors.New("Network unreachable"),
|
|
HostUnreachable: errors.New("Host unreachable"),
|
|
ConnRefused: errors.New("Connection refused"),
|
|
TTLExpired: errors.New("TTL expired"),
|
|
CmdUnsupported: errors.New("Command not supported"),
|
|
AddrUnsupported: errors.New("Address type not supported"),
|
|
}
|
|
)
|
|
|
|
/*
|
|
+----+-----+-------+------+----------+----------+
|
|
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
|
+----+-----+-------+------+----------+----------+
|
|
| 1 | 1 | X'00' | 1 | Variable | 2 |
|
|
+----+-----+-------+------+----------+----------+
|
|
*/
|
|
type Cmd struct {
|
|
Cmd uint8
|
|
AddrType uint8
|
|
Addr string
|
|
Port uint16
|
|
}
|
|
|
|
func NewCmd(cmd uint8, atype uint8, addr string, port uint16) *Cmd {
|
|
return &Cmd{
|
|
Cmd: cmd,
|
|
AddrType: atype,
|
|
Addr: addr,
|
|
Port: port,
|
|
}
|
|
}
|
|
|
|
func ReadCmd(r io.Reader) (*Cmd, error) {
|
|
b := make([]byte, 256)
|
|
n, err := r.Read(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n < 10 {
|
|
return nil, ErrBadFormat
|
|
}
|
|
if b[0] != Ver5 {
|
|
return nil, ErrBadVersion
|
|
}
|
|
|
|
cmd := &Cmd{
|
|
Cmd: b[1],
|
|
AddrType: b[3],
|
|
}
|
|
|
|
pos := 4
|
|
|
|
switch cmd.AddrType {
|
|
case AddrIPv4:
|
|
if n != 10 {
|
|
return nil, ErrBadFormat
|
|
}
|
|
cmd.Addr = net.IP(b[pos : pos+4]).String()
|
|
pos += 4
|
|
case AddrIPv6:
|
|
if n != 22 {
|
|
return nil, ErrBadFormat
|
|
}
|
|
cmd.Addr = net.IP(b[pos : pos+16]).String()
|
|
pos += 16
|
|
case AddrDomain:
|
|
length := int(b[pos])
|
|
if n != 4+1+length+2 {
|
|
return nil, ErrBadFormat
|
|
}
|
|
|
|
pos++
|
|
cmd.Addr = string(b[pos : pos+length])
|
|
pos += length
|
|
default:
|
|
return nil, ErrBadAddrType
|
|
}
|
|
|
|
cmd.Port = binary.BigEndian.Uint16(b[pos:])
|
|
|
|
return cmd, nil
|
|
}
|
|
|
|
func (cmd *Cmd) Write(w io.Writer) (err error) {
|
|
b := make([]byte, 256)
|
|
|
|
b[0] = Ver5
|
|
b[1] = cmd.Cmd
|
|
b[3] = cmd.AddrType
|
|
pos := 4
|
|
|
|
switch cmd.AddrType {
|
|
case AddrIPv4:
|
|
pos += copy(b[pos:], net.ParseIP(cmd.Addr).To4())
|
|
case AddrDomain:
|
|
b[pos] = byte(len(cmd.Addr))
|
|
pos++
|
|
pos += copy(b[pos:], []byte(cmd.Addr))
|
|
case AddrIPv6:
|
|
pos += copy(b[pos:], net.ParseIP(cmd.Addr).To16())
|
|
}
|
|
binary.BigEndian.PutUint16(b[pos:], cmd.Port)
|
|
pos += 2
|
|
|
|
_, err = w.Write(b[:pos])
|
|
|
|
return
|
|
}
|
|
|
|
func (cmd *Cmd) GetError() error {
|
|
return cmdErrMap[cmd.Cmd]
|
|
}
|