github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/proxy_socks4.go (about) 1 /* 2 * Copyright (c) 2015, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 /* 20 * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> 21 * All rights reserved. 22 * 23 * Redistribution and use in source and binary forms, with or without 24 * modification, are permitted provided that the following conditions are met: 25 * 26 * * Redistributions of source code must retain the above copyright notice, 27 * this list of conditions and the following disclaimer. 28 * 29 * * Redistributions in binary form must reproduce the above copyright notice, 30 * this list of conditions and the following disclaimer in the documentation 31 * and/or other materials provided with the distribution. 32 * 33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 34 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 37 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 38 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 41 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 43 * POSSIBILITY OF SUCH DAMAGE. 44 * 45 * This is inspired by go.net/proxy/socks5.go: 46 * 47 * Copyright 2011 The Go Authors. All rights reserved. 48 * Use of this source code is governed by a BSD-style 49 * license that can be found in the LICENSE file. 50 */ 51 52 package upstreamproxy 53 54 import ( 55 "fmt" 56 "io" 57 "net" 58 "net/url" 59 "strconv" 60 61 "golang.org/x/net/proxy" 62 ) 63 64 // socks4Proxy is a SOCKS4 proxy. 65 type socks4Proxy struct { 66 hostPort string 67 username string 68 forward proxy.Dialer 69 } 70 71 const ( 72 socks4Version = 0x04 73 socks4CommandConnect = 0x01 74 socks4Null = 0x00 75 socks4ReplyVersion = 0x00 76 77 socks4Granted = 0x5a 78 socks4Rejected = 0x5b 79 socks4RejectedIdentdFailed = 0x5c 80 socks4RejectedIdentdMismatch = 0x5d 81 ) 82 83 func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { 84 s := new(socks4Proxy) 85 s.hostPort = uri.Host 86 s.forward = forward 87 if uri.User != nil { 88 s.username = uri.User.Username() 89 } 90 return s, nil 91 } 92 93 func (s *socks4Proxy) Dial(network, addr string) (net.Conn, error) { 94 if network != "tcp" && network != "tcp4" { 95 return nil, proxyError(fmt.Errorf("invalid network type")) 96 } 97 98 // Deal with the destination address/string. 99 hostStr, portStr, err := net.SplitHostPort(addr) 100 domainDest := "" 101 if err != nil { 102 return nil, proxyError(fmt.Errorf("parsing destination address: %v", err)) 103 } 104 ip := net.ParseIP(hostStr) 105 if ip == nil { 106 // hostStr is not representing an IP, probably a domain name 107 // try to put an invalid IP into DSTIP field and 108 // append domain name terminated by '\x00' at the end of request 109 ip = net.IPv4(0, 0, 0, 1) 110 domainDest = hostStr 111 } 112 ip4 := ip.To4() 113 if ip4 == nil { 114 return nil, proxyError(fmt.Errorf("destination address is not IPv4")) 115 } 116 port, err := strconv.ParseUint(portStr, 10, 16) 117 if err != nil { 118 return nil, proxyError(fmt.Errorf("failed to parse destination port: %v", err)) 119 } 120 121 // Connect to the proxy. 122 c, err := s.forward.Dial("tcp", s.hostPort) 123 if err != nil { 124 return nil, proxyError(fmt.Errorf("failed to dial SOCKS4a proxy: %v", err)) 125 } 126 127 // Make/write the request: 128 // +----+----+----+----+----+----+----+----+----+----+....+----+ 129 // | VN | CD | DSTPORT | DSTIP | USERID |NULL| 130 // +----+----+----+----+----+----+----+----+----+----+....+----+ 131 132 req := make([]byte, 0, 9+len(s.username)) 133 req = append(req, socks4Version) 134 req = append(req, socks4CommandConnect) 135 req = append(req, byte(port>>8), byte(port)) 136 req = append(req, ip4...) 137 if s.username != "" { 138 req = append(req, s.username...) 139 } 140 req = append(req, socks4Null) 141 if domainDest != "" { 142 req = append(req, domainDest...) 143 req = append(req, socks4Null) 144 } 145 _, err = c.Write(req) 146 if err != nil { 147 c.Close() 148 return nil, proxyError(fmt.Errorf("failed to write to SOCKS4a proxy: %v", err)) 149 } 150 151 // Read the response: 152 // +----+----+----+----+----+----+----+----+ 153 // | VN | CD | DSTPORT | DSTIP | 154 // +----+----+----+----+----+----+----+----+ 155 156 var resp [8]byte 157 _, err = io.ReadFull(c, resp[:]) 158 if err != nil { 159 c.Close() 160 return nil, proxyError(fmt.Errorf("failed to read SOCKS4a proxy response: %v", err)) 161 } 162 if resp[0] != socks4ReplyVersion { 163 c.Close() 164 return nil, proxyError(fmt.Errorf("proxy returned invalid SOCKS4 version")) 165 } 166 if resp[1] != socks4Granted { 167 c.Close() 168 return nil, proxyError(fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1]))) 169 } 170 171 return c, nil 172 } 173 174 func socks4ErrorToString(code byte) string { 175 switch code { 176 case socks4Rejected: 177 return "request rejected or failed" 178 case socks4RejectedIdentdFailed: 179 return "request rejected because SOCKS server cannot connect to identd on the client" 180 case socks4RejectedIdentdMismatch: 181 return "request rejected because the client program and identd report different user-ids" 182 default: 183 return fmt.Sprintf("unknown failure code %x", code) 184 } 185 } 186 187 func init() { 188 proxy.RegisterDialerType("socks4a", newSOCKS4) 189 }