github.com/database64128/shadowsocks-go@v1.10.2-0.20240315062903-143a773533f1/socks5/stream.go (about) 1 package socks5 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 10 "github.com/database64128/shadowsocks-go/conn" 11 "github.com/database64128/shadowsocks-go/zerocopy" 12 ) 13 14 // SOCKS version 5. 15 const Version = 5 16 17 // SOCKS5 authentication methods as defined in RFC 1928 section 3. 18 const ( 19 MethodNoAuthenticationRequired = 0 20 MethodGSSAPI = 1 21 MethodUsernamePassword = 2 22 MethodNoAcceptable = 0xFF 23 ) 24 25 // SOCKS request commands as defined in RFC 1928 section 4. 26 const ( 27 CmdConnect = 1 28 CmdBind = 2 29 CmdUDPAssociate = 3 30 ) 31 32 // SOCKS errors as defined in RFC 1928 section 6. 33 const ( 34 Succeeded = 0 35 ErrGeneralFailure = 1 36 ErrConnectionNotAllowed = 2 37 ErrNetworkUnreachable = 3 38 ErrHostUnreachable = 4 39 ErrConnectionRefused = 5 40 ErrTTLExpired = 6 41 ErrCommandNotSupported = 7 42 ErrAddressNotSupported = 8 43 ) 44 45 var ( 46 ErrUnsupportedSocksVersion = errors.New("unsupported SOCKS version") 47 ErrUnsupportedAuthenticationMethod = errors.New("unsupported authentication method") 48 ErrUnsupportedCommand = errors.New("unsupported command") 49 ErrUDPAssociateDone = errors.New("UDP ASSOCIATE done") 50 ) 51 52 // replyWithStatus writes a reply to w with the REP field set to status. 53 func replyWithStatus(w io.Writer, b []byte, status byte) error { 54 const replyLen = 3 + IPv4AddrLen 55 reply := b[:replyLen] 56 reply[0] = Version 57 reply[1] = status 58 reply[2] = 0 59 *(*[IPv4AddrLen]byte)(reply[3:]) = IPv4UnspecifiedAddr 60 _, err := w.Write(reply) 61 return err 62 } 63 64 // ClientRequest writes a request to targetAddr and returns the bound address in reply. 65 func ClientRequest(rw io.ReadWriter, command byte, targetAddr conn.Addr) (addr conn.Addr, err error) { 66 b := make([]byte, 3+MaxAddrLen) 67 b[0] = Version 68 b[1] = 1 69 b[2] = MethodNoAuthenticationRequired 70 71 // Write VER NMETHDOS METHODS. 72 _, err = rw.Write(b[:3]) 73 if err != nil { 74 return 75 } 76 77 // Read version selection message. 78 _, err = io.ReadFull(rw, b[:2]) 79 if err != nil { 80 return 81 } 82 83 // Check VER. 84 if b[0] != Version { 85 err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0]) 86 return 87 } 88 89 // Check METHOD. 90 if b[1] != MethodNoAuthenticationRequired { 91 err = fmt.Errorf("%w: %d", ErrUnsupportedAuthenticationMethod, b[1]) 92 return 93 } 94 95 // Write VER, CMD, RSV, SOCKS address. 96 b[1] = command 97 n := WriteAddrFromConnAddr(b[3:], targetAddr) 98 _, err = rw.Write(b[:3+n]) 99 if err != nil { 100 return 101 } 102 103 // Read VER, REP, RSV. 104 _, err = io.ReadFull(rw, b[:3]) 105 if err != nil { 106 return 107 } 108 109 // Check VER. 110 if b[0] != Version { 111 err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0]) 112 return 113 } 114 115 // Check REP. 116 if b[1] != Succeeded { 117 err = fmt.Errorf("SOCKS error: %d", b[1]) 118 return 119 } 120 121 // Read SOCKS address. 122 sa, err := AppendFromReader(b[3:3], rw) 123 if err != nil { 124 return 125 } 126 addr, _, err = ConnAddrFromSlice(sa) 127 return 128 } 129 130 // ClientConnect writes a CONNECT request to targetAddr. 131 func ClientConnect(rw io.ReadWriter, targetAddr conn.Addr) error { 132 _, err := ClientRequest(rw, CmdConnect, targetAddr) 133 return err 134 } 135 136 // ClientUDPAssociate writes a UDP ASSOCIATE request to targetAddr. 137 func ClientUDPAssociate(rw io.ReadWriter, targetAddr conn.Addr) (conn.Addr, error) { 138 return ClientRequest(rw, CmdUDPAssociate, targetAddr) 139 } 140 141 // ServerAccept processes an incoming request from rw. 142 // 143 // enableTCP enables the CONNECT command. 144 // enableUDP enables the UDP ASSOCIATE command. 145 // 146 // When UDP is enabled, rw must be a [*net.TCPConn]. 147 func ServerAccept(rw io.ReadWriter, enableTCP, enableUDP bool) (addr conn.Addr, err error) { 148 b := make([]byte, 3+MaxAddrLen) 149 150 // Read VER, NMETHODS. 151 _, err = io.ReadFull(rw, b[:2]) 152 if err != nil { 153 return 154 } 155 156 // Check VER. 157 if b[0] != Version { 158 err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0]) 159 return 160 } 161 162 // Check NMETHODS. 163 if b[1] == 0 { 164 err = fmt.Errorf("NMETHODS is %d", b[1]) 165 return 166 } 167 168 // Read METHODS. 169 _, err = io.ReadFull(rw, b[:b[1]]) 170 if err != nil { 171 return 172 } 173 174 // Check METHODS. 175 if bytes.IndexByte(b[:b[1]], MethodNoAuthenticationRequired) == -1 { 176 b[0] = Version 177 b[1] = MethodNoAcceptable 178 _, err = rw.Write(b[:2]) 179 if err == nil { 180 err = ErrUnsupportedAuthenticationMethod 181 } 182 return 183 } 184 185 // Write method selection message. 186 // 187 // +-----+--------+ 188 // | VER | METHOD | 189 // +-----+--------+ 190 // | 1 | 1 | 191 // +-----+--------+ 192 b[0] = Version 193 b[1] = MethodNoAuthenticationRequired 194 _, err = rw.Write(b[:2]) 195 if err != nil { 196 return 197 } 198 199 // Read VER, CMD, RSV. 200 _, err = io.ReadFull(rw, b[:3]) 201 if err != nil { 202 return 203 } 204 205 // Check VER. 206 if b[0] != Version { 207 err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0]) 208 return 209 } 210 211 // Read SOCKS address. 212 sa, err := AppendFromReader(b[3:3], rw) 213 if err != nil { 214 return 215 } 216 addr, _, err = ConnAddrFromSlice(sa) 217 if err != nil { 218 return 219 } 220 221 switch { 222 case b[1] == CmdConnect && enableTCP: 223 err = replyWithStatus(rw, b, Succeeded) 224 225 case b[1] == CmdUDPAssociate && enableUDP: 226 // Use the connection's local address as the returned UDP bound address. 227 tc, ok := rw.(*net.TCPConn) 228 if !ok { 229 err = zerocopy.ErrAcceptRequiresTCPConn 230 return 231 } 232 localAddrPort := tc.LocalAddr().(*net.TCPAddr).AddrPort() 233 234 // Construct reply. 235 b[1] = Succeeded 236 reply := AppendAddrFromAddrPort(b[:3], localAddrPort) 237 238 // Write reply. 239 _, err = rw.Write(reply) 240 if err != nil { 241 return 242 } 243 244 // Hold the connection open. 245 _, err = rw.Read(b[:1]) 246 if err == nil || err == io.EOF { 247 err = ErrUDPAssociateDone 248 } 249 250 default: 251 err = replyWithStatus(rw, b, ErrCommandNotSupported) 252 if err == nil { 253 err = fmt.Errorf("%w: %d", ErrUnsupportedCommand, b[1]) 254 } 255 } 256 257 return 258 }