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