1// Copyright 2012, Hailiang Wang. 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/* 6Package socks implements a SOCKS (SOCKS4, SOCKS4A and SOCKS5) proxy client. 7 8A complete example using this package: 9 package main 10 11 import ( 12 "code.as/core/socks" 13 "fmt" 14 "net/http" 15 "io/ioutil" 16 ) 17 18 func main() { 19 dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, "127.0.0.1:1080") 20 tr := &http.Transport{Dial: dialSocksProxy} 21 httpClient := &http.Client{Transport: tr} 22 23 bodyText, err := TestHttpsGet(httpClient, "https://h12.io/about") 24 if err != nil { 25 fmt.Println(err.Error()) 26 } 27 fmt.Print(bodyText) 28 } 29 30 func TestHttpsGet(c *http.Client, url string) (bodyText string, err error) { 31 resp, err := c.Get(url) 32 if err != nil { return } 33 defer resp.Body.Close() 34 35 body, err := ioutil.ReadAll(resp.Body) 36 if err != nil { return } 37 bodyText = string(body) 38 return 39 } 40*/ 41package socks 42 43import ( 44 "errors" 45 "fmt" 46 "net" 47 "strconv" 48) 49 50// Constants to choose which version of SOCKS protocol to use. 51const ( 52 SOCKS4 = iota 53 SOCKS4A 54 SOCKS5 55) 56 57// DialSocksProxy returns the dial function to be used in http.Transport object. 58// Argument socksType should be one of SOCKS4, SOCKS4A and SOCKS5. 59// Argument proxy should be in this format "127.0.0.1:1080". 60func DialSocksProxy(socksType int, proxy string) func(string, string) (net.Conn, error) { 61 if socksType == SOCKS5 { 62 return func(_, targetAddr string) (conn net.Conn, err error) { 63 return dialSocks5(proxy, targetAddr) 64 } 65 } 66 67 // SOCKS4, SOCKS4A 68 return func(_, targetAddr string) (conn net.Conn, err error) { 69 return dialSocks4(socksType, proxy, targetAddr) 70 } 71} 72 73func dialSocks5(proxy, targetAddr string) (conn net.Conn, err error) { 74 // dial TCP 75 conn, err = net.Dial("tcp", proxy) 76 if err != nil { 77 return 78 } 79 80 // version identifier/method selection request 81 req := []byte{ 82 5, // version number 83 1, // number of methods 84 0, // method 0: no authentication (only anonymous access supported for now) 85 } 86 resp, err := sendReceive(conn, req) 87 if err != nil { 88 return 89 } else if len(resp) != 2 { 90 err = errors.New("Server does not respond properly.") 91 return 92 } else if resp[0] != 5 { 93 err = errors.New("Server does not support Socks 5.") 94 return 95 } else if resp[1] != 0 { // no auth 96 err = errors.New("socks method negotiation failed.") 97 return 98 } 99 100 // detail request 101 host, port, err := splitHostPort(targetAddr) 102 req = []byte{ 103 5, // version number 104 1, // connect command 105 0, // reserved, must be zero 106 3, // address type, 3 means domain name 107 byte(len(host)), // address length 108 } 109 req = append(req, []byte(host)...) 110 req = append(req, []byte{ 111 byte(port >> 8), // higher byte of destination port 112 byte(port), // lower byte of destination port (big endian) 113 }...) 114 resp, err = sendReceive(conn, req) 115 if err != nil { 116 return 117 } else if len(resp) != 10 { 118 err = errors.New("Server does not respond properly.") 119 } else if resp[1] != 0 { 120 err = errors.New("Can't complete SOCKS5 connection.") 121 } 122 123 return 124} 125 126func dialSocks4(socksType int, proxy, targetAddr string) (conn net.Conn, err error) { 127 // dial TCP 128 conn, err = net.Dial("tcp", proxy) 129 if err != nil { 130 return 131 } 132 133 // connection request 134 host, port, err := splitHostPort(targetAddr) 135 if err != nil { 136 return 137 } 138 ip := net.IPv4(0, 0, 0, 1).To4() 139 if socksType == SOCKS4 { 140 ip, err = lookupIP(host) 141 if err != nil { 142 return 143 } 144 } 145 req := []byte{ 146 4, // version number 147 1, // command CONNECT 148 byte(port >> 8), // higher byte of destination port 149 byte(port), // lower byte of destination port (big endian) 150 ip[0], ip[1], ip[2], ip[3], // special invalid IP address to indicate the host name is provided 151 0, // user id is empty, anonymous proxy only 152 } 153 if socksType == SOCKS4A { 154 req = append(req, []byte(host+"\x00")...) 155 } 156 157 resp, err := sendReceive(conn, req) 158 if err != nil { 159 return 160 } else if len(resp) != 8 { 161 err = errors.New("Server does not respond properly.") 162 return 163 } 164 switch resp[1] { 165 case 90: 166 // request granted 167 case 91: 168 err = errors.New("Socks connection request rejected or failed.") 169 case 92: 170 err = errors.New("Socks connection request rejected becasue SOCKS server cannot connect to identd on the client.") 171 case 93: 172 err = errors.New("Socks connection request rejected because the client program and identd report different user-ids.") 173 default: 174 err = errors.New("Socks connection request failed, unknown error.") 175 } 176 return 177} 178 179func sendReceive(conn net.Conn, req []byte) (resp []byte, err error) { 180 _, err = conn.Write(req) 181 if err != nil { 182 return 183 } 184 resp, err = readAll(conn) 185 return 186} 187 188func readAll(conn net.Conn) (resp []byte, err error) { 189 resp = make([]byte, 1024) 190 n, err := conn.Read(resp) 191 resp = resp[:n] 192 return 193} 194 195func lookupIP(host string) (ip net.IP, err error) { 196 ips, err := net.LookupIP(host) 197 if err != nil { 198 return 199 } 200 if len(ips) == 0 { 201 err = errors.New(fmt.Sprintf("Cannot resolve host: %s.", host)) 202 return 203 } 204 ip = ips[0].To4() 205 if len(ip) != net.IPv4len { 206 fmt.Println(len(ip), ip) 207 err = errors.New("IPv6 is not supported by SOCKS4.") 208 return 209 } 210 return 211} 212 213func splitHostPort(addr string) (host string, port uint16, err error) { 214 host, portStr, err := net.SplitHostPort(addr) 215 portInt, err := strconv.ParseUint(portStr, 10, 16) 216 port = uint16(portInt) 217 return 218} 219