github.com/sagernet/sing@v0.4.0-beta.19.0.20240518125136-f67a0988a636/protocol/socks/handshake.go (about) 1 package socks 2 3 import ( 4 "context" 5 "io" 6 "net" 7 "net/netip" 8 "os" 9 10 "github.com/sagernet/sing/common" 11 "github.com/sagernet/sing/common/auth" 12 E "github.com/sagernet/sing/common/exceptions" 13 M "github.com/sagernet/sing/common/metadata" 14 N "github.com/sagernet/sing/common/network" 15 "github.com/sagernet/sing/common/rw" 16 "github.com/sagernet/sing/protocol/socks/socks4" 17 "github.com/sagernet/sing/protocol/socks/socks5" 18 ) 19 20 type Handler interface { 21 N.TCPConnectionHandler 22 N.UDPConnectionHandler 23 } 24 25 func ClientHandshake4(conn io.ReadWriter, command byte, destination M.Socksaddr, username string) (socks4.Response, error) { 26 err := socks4.WriteRequest(conn, socks4.Request{ 27 Command: command, 28 Destination: destination, 29 Username: username, 30 }) 31 if err != nil { 32 return socks4.Response{}, err 33 } 34 response, err := socks4.ReadResponse(conn) 35 if err != nil { 36 return socks4.Response{}, err 37 } 38 if response.ReplyCode != socks4.ReplyCodeGranted { 39 err = E.New("socks4: request rejected, code= ", response.ReplyCode) 40 } 41 return response, err 42 } 43 44 func ClientHandshake5(conn io.ReadWriter, command byte, destination M.Socksaddr, username string, password string) (socks5.Response, error) { 45 var method byte 46 if username == "" { 47 method = socks5.AuthTypeNotRequired 48 } else { 49 method = socks5.AuthTypeUsernamePassword 50 } 51 err := socks5.WriteAuthRequest(conn, socks5.AuthRequest{ 52 Methods: []byte{method}, 53 }) 54 if err != nil { 55 return socks5.Response{}, err 56 } 57 authResponse, err := socks5.ReadAuthResponse(conn) 58 if err != nil { 59 return socks5.Response{}, err 60 } 61 if authResponse.Method == socks5.AuthTypeUsernamePassword { 62 err = socks5.WriteUsernamePasswordAuthRequest(conn, socks5.UsernamePasswordAuthRequest{ 63 Username: username, 64 Password: password, 65 }) 66 if err != nil { 67 return socks5.Response{}, err 68 } 69 usernamePasswordResponse, err := socks5.ReadUsernamePasswordAuthResponse(conn) 70 if err != nil { 71 return socks5.Response{}, err 72 } 73 if usernamePasswordResponse.Status != socks5.UsernamePasswordStatusSuccess { 74 return socks5.Response{}, E.New("socks5: incorrect user name or password") 75 } 76 } else if authResponse.Method != socks5.AuthTypeNotRequired { 77 return socks5.Response{}, E.New("socks5: unsupported auth method: ", authResponse.Method) 78 } 79 err = socks5.WriteRequest(conn, socks5.Request{ 80 Command: command, 81 Destination: destination, 82 }) 83 if err != nil { 84 return socks5.Response{}, err 85 } 86 response, err := socks5.ReadResponse(conn) 87 if err != nil { 88 return socks5.Response{}, err 89 } 90 if response.ReplyCode != socks5.ReplyCodeSuccess { 91 err = E.New("socks5: request rejected, code=", response.ReplyCode) 92 } 93 return response, err 94 } 95 96 func HandleConnection(ctx context.Context, conn net.Conn, authenticator *auth.Authenticator, handler Handler, metadata M.Metadata) error { 97 version, err := rw.ReadByte(conn) 98 if err != nil { 99 return err 100 } 101 return HandleConnection0(ctx, conn, version, authenticator, handler, metadata) 102 } 103 104 func HandleConnection0(ctx context.Context, conn net.Conn, version byte, authenticator *auth.Authenticator, handler Handler, metadata M.Metadata) error { 105 switch version { 106 case socks4.Version: 107 request, err := socks4.ReadRequest0(conn) 108 if err != nil { 109 return err 110 } 111 switch request.Command { 112 case socks4.CommandConnect: 113 if authenticator != nil && !authenticator.Verify(request.Username, "") { 114 err = socks4.WriteResponse(conn, socks4.Response{ 115 ReplyCode: socks4.ReplyCodeRejectedOrFailed, 116 Destination: request.Destination, 117 }) 118 if err != nil { 119 return err 120 } 121 return E.New("socks4: authentication failed, username=", request.Username) 122 } 123 err = socks4.WriteResponse(conn, socks4.Response{ 124 ReplyCode: socks4.ReplyCodeGranted, 125 Destination: M.SocksaddrFromNet(conn.LocalAddr()), 126 }) 127 if err != nil { 128 return err 129 } 130 metadata.Protocol = "socks4" 131 metadata.Destination = request.Destination 132 return handler.NewConnection(auth.ContextWithUser(ctx, request.Username), conn, metadata) 133 default: 134 err = socks4.WriteResponse(conn, socks4.Response{ 135 ReplyCode: socks4.ReplyCodeRejectedOrFailed, 136 Destination: request.Destination, 137 }) 138 if err != nil { 139 return err 140 } 141 return E.New("socks4: unsupported command ", request.Command) 142 } 143 case socks5.Version: 144 authRequest, err := socks5.ReadAuthRequest0(conn) 145 if err != nil { 146 return err 147 } 148 var authMethod byte 149 if authenticator != nil && !common.Contains(authRequest.Methods, socks5.AuthTypeUsernamePassword) { 150 err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{ 151 Method: socks5.AuthTypeNoAcceptedMethods, 152 }) 153 if err != nil { 154 return err 155 } 156 } 157 if authenticator != nil { 158 authMethod = socks5.AuthTypeUsernamePassword 159 } else { 160 authMethod = socks5.AuthTypeNotRequired 161 } 162 err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{ 163 Method: authMethod, 164 }) 165 if err != nil { 166 return err 167 } 168 if authMethod == socks5.AuthTypeUsernamePassword { 169 usernamePasswordAuthRequest, err := socks5.ReadUsernamePasswordAuthRequest(conn) 170 if err != nil { 171 return err 172 } 173 ctx = auth.ContextWithUser(ctx, usernamePasswordAuthRequest.Username) 174 response := socks5.UsernamePasswordAuthResponse{} 175 if authenticator.Verify(usernamePasswordAuthRequest.Username, usernamePasswordAuthRequest.Password) { 176 response.Status = socks5.UsernamePasswordStatusSuccess 177 } else { 178 response.Status = socks5.UsernamePasswordStatusFailure 179 } 180 err = socks5.WriteUsernamePasswordAuthResponse(conn, response) 181 if err != nil { 182 return err 183 } 184 if response.Status != socks5.UsernamePasswordStatusSuccess { 185 return E.New("socks5: authentication failed, username=", usernamePasswordAuthRequest.Username, ", password=", usernamePasswordAuthRequest.Password) 186 } 187 } 188 request, err := socks5.ReadRequest(conn) 189 if err != nil { 190 return err 191 } 192 switch request.Command { 193 case socks5.CommandConnect: 194 err = socks5.WriteResponse(conn, socks5.Response{ 195 ReplyCode: socks5.ReplyCodeSuccess, 196 Bind: M.SocksaddrFromNet(conn.LocalAddr()), 197 }) 198 if err != nil { 199 return err 200 } 201 metadata.Protocol = "socks5" 202 metadata.Destination = request.Destination 203 return handler.NewConnection(ctx, conn, metadata) 204 case socks5.CommandUDPAssociate: 205 var udpConn *net.UDPConn 206 udpConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), net.UDPAddrFromAddrPort(netip.AddrPortFrom(M.AddrFromNet(conn.LocalAddr()), 0))) 207 if err != nil { 208 return err 209 } 210 defer udpConn.Close() 211 err = socks5.WriteResponse(conn, socks5.Response{ 212 ReplyCode: socks5.ReplyCodeSuccess, 213 Bind: M.SocksaddrFromNet(udpConn.LocalAddr()), 214 }) 215 if err != nil { 216 return err 217 } 218 metadata.Protocol = "socks5" 219 metadata.Destination = request.Destination 220 var innerError error 221 done := make(chan struct{}) 222 associatePacketConn := NewAssociatePacketConn(udpConn, request.Destination, conn) 223 go func() { 224 innerError = handler.NewPacketConnection(ctx, associatePacketConn, metadata) 225 close(done) 226 }() 227 err = common.Error(io.Copy(io.Discard, conn)) 228 associatePacketConn.Close() 229 <-done 230 return E.Errors(innerError, err) 231 default: 232 err = socks5.WriteResponse(conn, socks5.Response{ 233 ReplyCode: socks5.ReplyCodeUnsupported, 234 }) 235 if err != nil { 236 return err 237 } 238 return E.New("socks5: unsupported command ", request.Command) 239 } 240 } 241 return os.ErrInvalid 242 }