Implemented a lot of LuprexSockets

This commit is contained in:
2023-06-26 17:28:07 -04:00
parent d5d4e0a650
commit a86eda5434
3 changed files with 559 additions and 19 deletions

View File

@@ -1,5 +1,11 @@
#include "LuprexSockets.hpp"
#include "enginewrapper.hpp"
#include "drvutil.hpp"
#include "Sockets.h"
#include "SocketTypes.h"
#include "SocketSubsystem.h"
#include "AddressInfoTypes.h"
#define UI UI_ST
THIRD_PARTY_INCLUDES_START
@@ -13,7 +19,14 @@ THIRD_PARTY_INCLUDES_START
#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)
enum EChanState {
CHAN_INACTIVE,
@@ -23,9 +36,25 @@ enum EChanState {
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;
@@ -49,25 +78,55 @@ class FLpxChannel
EChanState State;
uint32_t NBytes;
const char* Bytes;
};
// A port-listening socket.
class FLpxListener
{
int BoundPort;
FSocket* Socket;
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;
TArray<FLpxChannel> Channels;
TArray<FLpxListener> Listeners;
// A general-purpose character buffer.
char ChBuf[DRV_SHORTSTRING_SIZE];
TArray<FLpxChannel> Channels;
TArray<FLpxListener> Listeners;
// Pointer to the socket subsystem.
ISocketSubsystem* Subsys;
SSL_CTX* ServerCTX;
SSL_CTX* ClientSecureCTX;
@@ -76,23 +135,510 @@ public:
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"
"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";
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) {
SSL_CTX* ctx = SSL_CTX_new(TLS_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);
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) {
ERR_clear_error();
BIO* bio = BIO_new(BIO_s_mem());
BIO_puts(bio, str);
X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
std::string result;
if (SSL_CTX_use_certificate(ctx, certificate) <= 0)
{
result = SSLErrorString();
}
X509_free(certificate);
BIO_free(bio);
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 "";
}
// 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) {
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.
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;
}
}
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());
RecentError.clear();
RetryWriteNBytes = 0;
NeedAdvance = true;
if (st == CHAN_PLAINTEXT) {
SSLState = nullptr;
}
else {
SSLState = SSL_new(ctx);
SSL_set_bio(SSLState, RecvBIO, SendBIO);
}
State = st;
NBytes = 0;
Bytes = 0;
}
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;
}
RecvBIO = nullptr;
SendBIO = nullptr;
RecentError.clear();
RetryWriteNBytes = 0;
NeedAdvance = false;
// 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)
{
LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data());
}
// Close everything else.
State = CHAN_INACTIVE;
ChannelID = -1;
NBytes = 0;
Bytes = nullptr;
}
void FLpxChannel::TransferSocketToRecvBIO() {
if ((State == CHAN_INACTIVE) || (!RecentError.empty())) {
return;
}
int32 bytesread;
bool ok = Socket->Recv((uint8*)LSI->ChBuf, DRV_SHORTSTRING_SIZE, bytesread);
if (!ok)
{
RecentError = "EOF";
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())) {
return;
}
char* data;
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);
int32 bytessent;
bool ok = Socket->Send((const uint8*)data, ndata, bytessent);
if (!ok)
{
RecentError = "Send failure on socket";
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);
// 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 = SSLErrorString();
if (errstr == "") errstr = "unknown error";
Close(errstr);
}
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;
}
}
void FLpxSocketsI::SetError(const std::string& s)
{
if (FatalError.empty()) {
FatalError = s;
}
}
FLpxSocketsI::FLpxSocketsI(EngineWrapper *w)
{
Luprex = w;
ServerCTX = nullptr;
ClientSecureCTX = nullptr;
ClientInsecureCTX = nullptr;
Subsys = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
if (Subsys == nullptr)
{
SetError("Cannot obtain the socket subsystem");
}
ServerCTX = SSLNewContext(SSL_VERIFY_NONE);
ClientSecureCTX = SSLNewContext(SSL_VERIFY_PEER);
ClientInsecureCTX = SSLNewContext(SSL_VERIFY_NONE);
SetError(SSLLoadCertificateAuthorities(ClientSecureCTX));
SetError(SSLLoadDummyCert(ServerCTX));
HandleListenPorts();
}
FLpxSocketsI::~FLpxSocketsI()
{
if (ServerCTX != nullptr)
{
SSL_CTX_free(ServerCTX);
}
if (ClientSecureCTX != nullptr)
{
SSL_CTX_free(ClientSecureCTX);
}
if (ClientInsecureCTX != nullptr)
{
SSL_CTX_free(ClientInsecureCTX);
}
// Cleanup
//for (ChanInfo& chan : chans_) {
// close_channel(chan, "");
//}
}
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))
{
// TODO: Open a listening socket.
// Push the new port and socket onto Listeners.
}
}
}
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()
{
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(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::HandleSocketInputOutput()
{
}
void FLpxSocketsI::Update()
{
HandleNewOutgoingSockets();
HandleSocketInputOutput();
}
FLpxSockets* FLpxSockets::Create(EngineWrapper* w)