214 lines
5.7 KiB
Go
214 lines
5.7 KiB
Go
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package proxy
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"io"
|
||
|
"net"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||
|
// with an optional username and password. See RFC 1928.
|
||
|
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
||
|
s := &socks5{
|
||
|
network: network,
|
||
|
addr: addr,
|
||
|
forward: forward,
|
||
|
}
|
||
|
if auth != nil {
|
||
|
s.user = auth.User
|
||
|
s.password = auth.Password
|
||
|
}
|
||
|
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
type socks5 struct {
|
||
|
user, password string
|
||
|
network, addr string
|
||
|
forward Dialer
|
||
|
}
|
||
|
|
||
|
const socks5Version = 5
|
||
|
|
||
|
const (
|
||
|
socks5AuthNone = 0
|
||
|
socks5AuthPassword = 2
|
||
|
)
|
||
|
|
||
|
const socks5Connect = 1
|
||
|
|
||
|
const (
|
||
|
socks5IP4 = 1
|
||
|
socks5Domain = 3
|
||
|
socks5IP6 = 4
|
||
|
)
|
||
|
|
||
|
var socks5Errors = []string{
|
||
|
"",
|
||
|
"general failure",
|
||
|
"connection forbidden",
|
||
|
"network unreachable",
|
||
|
"host unreachable",
|
||
|
"connection refused",
|
||
|
"TTL expired",
|
||
|
"command not supported",
|
||
|
"address type not supported",
|
||
|
}
|
||
|
|
||
|
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||
|
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
||
|
switch network {
|
||
|
case "tcp", "tcp6", "tcp4":
|
||
|
default:
|
||
|
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||
|
}
|
||
|
|
||
|
conn, err := s.forward.Dial(s.network, s.addr)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if err := s.connect(conn, addr); err != nil {
|
||
|
conn.Close()
|
||
|
return nil, err
|
||
|
}
|
||
|
return conn, nil
|
||
|
}
|
||
|
|
||
|
// connect takes an existing connection to a socks5 proxy server,
|
||
|
// and commands the server to extend that connection to target,
|
||
|
// which must be a canonical address with a host and port.
|
||
|
func (s *socks5) connect(conn net.Conn, target string) error {
|
||
|
host, portStr, err := net.SplitHostPort(target)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
port, err := strconv.Atoi(portStr)
|
||
|
if err != nil {
|
||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||
|
}
|
||
|
if port < 1 || port > 0xffff {
|
||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||
|
}
|
||
|
|
||
|
// the size here is just an estimate
|
||
|
buf := make([]byte, 0, 6+len(host))
|
||
|
|
||
|
buf = append(buf, socks5Version)
|
||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||
|
buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
|
||
|
} else {
|
||
|
buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
|
||
|
}
|
||
|
|
||
|
if _, err := conn.Write(buf); err != nil {
|
||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
if buf[0] != 5 {
|
||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||
|
}
|
||
|
if buf[1] == 0xff {
|
||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||
|
}
|
||
|
|
||
|
if buf[1] == socks5AuthPassword {
|
||
|
buf = buf[:0]
|
||
|
buf = append(buf, 1 /* password protocol version */)
|
||
|
buf = append(buf, uint8(len(s.user)))
|
||
|
buf = append(buf, s.user...)
|
||
|
buf = append(buf, uint8(len(s.password)))
|
||
|
buf = append(buf, s.password...)
|
||
|
|
||
|
if _, err := conn.Write(buf); err != nil {
|
||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
if buf[1] != 0 {
|
||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
buf = buf[:0]
|
||
|
buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
|
||
|
|
||
|
if ip := net.ParseIP(host); ip != nil {
|
||
|
if ip4 := ip.To4(); ip4 != nil {
|
||
|
buf = append(buf, socks5IP4)
|
||
|
ip = ip4
|
||
|
} else {
|
||
|
buf = append(buf, socks5IP6)
|
||
|
}
|
||
|
buf = append(buf, ip...)
|
||
|
} else {
|
||
|
if len(host) > 255 {
|
||
|
return errors.New("proxy: destination hostname too long: " + host)
|
||
|
}
|
||
|
buf = append(buf, socks5Domain)
|
||
|
buf = append(buf, byte(len(host)))
|
||
|
buf = append(buf, host...)
|
||
|
}
|
||
|
buf = append(buf, byte(port>>8), byte(port))
|
||
|
|
||
|
if _, err := conn.Write(buf); err != nil {
|
||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
failure := "unknown error"
|
||
|
if int(buf[1]) < len(socks5Errors) {
|
||
|
failure = socks5Errors[buf[1]]
|
||
|
}
|
||
|
|
||
|
if len(failure) > 0 {
|
||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||
|
}
|
||
|
|
||
|
bytesToDiscard := 0
|
||
|
switch buf[3] {
|
||
|
case socks5IP4:
|
||
|
bytesToDiscard = net.IPv4len
|
||
|
case socks5IP6:
|
||
|
bytesToDiscard = net.IPv6len
|
||
|
case socks5Domain:
|
||
|
_, err := io.ReadFull(conn, buf[:1])
|
||
|
if err != nil {
|
||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
bytesToDiscard = int(buf[0])
|
||
|
default:
|
||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||
|
}
|
||
|
|
||
|
if cap(buf) < bytesToDiscard {
|
||
|
buf = make([]byte, bytesToDiscard)
|
||
|
} else {
|
||
|
buf = buf[:bytesToDiscard]
|
||
|
}
|
||
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
// Also need to discard the port number
|
||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|