diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index 64346113..19724980 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -18,7 +18,7 @@ AIntegrationGameModeBase::AIntegrationGameModeBase() //PrimaryActorTick.bTickEvenWhenPaused = true; // Probably wrong //PrimaryActorTick.TickGroup = TG_PrePhysics; // Probably wrong SetActorTickEnabled(true); - SetActorTickInterval(0.05f); + SetActorTickInterval(0.0f); } AIntegrationGameModeBase::~AIntegrationGameModeBase() @@ -40,11 +40,11 @@ uint32 AIntegrationGameModeBase::Run() engineutil::DPrint("Thread waiting a long time..."); continue; } - engineutil::DPrint("Thread triggered."); { FScopeLock lk(&LuprexMutex); Sockets->Update(); Luprex.play_invoke_event_update(&Luprex, EngineSeconds); + Sockets->Update(); } } return 0; @@ -64,15 +64,15 @@ void AIntegrationGameModeBase::ResetToInitialState() } ThreadStopRequested = false; + // Release and close all sockets. + Sockets.Reset(); + // Delete the engine. if (Luprex.release != nullptr) { Luprex.release(&Luprex); } - // Release and close all sockets. - Sockets.Reset(); - // Reset the clocks. EngineSeconds = 0; NextThreadTrigger = 1.0; @@ -115,7 +115,7 @@ void AIntegrationGameModeBase::Tick(float DeltaSeconds) if ((Thread != nullptr) && (EngineSeconds >= NextThreadTrigger)) { ThreadEvent->Trigger(); - NextThreadTrigger += 1.0; + NextThreadTrigger += 0.05; } } @@ -170,7 +170,7 @@ void AIntegrationGameModeBase::BeginPlay() } std::string_view srcpakv = srcpak.view(); char* argv[1]; - argv[0] = const_cast("lpxclient"); + argv[0] = const_cast("lpxserver"); Luprex.play_initialize(&Luprex, 1, argv, srcpakv.size(), srcpakv.data(), ""); if (Luprex.error[0]) { @@ -186,6 +186,8 @@ void AIntegrationGameModeBase::BeginPlay() if (Luprex.engine != nullptr) { Sockets.Reset(FLpxSockets::Create(&Luprex)); + std::string error = Sockets->GetError(); + check(error.empty()); ThreadEvent = FPlatformProcess::GetSynchEventFromPool(false); Thread = FRunnableThread::Create(this, TEXT("Worker Thread")); } diff --git a/Source/Integration/LuprexSockets.cpp b/Source/Integration/LuprexSockets.cpp index b7c662a4..0893c75b 100644 --- a/Source/Integration/LuprexSockets.cpp +++ b/Source/Integration/LuprexSockets.cpp @@ -2,6 +2,7 @@ #include "LuprexSockets.hpp" #include "enginewrapper.hpp" #include "drvutil.hpp" +#include "engineutil.hpp" #include "Sockets.h" #include "SocketTypes.h" #include "SocketSubsystem.h" @@ -19,6 +20,7 @@ THIRD_PARTY_INCLUDES_START #include THIRD_PARTY_INCLUDES_END #undef UI + #define WIN32_LEAN_AND_MEAN #include #include @@ -28,144 +30,6 @@ THIRD_PARTY_INCLUDES_END #define MAX_BIO_BUFFER (128 * 1024) -enum EChanState { - CHAN_INACTIVE, - CHAN_PLAINTEXT, - CHAN_SSL_CONNECTING, - CHAN_SSL_ACCEPTING, - CHAN_SSL_READWRITE, -}; - -class FLpxSocketsI; - -// A port-listening socket. -class FLpxListener -{ -public: - FLpxSocketsI *LSI; - int BoundPort; - FSocket* Socket; - - FLpxListener(FLpxSocketsI *lsi, int bp, FSocket* sock); - ~FLpxListener(); -}; - -// A communication socket. -class FLpxChannel -{ -public: - FLpxSocketsI* LSI; - int ChannelID; - FSocket* Socket; - SSL* SSLState; - BIO* RecvBIO; - BIO* SendBIO; - - // If recent_error is set, that means that a recent IO operation generated - // an error. As a special case, EOF on read is considered an error, we use - // the string "EOF" for this case. - std::string RecentError; - - // OpenSSL has a rule: if you try to SSL_write and it returns - // SSL_ERROR_WANT_READ, then you have to retry the write with the same - // number of bytes. In this event, we record how many bytes we - // attempted to write, which will enable us to retry. - int RetryWriteNBytes; - - // True if the channel needs to be advanced. - bool NeedAdvance; - - EChanState State; - uint32_t NBytes; - const char* Bytes; - - void Close(std::string_view error); - - // Copy data from the socket into the recv bio. - // - // If it detects an error or EOF, sets the RecentError flag. - // - void TransferSocketToRecvBIO(); - - // Copy data from the send bio to the socket. - // - // If it detects an error or EOF, sets the RecentError flag. - // - void TransferSendBIOToSocket(); - - // Check if an SSL error is serious. If so, close the channel. - // - // The 'retval' is the return value of the SSL function that returned an - // error. - // - // All errors are considered serious except for SSL_ERROR_WANT_READ, which - // is not serious because it is transient. However, if you get an - // SSL_ERROR_WANT_READ when there's tons of data available in the read - // buffer, that's inexplicable and therefore serious. - // - void CloseChannelIfSSLErrorIsSerious(int retval); - - FLpxChannel(FLpxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state); - ~FLpxChannel() { Close(""); } -}; - -class FLpxSocketsI : public FLpxSockets -{ -public: - // Fatal error status. - std::string FatalError; - - // We don't own the wrapper, we just have a pointer to it. - // We require a guarantee that it outlives us. - EngineWrapper* Luprex; - - // A general-purpose character buffer. - char ChBuf[DRV_SHORTSTRING_SIZE]; - - TArray Channels; - TArray Listeners; - - // Pointer to the socket subsystem. - ISocketSubsystem* Subsys; - - SSL_CTX* ServerCTX; - SSL_CTX* ClientSecureCTX; - SSL_CTX* ClientInsecureCTX; - - FLpxSocketsI(EngineWrapper* w); - virtual ~FLpxSocketsI() override; - - // Error handling. - void SetError(const std::string& s); - virtual std::string GetError() override { return FatalError; } - bool AnyError() { return !FatalError.empty(); } - - // Return true if we're listening on port P. - bool ListeningOnPort(int p); - - // Open a connection and return a socket. - FSocket* OpenConnection(const std::string& host, const std::string& port, std::string& err); - - // Handle various phases of the operation. - void HandleListenPorts(); - void HandleNewOutgoingSockets(); - void HandleSocketInputOutput(); - - // Main update routine. - virtual void Update() override; -}; - - -static std::string strerror_str(int errnum) { - char buf[256]; - int status = strerror_s(buf, 256, errnum); - if (status != 0) - { - snprintf(buf, 256, "unknown errno %d", errnum); - } - return buf; -} - static const char* dummy_cert = "-----BEGIN CERTIFICATE-----\n" "MIIDezCCAmOgAwIBAgIUajKmxrLMr9zBMlphrTJU5qKG8FgwDQYJKoZIhvcNAQEL\n" @@ -219,7 +83,275 @@ static const char* dummy_key = "HcKc9a4WXhC7yu79e5BnKWltHXY=\n" "-----END PRIVATE KEY-----\n"; -std::string SSLErrorString() { +class FLpxSocketsI; + +///////////////////////////////////////////////////////////////// +// +// A port-listening socket. +// +///////////////////////////////////////////////////////////////// + +class FLpxListener +{ +public: + FLpxSocketsI *LSI; + int BoundPort; + FSocket* Socket; + + FLpxListener(FLpxSocketsI *lsi, int bp, FSocket* sock); + ~FLpxListener(); + + void AcceptConnection(); +}; + +///////////////////////////////////////////////////////////////// +// +// A communication socket. +// +///////////////////////////////////////////////////////////////// + +enum EChanState { + CHAN_INACTIVE, + CHAN_SSL_CONNECTING, + CHAN_SSL_ACCEPTING, + CHAN_SSL_READWRITE, +}; + +class FLpxChannel +{ +public: + FLpxSocketsI* LSI; + EChanState State; + int ChannelID; + FSocket* Socket; + SSL* SSLState; + BIO* RecvBIO; + BIO* SendBIO; + + // True if the socket receive operation has ever failed. + bool RecvFail; + // True if the socket send operation has ever failed. + bool SendFail; + + // Most recent probe of the Luprex output buffer for this channel. + uint32_t NBytes; + const char* Bytes; + + // OpenSSL has a rule: if you try to SSL_write and it returns + // SSL_ERROR_WANT_READ, then you have to retry the write with the same + // number of bytes. In this event, we record how many bytes we + // attempted to write, which will enable us to retry. + int RetryWriteNBytes; + + void Close(std::string_view error); + + // Copy data from the socket into the recv bio. + // If it detects an error or EOF, sets the RecvFail flag. + // Once a RecvFail has occurred, further calls will be a No-OP. + void TransferSocketToRecvBIO(); + + // Copy data from the send bio to the socket. + // If it detects an error or EOF, sets the SendFail flag. + // Once a SendFail has occurred, further calls will be a No-Op. + void TransferSendBIOToSocket(); + + // Advance the channel + // + void AdvanceConnecting(); + void AdvanceAccepting(); + void AdvanceReadWrite(); + void Advance(); + + // Check if an SSL error is serious. If so, close the channel. + // The 'retval' is the return value of the SSL function that returned an + // error. All errors are considered serious except for SSL_ERROR_WANT_READ, + // which is not serious because it is transient. However, if you get an + // SSL_ERROR_WANT_READ when there's tons of data available in the read + // buffer, that's inexplicable and therefore serious. + void CloseChannelIfSSLErrorIsSerious(int retval); + + FLpxChannel(FLpxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state); + FLpxChannel() : FLpxChannel(nullptr, nullptr, 0, nullptr, CHAN_INACTIVE) {} + ~FLpxChannel() { Close(""); } +}; + +///////////////////////////////////////////////////////////////// +// +// The Entire Socket System Implementation +// +///////////////////////////////////////////////////////////////// + +class FLpxSocketsI : public FLpxSockets +{ +public: + // Fatal error status. + std::string FatalError; + + // We don't own the wrapper, we just have a pointer to it. + // We require a guarantee that it outlives us. + EngineWrapper* Luprex; + + // A general-purpose character buffer. + char ChBuf[DRV_SHORTSTRING_SIZE]; + + TArray Channels; + TArray Listeners; + + // Pointer to the socket subsystem. + ISocketSubsystem* Subsys; + + BIO* TraceBIO; + + SSL_CTX* ServerCTX; + SSL_CTX* ClientSecureCTX; + SSL_CTX* ClientInsecureCTX; + + FLpxSocketsI(EngineWrapper* w); + virtual ~FLpxSocketsI() override; + + // Copy the trace to the DPrint output. + void DPrintTrace(); + + // Error handling. + void SetError(const std::string& s); + virtual std::string GetError() override { return FatalError; } + bool AnyError() { return !FatalError.empty(); } + + // Return true if we're listening on port P. + bool ListeningOnPort(int p); + + // Handle various phases of the operation. + void RemoveInactiveChannels(); + void HandleListenPorts(); + void HandleNewOutgoingSockets(); + void HandleSocketInputOutput(); + + // Main update routine. + virtual void Update() override; +}; + +///////////////////////////////////////////////////////////////// +// +// General Support Functions +// +///////////////////////////////////////////////////////////////// + +static std::string strerror_str(int errnum) { + char buf[256]; + int status = strerror_s(buf, 256, errnum); + if (status != 0) + { + snprintf(buf, 256, "unknown errno %d", errnum); + } + return buf; +} + +static FSocket* OpenConnection(ISocketSubsystem *subsys, const std::string& host, const std::string& port, std::string& err) +{ + std::string hostport = host + ":" + port; + FString fshost(host.size(), (const UTF8CHAR*)host.c_str()); + FString fsport(port.size(), (const UTF8CHAR*)port.c_str()); + + FAddressInfoResult air = subsys->GetAddressInfo(*fshost, *fsport, EAddressInfoFlags::Default, NAME_None, ESocketType::SOCKTYPE_Streaming); + + if (air.Results.Num() == 0) { + err = std::string("DNS Lookup failed for: ") + hostport; + return nullptr; + } + + const FAddressInfoResultData& dns = air.Results[0]; + const FInternetAddr& inetaddr = *dns.Address; + + std::string sdescription = host + ":" + port; + FString description(sdescription.c_str()); + + FSocket* Socket = subsys->CreateSocket(NAME_Stream, description, inetaddr.GetProtocolType()); + if (Socket == nullptr) + { + err = std::string("Could not create socket for ") + hostport; + return nullptr; + } + bool connected = Socket->Connect(inetaddr); + if (!connected) + { + subsys->DestroySocket(Socket); + err = std::string("Could not connect to ") + hostport; + return nullptr; + } + Socket->SetNonBlocking(true); + err = ""; + return Socket; +} + +FSocket* ListenOnPort(ISocketSubsystem* subsys, int port, std::string& err) +{ + err = ""; + TSharedPtr addr = subsys->CreateInternetAddr(); + bool ok; + addr->SetIp(TEXT("127.0.0.1"), ok); + if (!ok) + { + err = "Could not parse internet address"; + return nullptr; + } + addr->SetPort(port); + FSocket* sock = subsys->CreateSocket(NAME_Stream, TEXT("Listener"), addr->GetProtocolType()); + if (sock == nullptr) + { + err = "Could not create socket for listening"; + return nullptr; + } + if (!sock->SetReuseAddr(true)) + { + subsys->DestroySocket(sock); + err = "Could not configure socket to reuse address"; + return nullptr; + } + if (!sock->Bind(*addr) || !sock->Listen(20)) + { + subsys->DestroySocket(sock); + err = "Could not bind socket to local port"; + return nullptr; + } + if (!sock->SetLinger(false)) + { + subsys->DestroySocket(sock); + err = "Could not configure socket to not linger"; + return nullptr; + } + if (!sock->SetNonBlocking(true)) + { + subsys->DestroySocket(sock); + err = "Could not configure socket for nonblocking"; + return nullptr; + } + return sock; +} + + +///////////////////////////////////////////////////////////////// +// +// OpenSSL-related Support Functions +// +///////////////////////////////////////////////////////////////// + +static void SSLClearErrors() { + ERR_clear_error(); + errno = 0; +} + +static std::string SSLFullErrorString() { + BIO* b = BIO_new(BIO_s_mem()); + ERR_print_errors(b); + char* data; + int ndata = BIO_get_mem_data(b, &data); + std::string result(' ', ndata); + memcpy(&result[0], data, ndata); + BIO_free(b); + return result; +} + +static std::string SSLErrorString() { // Get the last code. int code = 0; while (true) { @@ -249,11 +381,18 @@ std::string SSLErrorString() { } } -static SSL_CTX* SSLNewContext(int verify) { - SSL_CTX* ctx = SSL_CTX_new(TLS_method()); +static SSL_CTX* SSLNewContext(int verify, const SSL_METHOD *method, BIO *tracebio) { + check(method != nullptr); + SSL_CTX* ctx = SSL_CTX_new(method); SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_verify(ctx, verify, nullptr); + SSL_CTX_set_ecdh_auto(ctx, 1); + //if (tracebio != nullptr) + //{ + // SSL_CTX_set_msg_callback(ctx, SSL_trace); + // SSL_CTX_set_msg_callback_arg(ctx, tracebio); + //} return ctx; } @@ -285,10 +424,14 @@ static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { } static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) { - ERR_clear_error(); + SSLClearErrors(); BIO* bio = BIO_new(BIO_s_mem()); BIO_puts(bio, str); X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (certificate == nullptr) + { + return "Could not parse PEM certificate string"; + } std::string result; if (SSL_CTX_use_certificate(ctx, certificate) <= 0) { @@ -299,22 +442,39 @@ static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) { return result; } - -std::string SSLLoadDummyCert(SSL_CTX* ctx) -{ - std::string err1 = SSLUseCertificateString(ctx, dummy_cert); - std::string err2 = SSLUseCertificateString(ctx, dummy_key); - if (!err1.empty()) return err1; - if (!err1.empty()) return err2; - return ""; +static std::string SSLUsePrivateKeyString(SSL_CTX* ctx, const char* str) { + SSLClearErrors(); + BIO* bio = BIO_new(BIO_s_mem()); + BIO_puts(bio, str); + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (pkey == nullptr) + { + return "Could not parse PEM private key string"; + } + std::string result; + if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) + { + result = SSLErrorString(); + } + EVP_PKEY_free(pkey); + BIO_free(bio); + return result; } +static std::string SSLLoadDummyCert(SSL_CTX* ctx) +{ + std::string err1 = SSLUseCertificateString(ctx, dummy_cert); + std::string err2 = SSLUsePrivateKeyString(ctx, dummy_key); + if (!err1.empty()) return err1; + if (!err2.empty()) return err2; + return ""; +} // Return the amount of 'space left' in a BIO. This is a fiction, // because MEM BIOs technically have unlimited capacity. We're // artificially limiting them to a certain size because there's no // reason to buffer huge amounts of data. -int BIOSpace(BIO* bio) { +static int BIOSpace(BIO* bio) { int space = (MAX_BIO_BUFFER)-BIO_pending(bio); if (space < 0) space = 0; return space; @@ -323,7 +483,7 @@ int BIOSpace(BIO* bio) { // Discard the first nbytes in buffer. // This is a terribly inefficient way to discard data that has // already been processed. There has to be something better. -void BIODiscard(BIO* b, int nbytes, char* chbuf) { +static void BIODiscard(BIO* b, int nbytes, char* chbuf) { while (nbytes > 0) { int nread = nbytes; if (nread > DRV_SHORTSTRING_SIZE) nread = DRV_SHORTSTRING_SIZE; @@ -333,6 +493,14 @@ void BIODiscard(BIO* b, int nbytes, char* chbuf) { } } + +///////////////////////////////////////////////////////////////// +// +// Channel Methods +// +///////////////////////////////////////////////////////////////// + +#pragma optimize("", off) FLpxChannel::FLpxChannel(FLpxSocketsI* lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState st) { LSI = lsi; @@ -340,21 +508,16 @@ FLpxChannel::FLpxChannel(FLpxSocketsI* lsi, FSocket* sock, int chid, SSL_CTX* ct Socket = sock; RecvBIO = BIO_new(BIO_s_mem()); SendBIO = BIO_new(BIO_s_mem()); - RecentError.clear(); + RecvFail = false; + SendFail = false; + NBytes = 0; + Bytes = nullptr; RetryWriteNBytes = 0; - NeedAdvance = true; - if (st == CHAN_PLAINTEXT) { - SSLState = nullptr; - } - else { - SSLState = SSL_new(ctx); - SSL_set_bio(SSLState, RecvBIO, SendBIO); - } + SSLState = SSL_new(ctx); + SSL_set_bio(SSLState, RecvBIO, SendBIO); State = st; - NBytes = 0; - Bytes = 0; } void FLpxChannel::Close(std::string_view err) { @@ -364,12 +527,6 @@ void FLpxChannel::Close(std::string_view err) { SSL_free(SSLState); SSLState = nullptr; } - RecvBIO = nullptr; - SendBIO = nullptr; - - RecentError.clear(); - RetryWriteNBytes = 0; - NeedAdvance = false; // Close and release the socket. if (Socket != nullptr) @@ -382,37 +539,46 @@ void FLpxChannel::Close(std::string_view err) { // Notify luprex that the channel has been closed. if (ChannelID > 0) { - LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data()); + if (LSI->Luprex->engine) + { + LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data()); + } + ChannelID = -1; } - // Close everything else. - State = CHAN_INACTIVE; - ChannelID = -1; + RecvBIO = nullptr; + SendBIO = nullptr; + RecvFail = false; + SendFail = false; NBytes = 0; Bytes = nullptr; + RetryWriteNBytes = 0; + ChannelID = -1; + State = CHAN_INACTIVE; } +#pragma optimize("", off) void FLpxChannel::TransferSocketToRecvBIO() { - if ((State == CHAN_INACTIVE) || (!RecentError.empty())) { + if ((State == CHAN_INACTIVE) || RecvFail) { return; } + int32 bytesread; bool ok = Socket->Recv((uint8*)LSI->ChBuf, DRV_SHORTSTRING_SIZE, bytesread); if (!ok) { - RecentError = "EOF"; + RecvFail = true; return; } if (bytesread > 0) { int nstored = BIO_write(RecvBIO, LSI->ChBuf, bytesread); check(nstored == bytesread); - NeedAdvance = true; } } void FLpxChannel::TransferSendBIOToSocket() { - if ((State == CHAN_INACTIVE) || (!RecentError.empty())) { + if ((State == CHAN_INACTIVE) || SendFail) { return; } @@ -420,25 +586,21 @@ void FLpxChannel::TransferSendBIOToSocket() { int ndata = BIO_get_mem_data(SendBIO, &data); if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; - // It is an error to call this function when there is nothing in the send BIO. - check(ndata > 0); + if (ndata == 0) return; int32 bytessent; bool ok = Socket->Send((const uint8*)data, ndata, bytessent); if (!ok) { - RecentError = "Send failure on socket"; + SendFail = true; return; } if (bytessent > 0) { BIODiscard(SendBIO, bytessent, LSI->ChBuf); - NeedAdvance = true; } } -// Close the channel if there's a serious OpenSSL error. -// void FLpxChannel::CloseChannelIfSSLErrorIsSerious(int retval) { int error = SSL_get_error(SSLState, retval); @@ -455,13 +617,151 @@ void FLpxChannel::CloseChannelIfSSLErrorIsSerious(int retval) { return; } - // Any other error is an actual error. Close - // the channel. - std::string errstr = SSLErrorString(); + // Any other error is an actual error. Close the channel. + std::string errstr = SSLFullErrorString(); if (errstr == "") errstr = "unknown error"; Close(errstr); } +void FLpxChannel::AdvanceConnecting() +{ + int retval = SSL_connect(SSLState); + if (retval == 1) + { + State = CHAN_SSL_READWRITE; + } + else + { + CloseChannelIfSSLErrorIsSerious(retval); + } +} + +#pragma optimize("", off) +void FLpxChannel::AdvanceAccepting() +{ + int retval = SSL_accept(SSLState); + if (retval == 1) + { + State = CHAN_SSL_READWRITE; + } + else + { + CloseChannelIfSSLErrorIsSerious( retval); + } + LSI->DPrintTrace(); +} + +void FLpxChannel::AdvanceReadWrite() +{ + // Read as much as we can, which of course will be limited + // by the fact that the recv_bio contains finite data. + while (true) + { + int read_result = SSL_read(SSLState, LSI->ChBuf, DRV_SHORTSTRING_SIZE); + if (read_result > 0) + { + LSI->Luprex->play_recv_incoming(LSI->Luprex, ChannelID, read_result, LSI->ChBuf); + } + else + { + CloseChannelIfSSLErrorIsSerious(read_result); + break; + } + } + + // The read process could have generated an error which could + // have closed the channel. If so, don't try writing. + if (State == CHAN_INACTIVE) + { + return; + } + + // Try to write data. + while (NBytes) + { + uint32_t wbytes; + if (RetryWriteNBytes > 0) + { + wbytes = RetryWriteNBytes; + check(wbytes < NBytes); + } + else + { + wbytes = NBytes; + if (wbytes > DRV_SHORTSTRING_SIZE) wbytes = DRV_SHORTSTRING_SIZE; + } + if (wbytes == 0) break; + int write_result = SSL_write(SSLState, Bytes, wbytes); + if (write_result > 0) + { + LSI->Luprex->play_sent_outgoing(LSI->Luprex, ChannelID, write_result); + RetryWriteNBytes = 0; + NBytes -= write_result; + Bytes += write_result; + } + else + { + CloseChannelIfSSLErrorIsSerious(write_result); + RetryWriteNBytes = wbytes; + break; + } + } +} + +#pragma optimize("", off) +void FLpxChannel::Advance() +{ + check(State != CHAN_INACTIVE); + + // Pump from the socket into the RECV BIO. + TransferSocketToRecvBIO(); + + // Get a pointer to the Luprex outgoing bytes. + LSI->Luprex->get_outgoing(LSI->Luprex, ChannelID, &NBytes, &Bytes); + + // If all outgoing buffers are empty, and Luprex has released + // the channel, close the channel. + if (NBytes == 0) { + if (LSI->Luprex->get_channel_released(LSI->Luprex, ChannelID)) { + if (BIO_pending(SendBIO) == 0) { + Close(""); + return; + } + } + } + + SSLClearErrors(); + switch (State) { + case CHAN_SSL_CONNECTING: + AdvanceConnecting(); + break; + case CHAN_SSL_ACCEPTING: + AdvanceAccepting(); + break; + case CHAN_SSL_READWRITE: + AdvanceReadWrite(); + break; + default: + checkf(false, L"EChanState is invalid"); + break; + } + + // Pump from the Send BIO to the socket. + TransferSendBIOToSocket(); + + if (RecvFail || SendFail) + { + Close("Connection aborted"); + } +} + + +///////////////////////////////////////////////////////////////// +// +// Listener Methods +// +///////////////////////////////////////////////////////////////// + FLpxListener::FLpxListener(FLpxSocketsI *lsi, int bp, FSocket *sock) { LSI = lsi; @@ -479,6 +779,26 @@ FLpxListener::~FLpxListener() } } +#pragma optimize("", off) +void FLpxListener::AcceptConnection() +{ + FSocket* csocket = Socket->Accept(TEXT("Incoming Connection")); + if (csocket == nullptr) + { + return; + } + csocket->SetNonBlocking(true); + int ChannelID = LSI->Luprex->play_notify_accept(LSI->Luprex, BoundPort); + LSI->Channels.Emplace(LSI, csocket, ChannelID, LSI->ServerCTX, CHAN_SSL_ACCEPTING); +} + +///////////////////////////////////////////////////////////////// +// +// The Socket System Implementation +// +///////////////////////////////////////////////////////////////// + + void FLpxSocketsI::SetError(const std::string& s) { if (FatalError.empty()) { @@ -486,8 +806,16 @@ void FLpxSocketsI::SetError(const std::string& s) } } + FLpxSocketsI::FLpxSocketsI(EngineWrapper *w) { + // This function is nonreentrant. It's not clear whether + // this is needed - it may be initialized elsewhere in unreal. + // It is also not clear that it's safe to do this in the + // blueprint thread (this constructor runs in the blueprint + // thread). + SSL_library_init(); + Luprex = w; ServerCTX = nullptr; ClientSecureCTX = nullptr; @@ -499,13 +827,23 @@ FLpxSocketsI::FLpxSocketsI(EngineWrapper *w) SetError("Cannot obtain the socket subsystem"); } - ServerCTX = SSLNewContext(SSL_VERIFY_NONE); - ClientSecureCTX = SSLNewContext(SSL_VERIFY_PEER); - ClientInsecureCTX = SSLNewContext(SSL_VERIFY_NONE); + TraceBIO = BIO_new(BIO_s_mem()); + + ServerCTX = SSLNewContext(SSL_VERIFY_NONE, TLS_server_method(), TraceBIO); + ClientSecureCTX = SSLNewContext(SSL_VERIFY_PEER, TLS_client_method(), TraceBIO); + ClientInsecureCTX = SSLNewContext(SSL_VERIFY_NONE, TLS_client_method(), TraceBIO); SetError(SSLLoadCertificateAuthorities(ClientSecureCTX)); SetError(SSLLoadDummyCert(ServerCTX)); + STACK_OF(SSL_CIPHER)* ciphers = SSL_CTX_get_ciphers(ServerCTX); + std::string names = ""; + for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); i++) + { + const SSL_CIPHER* sslc = sk_SSL_CIPHER_value(ciphers, i); + const char* name = SSL_CIPHER_get_name(sslc); + names = names + " " + name; + } HandleListenPorts(); } @@ -530,6 +868,15 @@ FLpxSocketsI::~FLpxSocketsI() //} } +void FLpxSocketsI::DPrintTrace() +{ + char* data; + int ndata = BIO_get_mem_data(TraceBIO, &data); + if (ndata == 0) return; + engineutil::DPrintHook(data, ndata); + BIO_reset(TraceBIO); +} + bool FLpxSocketsI::ListeningOnPort(int p) { for (const FLpxListener& l : Listeners) @@ -547,48 +894,20 @@ void FLpxSocketsI::HandleListenPorts() int port = ports[i]; if (!ListeningOnPort(port)) { - // TODO: Open a listening socket. - // Push the new port and socket onto Listeners. + std::string err; + FSocket* sock = ListenOnPort(Subsys, port, err); + if (sock == nullptr) + { + SetError(err); + } + else + { + Listeners.Emplace(this, port, sock); + } } } } -FSocket* FLpxSocketsI::OpenConnection(const std::string& host, const std::string& port, std::string& err) -{ - std::string hostport = host + ":" + port; - FString fshost(host.size(), (const UTF8CHAR *)host.c_str()); - FString fsport(port.size(), (const UTF8CHAR *)port.c_str()); - - FAddressInfoResult air = Subsys->GetAddressInfo(*fshost, *fsport, EAddressInfoFlags::Default, NAME_None, ESocketType::SOCKTYPE_Streaming); - - if (air.Results.Num() == 0) { - err = std::string("DNS Lookup failed for: ") + hostport; - return nullptr; - } - - const FAddressInfoResultData& dns = air.Results[0]; - const FInternetAddr& inetaddr = *dns.Address; - - std::string sdescription = host + ":" + port; - FString description(sdescription.c_str()); - - FSocket* Socket = Subsys->CreateSocket(NAME_Stream, description, inetaddr.GetProtocolType()); - if (Socket == nullptr) - { - err = std::string("Could not create socket for ") + hostport; - return nullptr; - } - bool connected = Socket->Connect(inetaddr); - if (!connected) - { - Subsys->DestroySocket(Socket); - err = std::string("Could not connect to ") + hostport; - return nullptr; - } - Socket->SetNonBlocking(true); - err = ""; - return Socket; -} void FLpxSocketsI::HandleNewOutgoingSockets() { @@ -618,7 +937,7 @@ void FLpxSocketsI::HandleNewOutgoingSockets() Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); continue; } - FSocket *sock = OpenConnection(host, port, err); + FSocket *sock = OpenConnection(Subsys, host, port, err); if (sock == nullptr) { Luprex->play_notify_close(Luprex, chid, err.size(), err.c_str()); continue; @@ -629,10 +948,44 @@ void FLpxSocketsI::HandleNewOutgoingSockets() } +void FLpxSocketsI::RemoveInactiveChannels() +{ + int i = 0; + int n = Channels.Num(); + while (true) + { + while ((n > 0) && (Channels[n - 1].State == CHAN_INACTIVE)) { + n -= 1; + } + while ((i < n) && (Channels[i].State != CHAN_INACTIVE)) { + i += 1; + } + if (i >= n) break; + check(i < (n - 1)); + std::swap(Channels[i], Channels[n - 1]); + i += 1; + n -= 1; + } + Channels.SetNum(n); +} + + void FLpxSocketsI::HandleSocketInputOutput() { + for (FLpxListener& listener : Listeners) + { + listener.AcceptConnection(); + } + // Peek output buffers and determine channel release flags. + for (FLpxChannel& chan : Channels) + { + chan.Advance(); + } + + // Delete any channels released by the above. + RemoveInactiveChannels(); } void FLpxSocketsI::Update()