github.com/sagernet/sing@v0.2.6/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 err = socks4.WriteResponse(conn, socks4.Response{ 114 ReplyCode: socks4.ReplyCodeGranted, 115 Destination: M.SocksaddrFromNet(conn.LocalAddr()), 116 }) 117 if err != nil { 118 return err 119 } 120 metadata.Protocol = "socks4" 121 metadata.Destination = request.Destination 122 return handler.NewConnection(auth.ContextWithUser(ctx, request.Username), conn, metadata) 123 default: 124 err = socks4.WriteResponse(conn, socks4.Response{ 125 ReplyCode: socks4.ReplyCodeRejectedOrFailed, 126 Destination: request.Destination, 127 }) 128 if err != nil { 129 return err 130 } 131 return E.New("socks4: unsupported command ", request.Command) 132 } 133 case socks5.Version: 134 authRequest, err := socks5.ReadAuthRequest0(conn) 135 if err != nil { 136 return err 137 } 138 var authMethod byte 139 if authenticator != nil && !common.Contains(authRequest.Methods, socks5.AuthTypeUsernamePassword) { 140 err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{ 141 Method: socks5.AuthTypeNoAcceptedMethods, 142 }) 143 if err != nil { 144 return err 145 } 146 } 147 if authenticator != nil { 148 authMethod = socks5.AuthTypeUsernamePassword 149 } else { 150 authMethod = socks5.AuthTypeNotRequired 151 } 152 err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{ 153 Method: authMethod, 154 }) 155 if err != nil { 156 return err 157 } 158 if authMethod == socks5.AuthTypeUsernamePassword { 159 usernamePasswordAuthRequest, err := socks5.ReadUsernamePasswordAuthRequest(conn) 160 if err != nil { 161 return err 162 } 163 ctx = auth.ContextWithUser(ctx, usernamePasswordAuthRequest.Username) 164 response := socks5.UsernamePasswordAuthResponse{} 165 if authenticator.Verify(usernamePasswordAuthRequest.Username, usernamePasswordAuthRequest.Password) { 166 response.Status = socks5.UsernamePasswordStatusSuccess 167 } else { 168 response.Status = socks5.UsernamePasswordStatusFailure 169 } 170 err = socks5.WriteUsernamePasswordAuthResponse(conn, response) 171 if err != nil { 172 return err 173 } 174 } 175 request, err := socks5.ReadRequest(conn) 176 if err != nil { 177 return err 178 } 179 switch request.Command { 180 case socks5.CommandConnect: 181 err = socks5.WriteResponse(conn, socks5.Response{ 182 ReplyCode: socks5.ReplyCodeSuccess, 183 Bind: M.SocksaddrFromNet(conn.LocalAddr()), 184 }) 185 if err != nil { 186 return err 187 } 188 metadata.Protocol = "socks5" 189 metadata.Destination = request.Destination 190 return handler.NewConnection(ctx, conn, metadata) 191 case socks5.CommandUDPAssociate: 192 var udpConn *net.UDPConn 193 udpConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", M.AddrFromNetAddr(conn.LocalAddr())), net.UDPAddrFromAddrPort(netip.AddrPortFrom(M.AddrFromNetAddr(conn.LocalAddr()), 0))) 194 if err != nil { 195 return err 196 } 197 defer udpConn.Close() 198 err = socks5.WriteResponse(conn, socks5.Response{ 199 ReplyCode: socks5.ReplyCodeSuccess, 200 Bind: M.SocksaddrFromNet(udpConn.LocalAddr()), 201 }) 202 if err != nil { 203 return err 204 } 205 metadata.Protocol = "socks5" 206 metadata.Destination = request.Destination 207 var innerError error 208 done := make(chan struct{}) 209 associatePacketConn := NewAssociatePacketConn(udpConn, request.Destination, conn) 210 go func() { 211 innerError = handler.NewPacketConnection(ctx, associatePacketConn, metadata) 212 close(done) 213 }() 214 err = common.Error(io.Copy(io.Discard, conn)) 215 associatePacketConn.Close() 216 <-done 217 return E.Errors(innerError, err) 218 default: 219 err = socks5.WriteResponse(conn, socks5.Response{ 220 ReplyCode: socks5.ReplyCodeUnsupported, 221 }) 222 if err != nil { 223 return err 224 } 225 return E.New("socks5: unsupported command ", request.Command) 226 } 227 } 228 return os.ErrInvalid 229 }