123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- // 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
- }
- closeConn := &conn
- defer func() {
- if closeConn != nil {
- (*closeConn).Close()
- }
- }()
- host, portStr, err := net.SplitHostPort(addr)
- if err != nil {
- return nil, err
- }
- port, err := strconv.Atoi(portStr)
- if err != nil {
- return nil, errors.New("proxy: failed to parse port number: " + portStr)
- }
- if port < 1 || port > 0xffff {
- return nil, 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 nil, 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 nil, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
- }
- if buf[0] != 5 {
- return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
- }
- if buf[1] == 0xff {
- return nil, 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 nil, 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 nil, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
- }
- if buf[1] != 0 {
- return nil, 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 nil, 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 nil, 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 nil, 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 nil, 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 nil, errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
- }
- bytesToDiscard = int(buf[0])
- default:
- return nil, 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 nil, 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 nil, errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
- }
- closeConn = nil
- return conn, nil
- }
|