#include "LuprexSockets.h" #include "enginewrapper.hpp" #include "drvutil.hpp" #include "DebugPrint.h" #include "Sockets.h" #include "SocketTypes.h" #include "SocketSubsystem.h" #include "AddressInfoTypes.h" using namespace DebugPrint; #define UI UI_ST THIRD_PARTY_INCLUDES_START #include #include #include #include #include #include #include #include THIRD_PARTY_INCLUDES_END #undef UI #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #define MAX_BIO_BUFFER (128 * 1024) static const char* dummy_cert = "-----BEGIN CERTIFICATE-----\n" "MIIDezCCAmOgAwIBAgIUajKmxrLMr9zBMlphrTJU5qKG8FgwDQYJKoZIhvcNAQEL\n" "BQAwTDELMAkGA1UEBhMCVVMxFTATBgNVBAgMDFBlbm5zeWx2YW5pYTESMBAGA1UE\n" "CgwJbG9jYWxob3N0MRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjIwMzIyMTczMzA4\n" "WhgPMjEyMjAyMjYxNzMzMDhaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5u\n" "c3lsdmFuaWExEjAQBgNVBAoMCWxvY2FsaG9zdDESMBAGA1UEAwwJbG9jYWxob3N0\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5OWIaKqYae4nPxvu5EP3\n" "VilcjApYcMT4+2ypfQoB6PEep5lwguA929rNsTKnhGsEiQAZ0eZPEZN7VhUwf/hz\n" "26jIyTT43ELkt6k97wwSZSXuT65RpSiemwEs6g2mMwzpgP6nv+yam4HjE9AKiHGN\n" "YeTV72Nw1EN70t6IjIf4jsJRXqDJkUx5sSSD6j0WBTOhzozIDgZHTDwiLhatE66m\n" "SNoD8oWC0PscbUgOJkFpbaCAS8RJmpsdgkTFae2rzL9cOFLGw6OgV/BV1J1s0ks8\n" "+veoMMtIO6fese+OZ+DyQbuGaoaltZUXzY6QjD5l34m2mGplelT7BrpcqJTBHwmh\n" "CwIDAQABo1MwUTAdBgNVHQ4EFgQUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wHwYDVR0j\n" "BBgwFoAUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wDwYDVR0TAQH/BAUwAwEB/zANBgkq\n" "hkiG9w0BAQsFAAOCAQEAqYX/ZGv0Qh/xdXppjnqojm8mH0giDW4tvwMqHcW3YRa3\n" "9J2yYot+rHjU5g4n6HEmWDBE0eqLz9n3Y3fkFzT8RWZwBaST965CgsfGofyuA2hC\n" "Ddn4Am3B5tTPmi8WWRZg8amhpGVD/mwkoVFIK0M337b1aZUJYPE+Kc9WetSL2KqB\n" "EhqSQpkAWhVadzP85dq2T9EDjAvhlFTFlDEBx1GDUcc8M0KQ9NEvLT7LgoUcbMiT\n" "PerlSZQTB0crchXTRSERgiwu80r7D6STn/RcPL9Fg5PkA94/d87jGbmV4sxSRsvM\n" "z+DnJGjHrV1J/jHPrnVvVLpigBlGno3C5O/sRw3gcQ==\n" "-----END CERTIFICATE-----\n"; static const char* dummy_key = "-----BEGIN PRIVATE KEY-----\n" "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDk5Yhoqphp7ic/\n" "G+7kQ/dWKVyMClhwxPj7bKl9CgHo8R6nmXCC4D3b2s2xMqeEawSJABnR5k8Rk3tW\n" "FTB/+HPbqMjJNPjcQuS3qT3vDBJlJe5PrlGlKJ6bASzqDaYzDOmA/qe/7JqbgeMT\n" "0AqIcY1h5NXvY3DUQ3vS3oiMh/iOwlFeoMmRTHmxJIPqPRYFM6HOjMgOBkdMPCIu\n" "Fq0TrqZI2gPyhYLQ+xxtSA4mQWltoIBLxEmamx2CRMVp7avMv1w4UsbDo6BX8FXU\n" "nWzSSzz696gwy0g7p96x745n4PJBu4ZqhqW1lRfNjpCMPmXfibaYamV6VPsGulyo\n" "lMEfCaELAgMBAAECggEBAJa1AiFX4U4tva1xqNKmZV1XklWqIhzts7lnDBkF08gZ\n" "qcNT5Z5mIpR09eVropwvEidZ56Yp63l5D0XYYbyAS1gfQ0QnGot7h7fdOKgB3MK4\n" "PLY94gfKPNN17KqWHg2SvNNv1+cn04v78xUCb0zy5tHDp5Acexdm70ohtupARElJ\n" "LSHdS7ebsqZUFXbbM3BpPEsQLi3PrzNs1DrKkZ3rR6eMGrsDqExXx8/foi9aZKsd\n" "BGM2/kcTJ5aY6NhSv5iqO1oK46sbMrjVW/bYNsOyl0eFjwTRahn+Zhp/JMewZYeu\n" "715g6kzbZNwEzBLgrhNPF6E2ycEr/C6z5bE78g5QCkECgYEA8s07UUY25bjYiWWy\n" "W38pT7d/OXBSyKnq16N6MjVahl29r7nezFiDeLhLC0QiwXu/+qyxVZkB95MMGZXS\n" "AsaKFNis3AJ6eR4SYyhpSScYKNvlKIiW37TtR4FDcy7y5LL6tFpiDDIGH3LuyWNo\n" "d76142MBpv5aStnLGYU3pcZj43sCgYEA8VbNM4nqgSCQcbnHYjvsgphEMNSaoVie\n" "xob2uigXdV6Te0ayoUFBnVNKVsRhk+sswuTV4k1pK/On+USVl2tQ16tcaVMjTfSD\n" "HLYTJLmt6s4DcywWj5dfkbDoe5PulGXNZE960qXmOC62Lf0VMRwJ5x4FBRvGTjKC\n" "zvekI2/kO7ECgYEAhBGeclb/BXXGUvY+TgadMf9d9KBkZ0IFu8Xwcd8TnoLe6vbv\n" "ebery75zE228egIWKwREcYsIxuH1cvVLhrb35N73J7UxaTAyUD1rB598RL1XqPSj\n" "HIwNhReK2NxwwnWYaQHA02FiczjRKjooWPojdcwk2fEArDZLg1YzLrj7HIECgYEA\n" "htdx1Y8ESFtyeShMv5UtoxYCW6oeL3H9XH0CE6bc3IYYLvOkULbOO2HTEkGtJ2Fp\n" "5AbJfiS0U4tS2dI5Jp4eUDH9cxexjRfFvd/5ODbKdnver5X9kQMJsbQ/YPSZg66R\n" "oK9Lt7Bbvh5TScSy93psCgba1SzckspkDdGNkwMsaTECgYEAnFWaxormLUpXQRLs\n" "tKzMMHgVnHlsHiqXH432zmT2fpGZHYoWbsGuQjjrHGnSiu3QbDhnzM6y/T2GRs6z\n" "zHteIo/tzIyxg4MvJGJ9qANA7HoiKBdQ7G/I/NLJIyWAjj+e7/hgzKFcf+dpjpDq\n" "HcKc9a4WXhC7yu79e5BnKWltHXY=\n" "-----END PRIVATE KEY-----\n"; 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() { } }; ///////////////////////////////////////////////////////////////// // // The Entire Socket System Implementation // ///////////////////////////////////////////////////////////////// class FLpxSocketsI : public FLpxSockets { public: // Fatal error status. std::string FatalError; // This pointer is NULL except when inside // one of the methods that accepts a LockedWrapper. 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(FLockedWrapper &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(); // Force Close Everything. virtual void ForceCloseEverything(FLockedWrapper& w); // Main update routine. virtual void Update(FLockedWrapper &w) 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) { int icode = ERR_get_error(); if (icode == 0) break; code = icode; } // Fetch and clear errno. int terrno = errno; errno = 0; if (code != 0) { const char* rc = ERR_reason_error_string(code); if (rc != nullptr) { return rc; } else { return strerror_str(ERR_GET_REASON(code)); } } else if (terrno != 0) { return strerror_str(terrno); } else { return ""; } } 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; } static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); if (!hStore) { return "Could not open system cert store."; } PCCERT_CONTEXT pContext = NULL; X509* x509; X509_STORE* store = SSL_CTX_get_cert_store(ctx); while (true) { pContext = CertEnumCertificatesInStore(hStore, pContext); if (pContext == nullptr) break; const unsigned char* encoded_cert = pContext->pbCertEncoded; x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); if (x509) { X509_STORE_add_cert(store, x509); X509_free(x509); } } CertCloseStore(hStore, 0); return ""; } static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) { 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) { result = SSLErrorString(); } X509_free(certificate); BIO_free(bio); return result; } 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. static int BIOSpace(BIO* bio) { int space = (MAX_BIO_BUFFER)-BIO_pending(bio); if (space < 0) space = 0; return space; } // 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. static void BIODiscard(BIO* b, int nbytes, char* chbuf) { while (nbytes > 0) { int nread = nbytes; if (nread > DRV_SHORTSTRING_SIZE) nread = DRV_SHORTSTRING_SIZE; int ndropped = BIO_read(b, chbuf, nread); check(ndropped == nread); nbytes -= ndropped; } } ///////////////////////////////////////////////////////////////// // // Channel Methods // ///////////////////////////////////////////////////////////////// #pragma optimize("", off) FLpxChannel::FLpxChannel(FLpxSocketsI* lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState st) { LSI = lsi; ChannelID = chid; Socket = sock; RecvBIO = BIO_new(BIO_s_mem()); SendBIO = BIO_new(BIO_s_mem()); RecvFail = false; SendFail = false; NBytes = 0; Bytes = nullptr; RetryWriteNBytes = 0; SSLState = SSL_new(ctx); SSL_set_bio(SSLState, RecvBIO, SendBIO); State = st; } void FLpxChannel::Close(std::string_view err) { // Close and release the SSL channel. // This frees the BIO objects as well. if (SSLState != nullptr) { SSL_free(SSLState); SSLState = nullptr; } // Close and release the socket. if (Socket != nullptr) { Socket->Close(); LSI->Subsys->DestroySocket(Socket); Socket = nullptr; } // Notify luprex that the channel has been closed. if (ChannelID > 0) { if (LSI->Luprex->engine) { LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data()); } 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) || RecvFail) { return; } int32 bytesread; bool ok = Socket->Recv((uint8*)LSI->ChBuf, DRV_SHORTSTRING_SIZE, bytesread); if (!ok) { RecvFail = true; return; } if (bytesread > 0) { int nstored = BIO_write(RecvBIO, LSI->ChBuf, bytesread); check(nstored == bytesread); } } void FLpxChannel::TransferSendBIOToSocket() { if ((State == CHAN_INACTIVE) || SendFail) { return; } char* data; int ndata = BIO_get_mem_data(SendBIO, &data); if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; if (ndata == 0) return; int32 bytessent; bool ok = Socket->Send((const uint8*)data, ndata, bytessent); if (!ok) { SendFail = true; return; } if (bytessent > 0) { BIODiscard(SendBIO, bytessent, LSI->ChBuf); } } void FLpxChannel::CloseChannelIfSSLErrorIsSerious(int retval) { int error = SSL_get_error(SSLState, retval); // Should never have write errors, because we're // using a memory BIO with unlimited capacity. check(error != SSL_ERROR_WANT_WRITE); // If we get a read error, make sure it's plausible: // if the recv bio is full, that makes no sense. if (error == SSL_ERROR_WANT_READ) { if (BIOSpace(RecvBIO) == 0) { Close("ssl waiting for data, but there's tons of data"); } return; } // 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; BoundPort = bp; Socket = sock; } FLpxListener::~FLpxListener() { if (Socket != nullptr) { Socket->Close(); LSI->Subsys->DestroySocket(Socket); Socket = nullptr; } } #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()) { FatalError = s; } } FLpxSocketsI::FLpxSocketsI(FLockedWrapper &w) { // We retain this pointer only so long as we have the wrapper lock. Luprex = w.Get(); // 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(); ServerCTX = nullptr; ClientSecureCTX = nullptr; ClientInsecureCTX = nullptr; Subsys = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); if (Subsys == nullptr) { SetError("Cannot obtain the socket subsystem"); } 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(); // We're losing the wrapper lock, so set the pointer to nullptr. Luprex = nullptr; } void FLpxSocketsI::ForceCloseEverything(FLockedWrapper& w) { // We retain this pointer only so long as we have the wrapper lock. Luprex = w.Get(); // Close all channels for (FLpxChannel& chan : Channels) { chan.Close("Force Close Everything"); } // Delete any channels released by the above. RemoveInactiveChannels(); // All channels should be gone now. check(Channels.IsEmpty()); // We're losing the wrapper lock, so set the pointer to nullptr. Luprex = nullptr; } FLpxSocketsI::~FLpxSocketsI() { checkf(Channels.IsEmpty(), TEXT("Must call ForceCloseEverything before destructor")); if (ServerCTX != nullptr) { SSL_CTX_free(ServerCTX); ServerCTX = nullptr; } if (ClientSecureCTX != nullptr) { SSL_CTX_free(ClientSecureCTX); ClientSecureCTX = nullptr; } if (ClientInsecureCTX != nullptr) { SSL_CTX_free(ClientInsecureCTX); ClientInsecureCTX = nullptr; } // TODO: Be more thorough. } void FLpxSocketsI::DPrintTrace() { char* data; int ndata = BIO_get_mem_data(TraceBIO, &data); if (ndata == 0) return; DPrint(data, ndata); BIO_reset(TraceBIO); } bool FLpxSocketsI::ListeningOnPort(int p) { for (const FLpxListener& l : Listeners) { if (l.BoundPort == p) return true; } return false; } void FLpxSocketsI::HandleListenPorts() { uint32_t nports; const uint32_t* ports; Luprex->get_listen_ports(Luprex, &nports, &ports); for (uint32_t i = 0; i < nports; i++) { int port = ports[i]; if (!ListeningOnPort(port)) { std::string err; FSocket* sock = ListenOnPort(Subsys, port, err); if (sock == nullptr) { SetError(err); } else { Listeners.Emplace(this, port, sock); } } } } void FLpxSocketsI::HandleNewOutgoingSockets() { uint32_t nchids; const uint32_t* chids; Luprex->get_new_outgoing(Luprex, &nchids, &chids); for (uint32_t i = 0; i < nchids; i++) { uint32_t chid = chids[i]; std::string err, cert, host, port; const char* target = Luprex->get_target(Luprex, chid); drvutil::split_target(target, cert, host, port); if (cert.empty() || host.empty() || port.empty()) { std::string message = "invalid target: "; message += target; Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); continue; } SSL_CTX* ctx = nullptr; if (cert == "cert") { ctx = ClientSecureCTX; } else if (cert == "nocert") { ctx = ClientInsecureCTX; } else { std::string message = "invalid cert rule: "; message += target; Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); continue; } FSocket *sock = OpenConnection(Subsys, host, port, err); if (sock == nullptr) { Luprex->play_notify_close(Luprex, chid, err.size(), err.c_str()); continue; } Channels.Emplace(this, sock, chid, ctx, CHAN_SSL_CONNECTING); } Luprex->play_clear_new_outgoing(Luprex); } 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(FLockedWrapper &w) { // We retain this pointer only so long as we have the wrapper lock. Luprex = w.Get(); HandleNewOutgoingSockets(); HandleSocketInputOutput(); // We're losing the wrapper lock, so set the pointer to nullptr. Luprex = nullptr; } FLpxSockets* FLpxSockets::Create(FLockedWrapper &w) { return new FLpxSocketsI(w); }