Home | History | Annotate | Download | only in proxy
      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package proxy
      6 
      7 import (
      8 	"errors"
      9 	"io"
     10 	"net"
     11 	"strconv"
     12 )
     13 
     14 // SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
     15 // with an optional username and password. See RFC 1928.
     16 func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
     17 	s := &socks5{
     18 		network: network,
     19 		addr:    addr,
     20 		forward: forward,
     21 	}
     22 	if auth != nil {
     23 		s.user = auth.User
     24 		s.password = auth.Password
     25 	}
     26 
     27 	return s, nil
     28 }
     29 
     30 type socks5 struct {
     31 	user, password string
     32 	network, addr  string
     33 	forward        Dialer
     34 }
     35 
     36 const socks5Version = 5
     37 
     38 const (
     39 	socks5AuthNone     = 0
     40 	socks5AuthPassword = 2
     41 )
     42 
     43 const socks5Connect = 1
     44 
     45 const (
     46 	socks5IP4    = 1
     47 	socks5Domain = 3
     48 	socks5IP6    = 4
     49 )
     50 
     51 var socks5Errors = []string{
     52 	"",
     53 	"general failure",
     54 	"connection forbidden",
     55 	"network unreachable",
     56 	"host unreachable",
     57 	"connection refused",
     58 	"TTL expired",
     59 	"command not supported",
     60 	"address type not supported",
     61 }
     62 
     63 // Dial connects to the address addr on the network net via the SOCKS5 proxy.
     64 func (s *socks5) Dial(network, addr string) (net.Conn, error) {
     65 	switch network {
     66 	case "tcp", "tcp6", "tcp4":
     67 	default:
     68 		return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
     69 	}
     70 
     71 	conn, err := s.forward.Dial(s.network, s.addr)
     72 	if err != nil {
     73 		return nil, err
     74 	}
     75 	if err := s.connect(conn, addr); err != nil {
     76 		conn.Close()
     77 		return nil, err
     78 	}
     79 	return conn, nil
     80 }
     81 
     82 // connect takes an existing connection to a socks5 proxy server,
     83 // and commands the server to extend that connection to target,
     84 // which must be a canonical address with a host and port.
     85 func (s *socks5) connect(conn net.Conn, target string) error {
     86 	host, portStr, err := net.SplitHostPort(target)
     87 	if err != nil {
     88 		return err
     89 	}
     90 
     91 	port, err := strconv.Atoi(portStr)
     92 	if err != nil {
     93 		return errors.New("proxy: failed to parse port number: " + portStr)
     94 	}
     95 	if port < 1 || port > 0xffff {
     96 		return errors.New("proxy: port number out of range: " + portStr)
     97 	}
     98 
     99 	// the size here is just an estimate
    100 	buf := make([]byte, 0, 6+len(host))
    101 
    102 	buf = append(buf, socks5Version)
    103 	if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
    104 		buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
    105 	} else {
    106 		buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
    107 	}
    108 
    109 	if _, err := conn.Write(buf); err != nil {
    110 		return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
    111 	}
    112 
    113 	if _, err := io.ReadFull(conn, buf[:2]); err != nil {
    114 		return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
    115 	}
    116 	if buf[0] != 5 {
    117 		return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
    118 	}
    119 	if buf[1] == 0xff {
    120 		return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
    121 	}
    122 
    123 	if buf[1] == socks5AuthPassword {
    124 		buf = buf[:0]
    125 		buf = append(buf, 1 /* password protocol version */)
    126 		buf = append(buf, uint8(len(s.user)))
    127 		buf = append(buf, s.user...)
    128 		buf = append(buf, uint8(len(s.password)))
    129 		buf = append(buf, s.password...)
    130 
    131 		if _, err := conn.Write(buf); err != nil {
    132 			return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
    133 		}
    134 
    135 		if _, err := io.ReadFull(conn, buf[:2]); err != nil {
    136 			return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
    137 		}
    138 
    139 		if buf[1] != 0 {
    140 			return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
    141 		}
    142 	}
    143 
    144 	buf = buf[:0]
    145 	buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
    146 
    147 	if ip := net.ParseIP(host); ip != nil {
    148 		if ip4 := ip.To4(); ip4 != nil {
    149 			buf = append(buf, socks5IP4)
    150 			ip = ip4
    151 		} else {
    152 			buf = append(buf, socks5IP6)
    153 		}
    154 		buf = append(buf, ip...)
    155 	} else {
    156 		if len(host) > 255 {
    157 			return errors.New("proxy: destination hostname too long: " + host)
    158 		}
    159 		buf = append(buf, socks5Domain)
    160 		buf = append(buf, byte(len(host)))
    161 		buf = append(buf, host...)
    162 	}
    163 	buf = append(buf, byte(port>>8), byte(port))
    164 
    165 	if _, err := conn.Write(buf); err != nil {
    166 		return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
    167 	}
    168 
    169 	if _, err := io.ReadFull(conn, buf[:4]); err != nil {
    170 		return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
    171 	}
    172 
    173 	failure := "unknown error"
    174 	if int(buf[1]) < len(socks5Errors) {
    175 		failure = socks5Errors[buf[1]]
    176 	}
    177 
    178 	if len(failure) > 0 {
    179 		return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
    180 	}
    181 
    182 	bytesToDiscard := 0
    183 	switch buf[3] {
    184 	case socks5IP4:
    185 		bytesToDiscard = net.IPv4len
    186 	case socks5IP6:
    187 		bytesToDiscard = net.IPv6len
    188 	case socks5Domain:
    189 		_, err := io.ReadFull(conn, buf[:1])
    190 		if err != nil {
    191 			return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
    192 		}
    193 		bytesToDiscard = int(buf[0])
    194 	default:
    195 		return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
    196 	}
    197 
    198 	if cap(buf) < bytesToDiscard {
    199 		buf = make([]byte, bytesToDiscard)
    200 	} else {
    201 		buf = buf[:bytesToDiscard]
    202 	}
    203 	if _, err := io.ReadFull(conn, buf); err != nil {
    204 		return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
    205 	}
    206 
    207 	// Also need to discard the port number
    208 	if _, err := io.ReadFull(conn, buf[:2]); err != nil {
    209 		return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
    210 	}
    211 
    212 	return nil
    213 }
    214