mirror of https://github.com/subgraph/fw-daemon
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.
281 lines
6.5 KiB
281 lines
6.5 KiB
/*
|
|
* common.go - SOCSK5 common definitons/routines.
|
|
*
|
|
* To the extent possible under law, Yawning Angel has waived all copyright and
|
|
* related or neighboring rights to or-ctl-filter, using the creative commons
|
|
* "cc0" public domain dedication. See LICENSE or
|
|
* <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
|
*/
|
|
|
|
// Package socks5 implements a SOCKS5 client/server. For more information see
|
|
// RFC 1928 and RFC 1929.
|
|
//
|
|
// Notes:
|
|
// * GSSAPI authentication, is NOT supported.
|
|
// * The authentication provided by the client is always accepted.
|
|
// * A lot of the code is shamelessly stolen from obfs4proxy.
|
|
package socks5
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
version = 0x05
|
|
rsv = 0x00
|
|
|
|
atypIPv4 = 0x01
|
|
atypDomainName = 0x03
|
|
atypIPv6 = 0x04
|
|
|
|
authNoneRequired = 0x00
|
|
authUsernamePassword = 0x02
|
|
authNoAcceptableMethods = 0xff
|
|
|
|
inboundTimeout = 5 * time.Second
|
|
requestTimeout = 30 * time.Second
|
|
)
|
|
|
|
var errInvalidAtyp = errors.New("invalid address type")
|
|
|
|
// ReplyCode is a SOCKS 5 reply code.
|
|
type ReplyCode byte
|
|
|
|
// The various SOCKS 5 reply codes from RFC 1928.
|
|
const (
|
|
ReplySucceeded ReplyCode = iota
|
|
ReplyGeneralFailure
|
|
ReplyConnectionNotAllowed
|
|
ReplyNetworkUnreachable
|
|
ReplyHostUnreachable
|
|
ReplyConnectionRefused
|
|
ReplyTTLExpired
|
|
ReplyCommandNotSupported
|
|
ReplyAddressNotSupported
|
|
)
|
|
|
|
// Command is a SOCKS 5 command.
|
|
type Command byte
|
|
|
|
// The various SOCKS 5 commands.
|
|
const (
|
|
CommandConnect Command = 0x01
|
|
CommandTorResolve Command = 0xf0
|
|
CommandTorResolvePTR Command = 0xf1
|
|
)
|
|
|
|
// Address is a SOCKS 5 address + port.
|
|
type Address struct {
|
|
atyp uint8
|
|
raw []byte
|
|
addrStr string
|
|
portStr string
|
|
}
|
|
|
|
// FromString parses the provided "host:port" format address and populates the
|
|
// Address fields.
|
|
func (addr *Address) FromString(addrStr string) (err error) {
|
|
addr.addrStr, addr.portStr, err = net.SplitHostPort(addrStr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var raw []byte
|
|
if ip := net.ParseIP(addr.addrStr); ip != nil {
|
|
if v4Addr := ip.To4(); v4Addr != nil {
|
|
raw = append(raw, atypIPv4)
|
|
raw = append(raw, v4Addr...)
|
|
} else if v6Addr := ip.To16(); v6Addr != nil {
|
|
raw = append(raw, atypIPv6)
|
|
raw = append(raw, v6Addr...)
|
|
} else {
|
|
return errors.New("unsupported IP address type")
|
|
}
|
|
} else {
|
|
// Must be a FQDN.
|
|
if len(addr.addrStr) > 255 {
|
|
return fmt.Errorf("invalid FQDN, len > 255 bytes (%d bytes)", len(addr.addrStr))
|
|
}
|
|
raw = append(raw, atypDomainName)
|
|
raw = append(raw, addr.addrStr...)
|
|
}
|
|
|
|
var port uint64
|
|
if port, err = strconv.ParseUint(addr.portStr, 10, 16); err != nil {
|
|
return
|
|
}
|
|
raw = append(raw, byte(port>>8))
|
|
raw = append(raw, byte(port&0xff))
|
|
|
|
addr.raw = raw
|
|
return
|
|
}
|
|
|
|
// String returns the string representation of the address, in "host:port"
|
|
// format.
|
|
func (addr *Address) String() string {
|
|
return addr.addrStr + ":" + addr.portStr
|
|
}
|
|
|
|
// HostPort returns the string representation of the addess, split into the
|
|
// host and port components.
|
|
func (addr *Address) HostPort() (string, string) {
|
|
return addr.addrStr, addr.portStr
|
|
}
|
|
|
|
// Type returns the address type from the connect command this address was
|
|
// parsed from
|
|
func (addr *Address) Type() uint8 {
|
|
return addr.atyp
|
|
}
|
|
|
|
func (addr *Address) read(conn net.Conn) (err error) {
|
|
// The address looks like:
|
|
// uint8_t atyp
|
|
// uint8_t addr[] (Length depends on atyp)
|
|
// uint16_t port
|
|
|
|
// Read the atype.
|
|
var atyp byte
|
|
if atyp, err = readByte(conn); err != nil {
|
|
return
|
|
}
|
|
addr.raw = append(addr.raw, atyp)
|
|
|
|
// Read the address.
|
|
var rawAddr []byte
|
|
switch atyp {
|
|
case atypIPv4:
|
|
rawAddr = make([]byte, net.IPv4len)
|
|
if _, err = io.ReadFull(conn, rawAddr); err != nil {
|
|
return
|
|
}
|
|
v4Addr := net.IPv4(rawAddr[0], rawAddr[1], rawAddr[2], rawAddr[3])
|
|
addr.addrStr = v4Addr.String()
|
|
case atypDomainName:
|
|
var alen byte
|
|
if alen, err = readByte(conn); err != nil {
|
|
return
|
|
}
|
|
if alen == 0 {
|
|
return fmt.Errorf("domain name with 0 length")
|
|
}
|
|
rawAddr = make([]byte, alen)
|
|
addr.raw = append(addr.raw, alen)
|
|
if _, err = io.ReadFull(conn, rawAddr); err != nil {
|
|
return
|
|
}
|
|
addr.addrStr = string(rawAddr)
|
|
case atypIPv6:
|
|
rawAddr = make([]byte, net.IPv6len)
|
|
if _, err = io.ReadFull(conn, rawAddr); err != nil {
|
|
return
|
|
}
|
|
v6Addr := make(net.IP, net.IPv6len)
|
|
copy(v6Addr[:], rawAddr)
|
|
addr.addrStr = fmt.Sprintf("[%s]", v6Addr.String())
|
|
default:
|
|
return errInvalidAtyp
|
|
}
|
|
addr.atyp = atyp
|
|
addr.raw = append(addr.raw, rawAddr...)
|
|
|
|
// Read the port.
|
|
var rawPort [2]byte
|
|
if _, err = io.ReadFull(conn, rawPort[:]); err != nil {
|
|
return
|
|
}
|
|
port := int(rawPort[0])<<8 | int(rawPort[1])
|
|
addr.portStr = fmt.Sprintf("%d", port)
|
|
addr.raw = append(addr.raw, rawPort[:]...)
|
|
|
|
return
|
|
}
|
|
|
|
// ErrorToReplyCode converts an error to the "best" reply code.
|
|
func ErrorToReplyCode(err error) ReplyCode {
|
|
if cErr, ok := err.(clientError); ok {
|
|
return ReplyCode(cErr)
|
|
}
|
|
opErr, ok := err.(*net.OpError)
|
|
if !ok {
|
|
return ReplyGeneralFailure
|
|
}
|
|
|
|
errno, ok := opErr.Err.(syscall.Errno)
|
|
if !ok {
|
|
return ReplyGeneralFailure
|
|
}
|
|
switch errno {
|
|
case syscall.EADDRNOTAVAIL:
|
|
return ReplyAddressNotSupported
|
|
case syscall.ETIMEDOUT:
|
|
return ReplyTTLExpired
|
|
case syscall.ENETUNREACH:
|
|
return ReplyNetworkUnreachable
|
|
case syscall.EHOSTUNREACH:
|
|
return ReplyHostUnreachable
|
|
case syscall.ECONNREFUSED, syscall.ECONNRESET:
|
|
return ReplyConnectionRefused
|
|
default:
|
|
return ReplyGeneralFailure
|
|
}
|
|
}
|
|
|
|
// Request describes a SOCKS 5 request.
|
|
type Request struct {
|
|
Auth AuthInfo
|
|
Cmd Command
|
|
Addr Address
|
|
|
|
conn net.Conn
|
|
}
|
|
|
|
type clientError ReplyCode
|
|
|
|
func (e clientError) Error() string {
|
|
switch ReplyCode(e) {
|
|
case ReplySucceeded:
|
|
return "socks5: succeeded"
|
|
case ReplyGeneralFailure:
|
|
return "socks5: general failure"
|
|
case ReplyConnectionNotAllowed:
|
|
return "socks5: connection not allowed"
|
|
case ReplyNetworkUnreachable:
|
|
return "socks5: network unreachable"
|
|
case ReplyHostUnreachable:
|
|
return "socks5: host unreachable"
|
|
case ReplyConnectionRefused:
|
|
return "socks5: connection refused"
|
|
case ReplyTTLExpired:
|
|
return "socks5: ttl expired"
|
|
case ReplyCommandNotSupported:
|
|
return "socks5: command not supported"
|
|
case ReplyAddressNotSupported:
|
|
return "socks5: address not supported"
|
|
default:
|
|
return fmt.Sprintf("socks5: reply code: 0x%02x", e)
|
|
}
|
|
}
|
|
|
|
func readByte(conn net.Conn) (byte, error) {
|
|
var tmp [1]byte
|
|
if _, err := conn.Read(tmp[:]); err != nil {
|
|
return 0, err
|
|
}
|
|
return tmp[0], nil
|
|
}
|
|
|
|
func validateByte(descr string, val, expected byte) error {
|
|
if val != expected {
|
|
return fmt.Errorf("message field '%s' was 0x%02x (expected 0x%02x)", descr, val, expected)
|
|
}
|
|
return nil
|
|
}
|