1039 lines
28 KiB
C++
1039 lines
28 KiB
C++
|
|
#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 <openssl/ssl.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/conf.h>
|
|
THIRD_PARTY_INCLUDES_END
|
|
#undef UI
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <wincrypt.h>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#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<FLpxChannel> Channels;
|
|
TArray<FLpxListener> 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<FInternetAddr> 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);
|
|
}
|