github.com/Laplace-Game-Development/Laplace-Entangled-Environment@v0.0.3/internal/route/listener.go (about) 1 // Route Represents the routing and listening to connections. 2 // This module takes care of the communication links to users 3 // and clients. They will forward commands to data driven modules 4 // in `/data`. The Listener module makes sure to listen to 5 // connections over TCP and HTTP (maybe Websocket too later.) 6 // We also have the parser which uses the policy directives to 7 // break apart the user payloads into understandable commands. 8 // Secure takes care of any encryption necessary over the wire. 9 package route 10 11 import ( 12 "context" 13 "crypto/tls" 14 "encoding/json" 15 "errors" 16 "io/ioutil" 17 "log" 18 "net" 19 "net/http" 20 "time" 21 22 "github.com/Laplace-Game-Development/Laplace-Entangled-Environment/internal/policy" 23 "github.com/Laplace-Game-Development/Laplace-Entangled-Environment/internal/util" 24 ) 25 26 //// Configurables 27 28 // Time for shutdown. Quitting Mid Handle is really bad. This should be longer than any duration 29 const ShutdownDuration time.Duration = 10 * time.Second 30 31 /* Client TCP Settings */ 32 33 // Time spent waiting for incomming connections before checking for control signals/shutoff/etc 34 const IoDeadline time.Duration = 5 * time.Second 35 36 // TCP IP Mask to listen for connections on 37 const ListeningTCPIpAddress string = "127.0.0.1" 38 39 // TCP Port number to listen for connections on 40 const ListeningTCPPortNumber string = ":26005" 41 42 // SSL Port Number to Listen for connections on 43 const ListeningSSLPortNumber string = ":26006" 44 45 // Size of Packet Header for TCP commands 46 // Byte 1 :: Metadata/Parsing info 47 // Byte 2 :: More Significant byte for Command 48 // Byte 3 :: Lesser Significant byte for Command 49 const CommandBytes = 3 50 51 // Limit of Goroutines used for listening on TCP. 52 // 5 is a good number for testing, but a better number would be much higher. 53 const NumberOfTCPThreads = 10 54 55 // Limit of Goroutines used for listening on SSL. 56 // 5 is a good number for testing, but a better number would be much higher. 57 const NumberOfSSLThreads = 5 58 59 // Constant byte string of JSON representing a data malformed error 60 // May be moved to Policy 61 var MalformedDataMsg []byte = []byte("{\"success\": false, \"error\": \"Data Was Malformed!\"}") 62 63 // Constant integer length of a JSON byte string representing a data malformed error 64 // May be moved to Policy 65 var MalformedDataMsgLen int = len([]byte("{\"success\": false, \"error\": \"Data Was Malformed!\"}")) 66 67 // Constant byte string of JSON representing a Secured Connection 68 // May be moved to Policy 69 var SecureConnectionMsg []byte = []byte("{\"success\": true, \"message\": \"SECURED!\"}") 70 71 // Constant integer length of a JSON byte string representing a Secured Connection 72 // May be moved to Policy 73 var SecureConnectionMsgLen int = len([]byte("{\"success\": true, \"message\": \"SECURED!\"}")) 74 75 // HTTP Listening Host IP 76 const HttpHost string = "127.0.0.1" 77 78 // HTTP Host Listening Port Number 79 const HttpPort string = ":80" 80 81 //// Global Variables | Singletons 82 83 // Thread Pool for Connection Listening. This stores the threads and context 84 // for all listening for connections. 85 // The goroutines themselves will spawn other goroutines so we use more than 86 // 4 "threads" for the full application. 87 // 88 // 1 for TCP 89 // 1 for SSL 90 // 1 For HTTP 91 // 1 For WebSocket? 92 var listenerThreadPool util.ThreadPool = util.NewThreadPool(4) 93 94 // ServerTask Startup Function for Conneciton Listening. Takes care of initialization. 95 func StartListener() (func(), error) { 96 err := listenerThreadPool.SubmitFuncUnsafe(startTCPListening) 97 if err != nil { 98 return nil, err 99 } 100 101 err = listenerThreadPool.SubmitFuncUnsafe(startSSLListening) 102 if err != nil { 103 return nil, err 104 } 105 106 err = listenerThreadPool.SubmitFuncUnsafe(startHTTPListening) 107 if err != nil { 108 return nil, err 109 } 110 111 return cleanUpListener, nil 112 } 113 114 // CleanUp Function returned by Startup function. Stops all listeners by "Finish"ing the 115 // threadpool. This means that within the given "ShutdownDuration" the listeners should 116 // be closed. 117 func cleanUpListener() { 118 log.Println("Cleaning Up Listener Logic") 119 listenerThreadPool.Finish(time.Now().Add(ShutdownDuration)) 120 } 121 122 /////////////////////////////////////////////////////////////////////////////////////////////////// 123 //// 124 //// General Listening Functions 125 //// 126 /////////////////////////////////////////////////////////////////////////////////////////////////// 127 128 // takes the required parameters and ensures any listener that is ready to send their command 129 // to data has the required components. This will parse and perform the commmand requested. It 130 // will then return the calculated byte slice response. 131 // 132 // requestHeader :: Common Fields for all requests including authentication and endpoint selection 133 // bodyFactories :: Arguments for the commands in the form of first order functions 134 // isSecured :: Whether the request came over an encrypted connection (i.e. SSL/SSH/HTTPS) 135 // 136 // returns []byte :: byte slice response for user 137 // error :: non-nil when an invalid command is sent or an error occurred when processing 138 // typically means request was rejected. 139 func calculateResponse(requestHeader policy.RequestHeader, bodyFactories policy.RequestBodyFactories, isSecured bool) ([]byte, error) { 140 // parse.go 141 return switchOnCommand(requestHeader, bodyFactories, isSecured) 142 } 143 144 /////////////////////////////////////////////////////////////////////////////////////////////////// 145 //// 146 //// HTTP Listening Functions 147 //// 148 /////////////////////////////////////////////////////////////////////////////////////////////////// 149 150 // A Constant map of what commands should be made with a POST HTTP Request 151 // rather than a GET Request. This is for semantic and API design reasons. 152 // 153 // This should never change during runtime! 154 var postOnlyCmdMap map[policy.ClientCmd]bool = map[policy.ClientCmd]bool{ 155 policy.CmdError: false, 156 policy.CmdEmpty: false, 157 policy.CmdRegister: true, 158 policy.CmdLogin: true, 159 policy.CmdAction: true, 160 policy.CmdObserve: true, 161 policy.CmdGetUser: true, 162 policy.CmdGameCreate: true, 163 policy.CmdGameJoin: true, 164 policy.CmdGameLeave: true, 165 policy.CmdGameDelete: true, 166 } 167 168 // Attaches Path Handlers for HTTP Web Server. Uses Paths to 169 // communicate command used. i.e. /user/ -> CmdGetUser 170 // 171 // ctx :: Owning Context for HTTP Listener 172 func startHTTPListening(ctx context.Context) { 173 http.HandleFunc("/empty/", getHttpHandler(policy.CmdEmpty)) 174 http.HandleFunc("/error/", getHttpHandler(policy.CmdError)) 175 http.HandleFunc("/register/", getHttpHandler(policy.CmdRegister)) 176 http.HandleFunc("/login/", getHttpHandler(policy.CmdLogin)) 177 http.HandleFunc("/action/", getHttpHandler(policy.CmdAction)) 178 http.HandleFunc("/observe/", getHttpHandler(policy.CmdObserve)) 179 http.HandleFunc("/user/", getHttpHandler(policy.CmdGetUser)) 180 http.HandleFunc("/game/create/", getHttpHandler(policy.CmdGameCreate)) 181 http.HandleFunc("/game/join/", getHttpHandler(policy.CmdGameJoin)) 182 http.HandleFunc("/game/leave/", getHttpHandler(policy.CmdGameLeave)) 183 http.HandleFunc("/game/delete/", getHttpHandler(policy.CmdGameDelete)) 184 185 http.HandleFunc("*", http.NotFound) 186 187 serverConfig := http.Server{ 188 Addr: HttpHost + HttpPort, 189 TLSConfig: &tlsConfig, // Found in secure.go 190 BaseContext: func(l net.Listener) context.Context { return ctx }, 191 } 192 193 // Error will always be Non-Nil Here! 194 err := serverConfig.ListenAndServe() 195 if err != nil { 196 log.Printf("HTTP Error: %v\n", err) 197 } 198 } 199 200 // Creates a First Order Function for the given command. 201 // Useful for when adding handlers in the initialization function 202 // for HTTP. 203 func getHttpHandler(command policy.ClientCmd) func(writer http.ResponseWriter, req *http.Request) { 204 return func(writer http.ResponseWriter, req *http.Request) { 205 handleHttp(command, writer, req) 206 } 207 } 208 209 // Handles a given HTTP request with the given Client Command (endpoint), and data 210 // 211 // clientCmd :: Selected Endpoint/Command 212 // writer :: writer to be written to with response data for user 213 // req :: Given HTTP Request with associated non-command data (args and authentication) 214 func handleHttp(clientCmd policy.ClientCmd, writer http.ResponseWriter, req *http.Request) { 215 if checkPost(clientCmd, writer, req) { 216 return 217 } 218 219 body, err := ioutil.ReadAll(req.Body) 220 if err != nil { 221 log.Printf("Error Reading Body: %v\n", err) 222 } 223 224 requestAttachment := parseHeaderInfo(req, &body) 225 226 requestHeader := policy.RequestHeader{ 227 Command: clientCmd, 228 UserID: requestAttachment.UserID, 229 Sig: requestAttachment.Sig, 230 } 231 232 bodyFactories := policy.RequestBodyFactories{ 233 ParseFactory: func(ptr interface{}) error { 234 return json.Unmarshal(body, ptr) 235 }, 236 SigVerify: func(userID string, userSig string) error { 237 return SigVerification(userID, userSig, &body) 238 }, 239 } 240 241 calculateResponse(requestHeader, bodyFactories, req.TLS != nil) 242 } 243 244 // Returns whether the given HTTP request is a POST request if needed. 245 // see "postOnlyCmdMap" 246 // 247 // clientCmd :: Selected Endpoint/Command 248 // writer :: writer to be written to with response data for user 249 // req :: Given HTTP Request with associated non-command data (args and authentication) 250 // returns -> bool 251 // true | continue processing the request 252 // false | ignore the request. An error was already given to the user 253 func checkPost(clientCmd policy.ClientCmd, writer http.ResponseWriter, req *http.Request) bool { 254 needsPost, exists := postOnlyCmdMap[clientCmd] 255 if exists && needsPost && req.Method != http.MethodPost { 256 output := policy.UnSuccessfulResponse("Post Required!") 257 writeable, err := output.Digest(output.Data) 258 if err != nil { 259 log.Fatal("handleHttp: Could Not Write Utility Response to User!") 260 } 261 262 writer.Write(writeable) 263 return true 264 } 265 266 return false 267 } 268 269 // Creates the Request Attachment (Authentication Portion) of the request 270 // 271 // req :: HTTP Request with associated data 272 // body :: slice of data representing the request body. We use a parameter 273 // rather than grabbing it all to optimize the process. It needs to be 274 // used by other functions, so passing it in is better than creating 275 // a variable just to be garbage collected 276 // 277 // returns -> policy.RequestAttachment :: the authentication components found 278 // or empty components if none are found in header/cookies/or body 279 func parseHeaderInfo(req *http.Request, body *[]byte) policy.RequestAttachment { 280 requestAttachment := policy.RequestAttachment{} 281 userIDFound := false 282 sigFound := false 283 284 possibleUserIDs := make([]string, 3) 285 possibleSigs := make([]string, 3) 286 287 // Check Header 288 possibleUserIDs[0] = req.Header.Get("laplace-user-id") 289 possibleSigs[0] = req.Header.Get("laplace-signature") 290 291 // Check Cookies 292 userIDCookie, cookieErr := req.Cookie("laplaceUserId") 293 if cookieErr != nil { 294 log.Println("UserID Cookie Could Not Be Parsed") 295 } else { 296 possibleUserIDs[1] = userIDCookie.Value 297 } 298 299 sigCookie, cookieErr := req.Cookie("laplaceSig") 300 if cookieErr == nil { 301 log.Println("Signature Cookie Could Not Be Parsed") 302 } else { 303 possibleSigs[1] = sigCookie.Value 304 } 305 306 // Check Body 307 if req.Body != nil { 308 userSigObj := policy.RequestAttachment{} 309 310 err := json.Unmarshal(*body, &userSigObj) 311 if err == nil { 312 possibleUserIDs[2] = userSigObj.UserID 313 possibleSigs[2] = userSigObj.Sig 314 } else { 315 log.Println("Illformatted JSON sent to HTTP Header") 316 } 317 } 318 319 for i := 0; !userIDFound && !sigFound && i < 3; i++ { 320 if !userIDFound && len(possibleUserIDs[i]) > 0 { 321 requestAttachment.UserID = possibleUserIDs[i] 322 userIDFound = true 323 } 324 325 if !sigFound && len(possibleSigs[i]) > 0 { 326 requestAttachment.Sig = possibleSigs[i] 327 sigFound = true 328 } 329 } 330 331 return requestAttachment 332 } 333 334 /////////////////////////////////////////////////////////////////////////////////////////////////// 335 //// 336 //// TCP Listening Functions 337 //// 338 /////////////////////////////////////////////////////////////////////////////////////////////////// 339 340 // Wrapper Structure with boolean fields for a TCP Connection. 341 // used to easily differentiate between secure and insecure 342 // connections. It also helps in deciding if the TCP connection 343 // needs to parse more requests (HTTP requests close connections 344 // after one requests, but TCP connections do not.) 345 type TCPClientConn struct { 346 conn net.Conn 347 isSecured bool 348 isReadNeeded bool 349 } 350 351 // First byte of a TCP request. This is a struct of booleans 352 // about how the request is structured over TCP. 353 type TCPRequestPrefix struct { 354 IsBase64Enc bool // First Most Sig Bit 355 IsJSON bool // Second Most Sig Bit 356 } 357 358 // Creates TCP Connection Listener(s) with a designated threadpool and addressing. 359 // It submits goroutine each time it finds a connection. If no goroutine is available 360 // in the threadpool the listener blocks until one is found. 361 // 362 // ctx :: Owning Context 363 func startTCPListening(ctx context.Context) { 364 log.Println("TCP Listening on " + ListeningTCPIpAddress + ListeningTCPPortNumber + "!") 365 ln, err := net.Listen("tcp", ListeningTCPIpAddress+ListeningTCPPortNumber) 366 if err != nil { 367 log.Fatal(err) 368 } 369 370 pool := util.NewThreadPoolWithContext(NumberOfTCPThreads, ctx) 371 372 for { 373 select { 374 case <-ctx.Done(): 375 return 376 default: 377 } 378 379 // SYN + ACK 380 conn, err := ln.Accept() 381 if err != nil { 382 log.Println("Error Occurred In TCP Handshake!") 383 log.Println(err) 384 continue 385 } 386 pool.SubmitFuncBlock(func(ctx context.Context) { 387 handleTCPConnection(ctx, TCPClientConn{conn: conn, isSecured: false, isReadNeeded: false}) 388 }) 389 } 390 } 391 392 // Creates SSL Connection Listener(s) with a designated threadpool and addressing. 393 // See "startTCPListening" 394 // 395 // ctx :: Owning Context 396 func startSSLListening(ctx context.Context) { 397 log.Println("SSL Listening on " + ListeningTCPIpAddress + ListeningSSLPortNumber + "!") 398 ln, err := tls.Listen("tcp", ListeningTCPIpAddress+ListeningSSLPortNumber, &tlsConfig) 399 if err != nil { 400 log.Fatal(err) 401 } 402 403 pool := util.NewThreadPoolWithContext(NumberOfSSLThreads, ctx) 404 405 for { 406 select { 407 case <-ctx.Done(): 408 return 409 default: 410 } 411 412 // SYN + ACK 413 conn, err := ln.Accept() 414 if err != nil { 415 log.Println("Error Occurred In SSL Handshake!") 416 log.Println(err) 417 continue 418 } 419 pool.SubmitFuncBlock(func(ctx context.Context) { 420 handleTCPConnection(ctx, TCPClientConn{conn: conn, isSecured: true, isReadNeeded: false}) 421 }) 422 } 423 } 424 425 // TCP goroutine function, using the prepackaged information to serve the 426 // connected client. It parses the data it receives to compile a response. 427 // Function will loop until the connection is closed. 428 // 429 // ctx :: Owning Context 430 // clientConn :: Metadata and reference to TCP Connection 431 func handleTCPConnection(ctx context.Context, clientConn TCPClientConn) { 432 log.Println("New Connection!") 433 defer clientConn.conn.Close() 434 defer log.Println("Connection Closed!") 435 436 // Read Bytes 437 // Bytes need to be instantiated otherwise golang will not read to them 438 dataIn := make([]byte, 2048) 439 440 // In the off chance we start and shutdown straight after without handling 441 select { 442 case <-ctx.Done(): 443 return 444 default: 445 } 446 447 keepAlive := true 448 449 for keepAlive { 450 keepAlive = false 451 452 // If We need to shutdown 453 select { 454 case <-ctx.Done(): 455 return 456 default: 457 } 458 459 keepAlive = readAndRespondTCP(clientConn, &dataIn) && computeTCPKeepAlive(clientConn) 460 461 if keepAlive { 462 util.Clear(&dataIn) 463 } 464 } 465 } 466 467 // Read and Gather Byte Response for a TCP Client Connection 468 // 469 // clientConn :: Metadata and reference to TCP Connection 470 // dataIn :: byte slice data read in for 471 // Command, Args, Authentication, etc. 472 // 473 // returns -> bool 474 // true | command was successful 475 // false | command was unsuccessful 476 func readAndRespondTCP(clientConn TCPClientConn, dataIn *[]byte) bool { 477 // Set Timeout 478 clientConn.conn.SetReadDeadline(time.Now().Add(IoDeadline)) 479 480 n, err := clientConn.conn.Read(*dataIn) 481 if err != nil { 482 log.Printf("Error Reading TCP Data! Bytes Received: %d! Bytes: %s | Err: %s", n, dataIn, err) 483 return false 484 } 485 486 prefix, err := parseTCPPrefix(n, dataIn) 487 if err != nil { 488 log.Printf("Error Parsing TCP Request! Err: %s\n", err) 489 return false 490 } 491 492 header, bodyFactory, err := generateRequestFromSocket(n, dataIn, prefix) 493 if err != nil { 494 log.Printf("Error Generating Request Command Payloads! Err: %s\n", err) 495 clientConn.conn.SetWriteDeadline(time.Now().Add(IoDeadline)) 496 err = writeTCPResponse(clientConn, &MalformedDataMsg, MalformedDataMsgLen) 497 if err != nil { 498 log.Printf("Error Writing TCP Response For Malformed Data! Err: %s\n", err) 499 } 500 501 return false 502 } 503 504 response, err := calculateResponse(header, bodyFactory, clientConn.isSecured) 505 506 // Tokenize and Encrypt Response Here 507 clientConn.conn.SetWriteDeadline(time.Now().Add(IoDeadline)) 508 err = writeTCPResponse(clientConn, &response, len(response)) 509 if err != nil { 510 log.Printf("Error Writing TCP Response! Err: %s\n", err) 511 } 512 513 return true 514 } 515 516 // Gather TCP Prefix. 517 // Prefix is the first Byte of a TCP Request. 518 // It instructs us how the data is structured. 519 // 520 // length :: number of bytes in data 521 // data :: payload/data for request i.e. Command, Auth, and Args 522 // 523 // returns -> TCPRequestPrefix :: Boolean struct for Structuring metadata 524 func parseTCPPrefix(length int, data *[]byte) (TCPRequestPrefix, error) { 525 if length < 1 { 526 return TCPRequestPrefix{}, errors.New("Packet Has No Prefix") 527 } 528 529 firstByte := (*data)[0] 530 531 prefix := TCPRequestPrefix{ 532 IsBase64Enc: (firstByte & 0b1000_0000) != 0, 533 IsJSON: (firstByte & 0b0100_0000) != 0, 534 } 535 536 return prefix, nil 537 } 538 539 // Using the structuring metadata 540 // and the rest of the payload data, the function generates 541 // a request to the server and returns the information 542 // needed to "calculateResponse" 543 // 544 // length :: number of bytes in data 545 // data :: payload/data for request i.e. Command, Auth, and Args 546 // prefix :: Structuring Metadata 547 // 548 // returns { 549 // RequestHeader :: header data used for all request (Command and Authentication) 550 // RequestBodyFactories :: Transform functions for getting request arguments 551 // error :: If parsing goes wrong and the request is illformed an error is returned 552 // } 553 func generateRequestFromSocket(length int, data *[]byte, prefix TCPRequestPrefix) (policy.RequestHeader, policy.RequestBodyFactories, error) { 554 header := policy.RequestHeader{} 555 factories := policy.RequestBodyFactories{} 556 557 if length < 3 { 558 return header, factories, errors.New("No Command In Request") 559 } 560 561 // Get Command 562 cmd, err := ParseCommand((*data)[1], (*data)[2]) 563 if err != nil { 564 return header, factories, err 565 } 566 567 header.Command = cmd 568 569 // Add Attachment to Header 570 // Also Snip Off Trailing Characters 571 bodyAttachmentAndPayload := (*data)[3:length] 572 attachment, bodyStart, err := parseRequestAttachment(prefix.IsJSON, &bodyAttachmentAndPayload) 573 if err != nil { 574 return header, factories, err 575 } 576 header.Sig = attachment.Sig 577 header.UserID = attachment.UserID 578 579 bodyPayload := bodyAttachmentAndPayload[bodyStart:] 580 factories.ParseFactory = func(ptr interface{}) error { 581 return parseBody(ptr, prefix, &bodyPayload) 582 } 583 584 factories.SigVerify = func(userID string, userSig string) error { 585 return SigVerification(userID, userSig, &bodyPayload) 586 } 587 588 return header, factories, nil 589 } 590 591 // After successful Read->Response should we continue communications? 592 // 593 // clientConn :: Metadata and reference to TCP Connection 594 // 595 // returns -> true | keep the connection alive OR 596 // false | close the connection 597 func computeTCPKeepAlive(clientConn TCPClientConn) bool { 598 // Add Logic for Connection Overhead 599 return clientConn.isReadNeeded 600 } 601 602 // Write byte slice to client 603 // 604 // clientConn :: Metadata and reference to TCP Connection 605 // response :: byte slice of what needs to be sent to client 606 // length :: number of bytes in byte slice 607 // 608 // returns -> error if an error occurs and nil otherwise. 609 func writeTCPResponse(clientConn TCPClientConn, response *[]byte, length int) error { 610 numSent := 0 611 612 for numSent < length { 613 n, err := clientConn.conn.Write((*response)[numSent:]) 614 if err != nil { 615 return err 616 } 617 618 numSent += n 619 } 620 621 clientConn.conn.Write([]byte{4}) 622 623 return nil 624 }