13 #include <openssl/evp.h>
14 #include <openssl/rsa.h>
15 #include <openssl/x509.h>
16 #include <openssl/x509v3.h>
17 #include <openssl/pem.h>
86 std::vector<NetworkChannel*> stopChannels;
87 if (
mapMutex.enter(2000,
"NetworkManager::~NetworkManager")) {
92 std::map<uint32, NetworkChannel*>::iterator it, itEnd;
94 if (it->second != NULL)
95 stopChannels.push_back(it->second);
100 for (
size_t i = 0; i < stopChannels.size(); i++) {
101 stopChannels[i]->shutdown();
102 delete(stopChannels[i]);
117 bool createdConnection =
false;
121 channelID = channel->
cid;
126 if (channel == NULL) {
129 channel->
cid = channelID;
132 createdConnection =
true;
136 if (!channel->
startListener(channelID, port, encryption, protocol, isAsync, protocolTimeout, isDefaultProtocol)) {
137 if (createdConnection) {
161NetworkChannel*
NetworkManager::createTCPConnection(
const char* addr, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync,
bool autoreconnect, uint32 channelID,
NetworkReceiver* recv, uint64& conid, uint64& location, uint32 timeoutMS) {
162 bool createdConnection =
false;
166 if (channel == NULL) {
169 channel->
cid = channelID;
171 createdConnection =
true;
175 if ( (conid = channel->
createTCPConnection(addr, port, encryption, protocol, isAsync, autoreconnect, location, timeoutMS)) == 0) {
176 if (createdConnection) {
194 bool createdConnection =
false;
198 if (channel == NULL) {
201 if (!con->
connect(location, timeoutMS, NULL)) {
207 channel->
cid = channelID;
208 if ( (conid = channel->
startConnection(con, protocol, isAsync, autoreconnect)) == 0) {
213 createdConnection =
true;
217 if ( (conid = channel->
createTCPConnection(location, encryption, protocol, isAsync, autoreconnect, timeoutMS)) == 0) {
218 if (createdConnection) {
236NetworkChannel*
NetworkManager::createTCPConnection(
const uint32* addresses, uint16 addressCount, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync,
bool autoreconnect, uint32 channelID,
NetworkReceiver* recv, uint64& conid, uint64& location, uint32 timeoutMS) {
237 bool createdConnection =
false;
241 if (channel == NULL) {
244 channel->
cid = channelID;
246 createdConnection =
true;
250 if ( (conid = channel->
createTCPConnection(addresses, addressCount, port, encryption, protocol, isAsync, autoreconnect, location, timeoutMS)) == 0) {
251 if (createdConnection) {
269 bool createdConnection =
false;
273 if (channel == NULL) {
276 channel->
cid = channelID;
278 createdConnection =
true;
283 if (createdConnection) {
300 bool createdConnection =
false;
304 if (channel == NULL) {
307 channel->
cid = channelID;
309 createdConnection =
true;
314 if (createdConnection) {
331NetworkChannel*
NetworkManager::addTCPConnection(
const char* addr, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync, uint32 channelID,
NetworkReceiver* recv, uint64& conid, uint64& location, uint32 timeoutMS,
const char* greetingData, uint32 greetingSize) {
332 bool createdConnection =
false;
336 if (channel == NULL) {
339 channel->
cid = channelID;
341 createdConnection =
true;
345 if ((conid = channel->
addTCPConnection(addr, port, encryption, protocol, isAsync, location, timeoutMS, greetingData, greetingSize)) == 0) {
346 if (createdConnection) {
364 bool createdConnection =
false;
368 if (channel == NULL) {
371 channel->
cid = channelID;
373 createdConnection =
true;
377 if ((conid = channel->
addTCPConnection(location, encryption, protocol, isAsync, timeoutMS, greetingData, greetingSize)) == 0) {
378 if (createdConnection) {
398 bool createdConnection =
false;
402 if (channel == NULL) {
405 channel->
cid = channelID;
407 createdConnection =
true;
411 if ( (conid = channel->
createUDPConnection(port, protocol, isAsync, autoreconnect)) == 0) {
412 if (createdConnection) {
429 if (!
mapMutex.enter(2000,
"NetworkManager::removeConnection"))
456 if (!
mapMutex.enter(2000,
"NetworkManager::addConnection"))
484 if (!
mapMutex.enter(2000,
"NetworkManager::registerChannel"))
491 if (!
mapMutex.enter(2000,
"NetworkManager::unregisterChannel"))
498 if (!
mapMutex.enter(2000,
"NetworkManager::registerConnection"))
505 if (!
mapMutex.enter(2000,
"NetworkManager::registerTCPListener"))
512 if (!
mapMutex.enter(2000,
"NetworkManager::unregisterTCPListener"))
519 if (!
mapMutex.enter(2000,
"NetworkManager::registerUDPListener"))
529 if (!
mapMutex.enter(2000,
"NetworkManager::allocateChannelID"))
543 if (!
mapMutex.enter(2000,
"NetworkManager::findChannelByConnection"))
554 if (!
mapMutex.enter(2000,
"NetworkManager::findChannelByID"))
556 std::map<uint32, NetworkChannel*>::iterator it =
channels.find(channelID);
567 if (!
mapMutex.enter(2000,
"NetworkManager::getTCPConnectionByPort"))
569 std::map<uint16, NetworkChannel*>::iterator it =
listeners.find(port);
576 if (!
mapMutex.enter(2000,
"NetworkManager::getUDPConnectionByPort"))
578 std::map<uint16, NetworkChannel*>::iterator it =
udpListeners.find(port);
608 int8 encryption =
NOENC;
609 if (protocolString ==
"http") {}
610 else if (protocolString ==
"https")
616 if (!hostString.size())
628 if (!uriString.size())
632 if (content && contentSize)
645 uint64 conid, location;
665 std::map<std::string, std::string>& headerEntries,
const char* content,
const char* contentType, uint32 contentSize,
666 bool keepAlive, uint64 ifModifiedSince) {
671 uint64 conid, location;
678 if (!host.length() || !protocol.length())
681 int8 encryption =
NOENC;
682 if (
stricmp(protocol.c_str(),
"https") == 0) {
718 std::map<std::string, std::string>& headerEntries, std::map<std::string, HTTPPostEntry*>& bodyEntries,
719 bool keepAlive, uint64 ifModifiedSince) {
724 uint64 conid, location;
731 if (!host.length() || !protocol.length())
734 int8 encryption =
NOENC;
735 if (
stricmp(protocol.c_str(),
"https") == 0) {
746 if (bodyEntries.size() > 1) {
749 else if (bodyEntries.size() == 1) {
822 std::map<uint16, NetworkThread*>::iterator it, itEnd;
828 std::vector<NetworkThread*> stopListeners;
829 std::vector<NetworkThread*> stopConnections;
837 if (!
channelMutex.enter(10000,
"NetworkChannel::shutdown")) {
840 LogPrint(0,
LOG_NETWORK, 1,
"NetworkChannel::shutdown could not acquire channelMutex within 10s; aborting shutdown to avoid double-delete...");
844 if ( (thread = it->second) != NULL) {
847 stopListeners.push_back(thread);
852 std::map<uint64, NetworkThread*>::iterator it2, it2End;
854 if ( (thread = it2->second) != NULL) {
858 stopConnections.push_back(thread);
866 for (
size_t i = 0; i < stopListeners.size(); i++) {
867 thread = stopListeners[i];
871 for (
int waited = 0; thread->
isRunning && waited < 3000; waited += 5)
877 LogPrint(0,
LOG_NETWORK, 1,
"NetworkChannel::shutdown FORCE-terminating listener thread %u after 3s cooperative wait - this should never happen, investigate", thread->
threadID);
889 for (
size_t i = 0; i < stopConnections.size(); i++) {
890 thread = stopConnections[i];
899 for (
int waited = 0; thread->
isRunning && waited < 5000; waited += 5)
904 LogPrint(0,
LOG_NETWORK, 1,
"NetworkChannel::shutdown FORCE-terminating connection thread %u after 5s cooperative wait - this should never happen, investigate", thread->
threadID);
973 bool created =
false;
974 if (thread == NULL) {
977 conid =
manager->addConnection(
this);
983 if (
manager->sslCertPath.size())
993 if (isDefaultProtocol)
997 if ( isDefaultProtocol && (protocolTimeout == 0) )
1017 if (isDefaultProtocol)
1021 if ( isDefaultProtocol && (protocolTimeout == 0) )
1033 std::map<uint16, NetworkThread*>::iterator lisIt =
listeners.find(port);
1035 if (thread == NULL) {
1047 for (
int waited = 0; thread->
isRunning && waited < 3000; waited += 5)
1050 LogPrint(0,
LOG_NETWORK, 1,
"NetworkChannel::stopListener FORCE-terminating listener thread %u after 3s cooperative wait - this should never happen, investigate", thread->
threadID);
1056 uint32 workerTMID = thread->
threadID;
1073 if (encryption ==
NOENC) {
1075 if (!tcpCon->
connect(addr, port, location, timeoutMS, NULL)) {
1081 else if (encryption ==
SSLENC) {
1084 if (!sslCon->
init()) {
1088 if (!sslCon->
connect(addr, port, location, timeoutMS, NULL)) {
1094 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
1099 if (encryption ==
NOENC) {
1101 if (!tcpCon->
connect(location, timeoutMS, NULL)) {
1107 else if (encryption ==
SSLENC) {
1110 if (!sslCon->
connect(location, timeoutMS, NULL)) {
1116 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
1119uint64
NetworkChannel::createTCPConnection(
const uint32* addresses, uint16 addressCount, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync,
bool autoreconnect, uint64& location, uint32 timeoutMS) {
1121 if (encryption ==
NOENC) {
1123 if (!tcpCon->
connect(addresses, addressCount, port, location, timeoutMS, NULL)) {
1129 else if (encryption ==
SSLENC) {
1132 if (!sslCon->
connect(addresses, addressCount, port, location, timeoutMS, NULL)) {
1138 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
1141uint64
NetworkChannel::addTCPConnection(
const char* addr, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync, uint64& location, uint32 timeoutMS,
const char* greetingData, uint32 greetingSize) {
1143 if (encryption ==
NOENC) {
1145 if (greetingData && greetingSize)
1147 if (!tcpCon->
delayedConnect(addr, port, location, timeoutMS, NULL)) {
1153 else if (encryption ==
SSLENC) {
1156 if (!sslCon->
init()) {
1160 if (greetingData && greetingSize)
1162 if (!sslCon->
delayedConnect(addr, port, location, timeoutMS, NULL)) {
1173 if (encryption ==
NOENC) {
1175 if (greetingData && greetingSize)
1183 else if (encryption ==
SSLENC) {
1186 if (!sslCon->
init()) {
1190 if (greetingData && greetingSize)
1207 int8 encryption =
NOENC;
1208 if (protocolString ==
"http") {}
1209 else if (protocolString ==
"https")
1215 if (!hostString.size())
1220 if (encryption ==
SSLENC)
1227 if (!uriString.size())
1230 return createWebsocketConnection(uriString.c_str(), hostString.c_str(), port, encryption, protocolName, origin, timeoutMS);
1237 if (encryption ==
NOENC) {
1239 if (!tcpCon->
connect(addr, port, location, timeoutMS, NULL)) {
1245 else if (encryption ==
SSLENC) {
1248 if (!sslCon->
connect(addr, port, location, timeoutMS, NULL)) {
1286 uint64 conid =
startConnection(con, protocol, isAsync, autoreconnect);
1292 if (thread == NULL) {
1303 std::map<uint16, NetworkThread*>::iterator udpIt =
udpListeners.find(port);
1309 uint64 conid = thread->
id;
1322 if (!thread || !thread->
con)
1329 if (!thread || !thread->
con)
1335 if (!
channelMutex.enter(2000,
"NetworkChannel::endConnection")) {
1341 std::map<uint64, NetworkThread*>::iterator conIt =
connectionThreads.find(conid);
1343 if (thread == NULL) {
1357 bool forced =
false;
1366 uint32 timeleft = 5000;
1369 if ( (timeleft -= 5) <= 0)
1375 LogPrint(0,
LOG_NETWORK, 1,
"NetworkChannel::endConnection FORCE-terminating connection thread %u after 5s cooperative wait - this should never happen, investigate", thread->
threadID);
1384 manager->removeConnection(conid);
1385 uint32 workerTMID = thread->
threadID;
1534 if ((thread == NULL) || (thread->
con == NULL))
1542 if ((thread == NULL) || (thread->
con == NULL))
1550 if ((thread == NULL) || (thread->
con == NULL))
1558 if ((thread == NULL) || (thread->
con == NULL))
1580 thread = it->second;
1583 if ((thread == NULL) || (thread->
con == NULL))
1590 if ((thread == NULL) || (thread->
con == NULL))
1600 if ((thread == NULL) || (thread->
con == NULL))
1612 if ((thread == NULL) || (thread->
con == NULL))
1619 if ((thread == NULL) || (thread->
con == NULL))
1626 if ((thread == NULL) || (thread->
con == NULL))
1719 uint64 conid =
manager->addConnection(
this);
1754 uint64 conid =
manager->addConnection(
this);
1801 if ((thread == NULL) || (thread->
listener == NULL))
1825 if ((thread == NULL) || (thread->
con == NULL))
1831 uint32 maxSize = 1024;
1832 char* buffer =
new char[maxSize];
1842 delete(thread->
con);
1877 delete(thread->
con);
1883 if (protocol == 0) {
1885 LogPrint(0,
LOG_NETWORK,2,
"No valid protocol detected for incoming network connection, disconnecting...\n\n");
1887 delete(thread->
con);
1929 delete(thread->
con);
1942 if ((thread == NULL) || (thread->
con == NULL))
1946 bool disconnected =
false;
1948 bool upgradedToWebsocket =
false;
1958 if (upgradedToWebsocket) {
1968 uint64 endConid = thread->
id;
1990 upgradedToWebsocket =
true;
2002 if (!disconnected) {
2004 disconnected =
true;
2018 disconnected =
false;
2026 uint64 endConid = thread->
id;
2038 uint64 endConid = thread->
id;
2056 if ((thread == NULL) || (thread->
con == NULL))
2060 bool disconnected =
false;
2062 bool upgradedToWebsocket =
false;
2063 std::string wsOrigin;
2071 if (upgradedToWebsocket) {
2079 delete(wsDataReply);
2101 const char* version = req->
getHeaderEntry(
"Sec-WebSocket-Version");
2102 if (key && version) {
2116 upgradedToWebsocket =
true;
2122 LogPrint(0,
LOG_NETWORK, 1,
"Unable to upgrade HTTP Server %llu to Websocket, key and/or version not provided", thread->
id);
2143 uint64 endConid = thread->
id;
2155 uint64 endConid = thread->
id;
2177 if ((thread == NULL) || (thread->
con == NULL))
2181 bool disconnected =
false;
2182 bool wasConnected =
false;
2189 wasConnected =
true;
2208 if (!disconnected && wasConnected) {
2210 disconnected =
true;
2244 disconnected =
false;
2256 uint64 endConid = thread->
id;
2268 uint64 endConid = thread->
id;
2278 if ((thread == NULL) || (thread->
con == NULL))
2282 bool disconnected =
false;
2312 uint64 endConid = thread->
id;
2323 uint64 endConid = thread->
id;
2335 const uint16 PORT = 38101;
2336 const uint32 CONNECTIONS = 3;
2344 unittest::fail(
"NetworkManager delayed-connect test: could not start listening on port %u", PORT);
2351 msgConnect->
setString(
"URI",
"ExecutorConnect");
2353 std::vector<uint64> conIDs;
2355 uint64 location = 0;
2359 channel = manager->
addTCPConnection(
"localhost", PORT,
NOENC,
PROTOCOL_MESSAGE,
true, 0, NULL, conid, location, 1000, (
char*)msgConnect->
data, msgConnect->
getSize());
2360 if (!channel || !conid) {
2361 unittest::fail(
"NetworkManager delayed-connect test: could not create channel for connection 1");
2364 conIDs.push_back(conid);
2365 for (uint32 n = 1; n < CONNECTIONS; n++) {
2368 unittest::fail(
"NetworkManager delayed-connect test: could not add connection %u", n + 1);
2371 conIDs.push_back(conid);
2375 for (uint32 n = 0; n < CONNECTIONS; n++) {
2379 unittest::fail(
"NetworkManager delayed-connect test: greeting %u not received", n + 1);
2402 uint32 size = 70000;
2411 uint32 count = 100, subcount = 100, subcount2 = 10, n, m, k;
2414 PsyType type = { { 1,10001,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };
2429 for (
int tries = 0; tries < 20 && !listen; tries++) {
2434 unittest::fail(
"NetworkManager test: could not start listening on port 38100");
2444 dat =
new char[size];
2445 memset(dat, 0, size);
2447 msg->
setData(
"Test", dat, size);
2459 unittest::fail(
"NetworkManager test: message sent and received mismatch");
2478 unittest::fail(
"NetworkManager test: message sent and received mismatch");
2490 count = 10, subcount = 10, subcount2 = 15;
2491 for (m = 0; m < count; m++) {
2494 for (n = 0; n < subcount; n++) {
2495 type.levels[15] = n;
2498 for (k=0; k<subcount2; k++) {
2500 unittest::fail(
"NetworkManager test: could not send TCP message %u", n);
2510 for (k=0; k<subcount2; k++) {
2512 type.levels[15] = n;
2514 unittest::fail(
"NetworkManager test: [%u/%u/%u] no TCP message received", n, m, k);
2518 unittest::fail(
"NetworkManager test: [%u] TCP message sent and received mismatch", n);
2525 unittest::detail(
"[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2526 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2527 subcount*subcount2, subcount*subcount2 * size,
2528 ((
double)(end-start))/(subcount*subcount2));
2533 uint32 tcpMsgs = count*subcount*subcount2;
2534 double tcpUs = (double)t;
2535 unittest::detail(
"Total: Sent and received %u TCP msgs, %.3fus per msg", tcpMsgs, tcpUs/tcpMsgs);
2536 unittest::metric(
"tcp_msg_throughput", (
double)tcpMsgs / tcpUs * 1e6,
"msg/s",
true);
2538 unittest::metric(
"tcp_throughput", ((
double)tcpMsgs * size) / tcpUs,
"MB/s",
true);
2549 unittest::fail(
"NetworkManager test: could not start UDP listening on port 38101");
2561 dat =
new char[size];
2562 memset(dat, 0, size);
2564 msg->
setData(
"Test", dat, size);
2574 for (
int attempt = 0; attempt < 10 && !udpOk; attempt++) {
2576 unittest::fail(
"NetworkManager test: could not send UDP message");
2584 unittest::fail(
"NetworkManager test: UDP message sent and received mismatch");
2591 unittest::fail(
"NetworkManager test: no UDP round-trip on port 38101 after 10 attempts (loopback UDP may be blocked here)");
2604 count = 10, subcount = 10, subcount2 = 15;
2607 const int32 udpBudgetMs = 5000;
2608 for (m = 0; m < count &&
GetTimeAgeMS(udpLoopStart) < udpBudgetMs; m++) {
2611 for (n = 0; n < subcount; n++) {
2612 type.levels[15] = n;
2615 for (k=0; k<subcount2; k++) {
2617 unittest::fail(
"NetworkManager test: could not send UDP message %u", n);
2627 for (k=0; k<subcount2; k++) {
2639 unittest::detail(
"[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2640 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2641 subcount*subcount2, subcount*subcount2 * size,
2642 ((
double)(end-start))/(subcount*subcount2));
2649 uint32 udpMsgs = count*subcount*subcount2;
2650 double udpUs = (double)t;
2652 unittest::detail(
"Total UDP: Sent and received %u msgs, %.3fus per msg", udpMsgs, udpUs/udpMsgs);
2653 unittest::metric(
"udp_msg_throughput", (
double)udpMsgs / udpUs * 1e6,
"msg/s",
true);
2689 snprintf(text, 512,
"Hello World %llu",
GetTimeNow());
2691 uint64 localAddr = 0;
2696 printf(
"Error generating HTML page...\n\n");
2700 printf(
"Error sending response...\n\n");
2722 printf(
"WebsocketTestServer received HTTPRequest...\n\n");
2733 wsDataOut->
setData(wsData->
TEXT,
false, str.c_str(), str.length());
2787 uint64 httpStart, httpEnd;
2796 unittest::fail(
"NetworkManager HTTP test: could not start listening on port 38102");
2811 unittest::fail(
"NetworkManager HTTP test: could not create request");
2816 if (reply == NULL) {
2817 unittest::fail(
"NetworkManager HTTP test: did not receive reply");
2825 for (n=0; n<count; n++) {
2830 unittest::fail(
"NetworkManager HTTP test: could not reconnect [%u]", n);
2838 unittest::fail(
"NetworkManager HTTP test: could not create request [%u]", n);
2843 if (reply == NULL) {
2844 unittest::fail(
"NetworkManager HTTP test: did not receive reply [%u]", n);
2851 double httpUs = (double)(httpEnd - httpStart);
2853 unittest::metric(
"http_request_rate", (
double)count / httpUs * 1e6,
"req/s",
true);
2917static std::string sslTestTempPath(
const char* filename) {
2919 const char* t = getenv(
"TEMP");
2920 if (!t) t = getenv(
"TMP");
2922 return std::string(t) +
"\\" + filename;
2924 return std::string(
"/tmp/") + filename;
2928static bool generateSelfSignedCert(
const char* certPath,
const char* keyPath) {
2930 EVP_PKEY* pkey = EVP_RSA_gen(2048);
2933 X509* x509 = X509_new();
2935 ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
2936 X509_gmtime_adj(X509_getm_notBefore(x509), 0);
2937 X509_gmtime_adj(X509_getm_notAfter(x509), (
long)60 * 60 * 24 * 3650);
2938 X509_set_pubkey(x509, pkey);
2939 X509_NAME* name = X509_get_subject_name(x509);
2940 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
2941 (
const unsigned char*)
"localhost", -1, -1, 0);
2942 X509_set_issuer_name(x509, name);
2943 if (X509_sign(x509, pkey, EVP_sha256())) {
2944 FILE* kf = fopen(keyPath,
"wb");
2945 FILE* cf = fopen(certPath,
"wb");
2947 PEM_write_PrivateKey(kf, pkey, NULL, NULL, 0, NULL, NULL) == 1 &&
2948 PEM_write_X509(cf, x509) == 1)
2955 EVP_PKEY_free(pkey);
2963bool NetworkManager::UnitTestHTTPS() {
2971 uint64 httpsStart, httpsEnd;
2973 std::string certPathS = sslTestTempPath(
"psytest_https_cert.pem");
2974 std::string keyPathS = sslTestTempPath(
"psytest_https_key.pem");
2975 const char* certPath = certPathS.c_str();
2976 const char* keyPath = keyPathS.c_str();
2979 if (!generateSelfSignedCert(certPath, keyPath)) {
2980 unittest::fail(
"NetworkManager HTTPS test: could not generate self-signed certificate");
2984 HTTPTestServer* testServer =
new HTTPTestServer();
2986 manager->setSSLCertificate(certPath, keyPath);
2990 manager->setSSLAllowSelfSigned(
true);
2995 unittest::fail(
"NetworkManager HTTPS test: could not start SSL listener on port 38112");
3002 unittest::fail(
"NetworkManager HTTPS test: could not connect / TLS handshake failed");
3007 req =
new HTTPRequest((uint64)0);
3008 if (!req->createRequest(
HTTP_GET,
"",
"/", NULL, 0,
true, 0)) {
3010 unittest::fail(
"NetworkManager HTTPS test: could not create request");
3013 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3015 if (reply == NULL) {
3016 unittest::fail(
"NetworkManager HTTPS test: did not receive reply over TLS");
3024 for (n=0; n<count; n++) {
3026 con->endConnection(conid);
3029 unittest::fail(
"NetworkManager HTTPS test: could not reconnect over TLS [%u]", n);
3033 req =
new HTTPRequest((uint64)0);
3034 if (!req->createRequest(
HTTP_GET,
"",
"/", NULL, 0,
true, 0)) {
3036 unittest::fail(
"NetworkManager HTTPS test: could not create request [%u]", n);
3039 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3041 if (reply == NULL) {
3042 unittest::fail(
"NetworkManager HTTPS test: did not receive reply over TLS [%u]", n);
3049 double httpsUs = (double)(httpsEnd - httpsStart);
3050 unittest::detail(
"HTTPS: %u TLS requests in %.3fms", count, httpsUs / 1000.0);
3051 unittest::metric(
"https_request_rate", (
double)count / httpsUs * 1e6,
"req/s",
true);
3054 con->endConnection(conid);
3081bool NetworkManager::UnitTestSSLVerify() {
3083 std::string certPathS = sslTestTempPath(
"psytest_sslverify_cert.pem");
3084 std::string keyPathS = sslTestTempPath(
"psytest_sslverify_key.pem");
3085 const char* certPath = certPathS.c_str();
3086 const char* keyPath = keyPathS.c_str();
3087 uint64 conid = 0, location = 0;
3093 SSLConnection* c1 =
new SSLConnection();
3094 if (!c1->init() || (c1->getVerifyMode() != SSL_VERIFY_PEER)) {
3095 unittest::fail(
"default client ctx verify mode is not SSL_VERIFY_PEER (got %d)", c1->getVerifyMode());
3100 SSLConnection* c2 =
new SSLConnection();
3101 c2->setAllowSelfSigned(
true);
3102 if (!c2->init() || (c2->getVerifyMode() != SSL_VERIFY_NONE)) {
3103 unittest::fail(
"allowselfsigned client ctx verify mode is not SSL_VERIFY_NONE (got %d)", c2->getVerifyMode());
3108 unittest::detail(
"ctx verify modes correct (PEER by default, NONE when allowselfsigned)");
3112 if (!generateSelfSignedCert(certPath, keyPath)) {
3117 HTTPTestServer* testServer =
new HTTPTestServer();
3119 manager->setSSLCertificate(certPath, keyPath);
3131 con = manager->createTCPConnection(
"localhost", 38113,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3133 unittest::fail(
"connection to untrusted self-signed server was ACCEPTED with verification on");
3134 con->endConnection(conid);
3138 unittest::detail(
"verification on: TLS handshake to self-signed server correctly rejected");
3141 manager->setSSLAllowSelfSigned(
true);
3142 con = manager->createTCPConnection(
"localhost", 38113,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3144 unittest::fail(
"connection to self-signed server FAILED despite allowselfsigned=true");
3148 unittest::detail(
"allowselfsigned: TLS handshake to self-signed server accepted");
3149 con->endConnection(conid);
3166static EVP_PKEY* sslTestGenKey() {
3167 return EVP_RSA_gen(2048);
3170static bool sslTestWritePEM(X509* cert, EVP_PKEY* key,
const char* certPath,
const char* keyPath) {
3172 FILE* cf = fopen(certPath,
"wb");
3173 FILE* kf = keyPath ? fopen(keyPath,
"wb") : NULL;
3174 if (cf && PEM_write_X509(cf, cert) == 1)
3177 ok = (kf && PEM_write_PrivateKey(kf, key, NULL, NULL, 0, NULL, NULL) == 1);
3183static bool sslTestAddExt(X509* cert, X509* issuer,
int nid,
const char* value) {
3185 X509V3_set_ctx_nodb(&ctx);
3186 X509V3_set_ctx(&ctx, issuer, cert, NULL, NULL, 0);
3187 X509_EXTENSION* ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
3190 X509_add_ext(cert, ext, -1);
3191 X509_EXTENSION_free(ext);
3196static bool sslTestGenCA(
const char* caCertPath, EVP_PKEY** caKeyOut, X509** caCertOut) {
3197 EVP_PKEY* key = sslTestGenKey();
3200 X509* x = X509_new();
3201 if (!x) { EVP_PKEY_free(key);
return false; }
3202 X509_set_version(x, 2);
3203 ASN1_INTEGER_set(X509_get_serialNumber(x), 1000);
3204 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3205 X509_gmtime_adj(X509_getm_notAfter(x), (
long)60 * 60 * 24 * 365);
3206 X509_set_pubkey(x, key);
3207 X509_NAME* name = X509_get_subject_name(x);
3208 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
3209 (
const unsigned char*)
"Psyclone Test CA", -1, -1, 0);
3210 X509_set_issuer_name(x, name);
3211 bool ok = sslTestAddExt(x, x, NID_basic_constraints,
"critical,CA:TRUE") &&
3212 sslTestAddExt(x, x, NID_key_usage,
"critical,keyCertSign,cRLSign") &&
3213 (X509_sign(x, key, EVP_sha256()) != 0) &&
3214 sslTestWritePEM(x, NULL, caCertPath, NULL);
3215 if (!ok) { X509_free(x); EVP_PKEY_free(key);
return false; }
3222static bool sslTestGenLeaf(
const char* certPath,
const char* keyPath,
const char* cn,
3223 const char* san, X509* caCert, EVP_PKEY* caKey,
long serial) {
3224 EVP_PKEY* key = sslTestGenKey();
3227 X509* x = X509_new();
3228 if (!x) { EVP_PKEY_free(key);
return false; }
3229 X509_set_version(x, 2);
3230 ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
3231 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3232 X509_gmtime_adj(X509_getm_notAfter(x), (
long)60 * 60 * 24 * 365);
3233 X509_set_pubkey(x, key);
3234 X509_NAME* name = X509_get_subject_name(x);
3235 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
3236 (
const unsigned char*)cn, -1, -1, 0);
3237 X509_set_issuer_name(x, X509_get_subject_name(caCert));
3238 bool ok = sslTestAddExt(x, caCert, NID_basic_constraints,
"critical,CA:FALSE") &&
3239 sslTestAddExt(x, caCert, NID_subject_alt_name, san) &&
3240 (X509_sign(x, caKey, EVP_sha256()) != 0) &&
3241 sslTestWritePEM(x, key, certPath, keyPath);
3255bool NetworkManager::UnitTestSSLHostCA() {
3257 std::string caCertPathS = sslTestTempPath(
"psytest_sslhostca_ca.pem");
3258 std::string goodCertPathS = sslTestTempPath(
"psytest_sslhostca_good_cert.pem");
3259 std::string goodKeyPathS = sslTestTempPath(
"psytest_sslhostca_good_key.pem");
3260 std::string badCertPathS = sslTestTempPath(
"psytest_sslhostca_bad_cert.pem");
3261 std::string badKeyPathS = sslTestTempPath(
"psytest_sslhostca_bad_key.pem");
3262 const char* caCertPath = caCertPathS.c_str();
3263 const char* goodCertPath = goodCertPathS.c_str();
3264 const char* goodKeyPath = goodKeyPathS.c_str();
3265 const char* badCertPath = badCertPathS.c_str();
3266 const char* badKeyPath = badKeyPathS.c_str();
3267 EVP_PKEY* caKey = NULL;
3268 X509* caCert = NULL;
3269 uint64 conid = 0, location = 0;
3271 HTTPTestServer* testServer = NULL;
3278 if (!sslTestGenCA(caCertPath, &caKey, &caCert)) {
3282 if (!sslTestGenLeaf(goodCertPath, goodKeyPath,
"localhost",
"DNS:localhost", caCert, caKey, 1001) ||
3283 !sslTestGenLeaf(badCertPath, badKeyPath,
"wronghost.example",
"DNS:wronghost.example", caCert, caKey, 1002)) {
3284 unittest::fail(
"could not generate CA-signed server certificates");
3285 X509_free(caCert); EVP_PKEY_free(caKey);
3289 unittest::detail(
"CA + leaf certs generated (SAN localhost / wronghost.example)");
3291 testServer =
new HTTPTestServer();
3295 goodManager->setSSLCertificate(goodCertPath, goodKeyPath);
3304 unittest::progress(30,
"connecting with verify on, custom CA NOT provided (must be rejected)");
3305 con = goodManager->createTCPConnection(
"localhost", 38114,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3307 unittest::fail(
"CA-signed server was ACCEPTED although its CA was not provided");
3308 con->endConnection(conid);
3314 unittest::progress(45,
"connecting with custom CA provided + matching hostname (must be accepted)");
3315 goodManager->setSSLCALocation(caCertPath, NULL);
3316 con = goodManager->createTCPConnection(
"localhost", 38114,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3318 unittest::fail(
"connection FAILED despite custom CA provided and matching hostname");
3323 con->endConnection(conid);
3327 badManager->setSSLCertificate(badCertPath, badKeyPath);
3328 badManager->setSSLCALocation(caCertPath, NULL);
3337 unittest::progress(70,
"connecting by hostname to server with mismatching cert (must be rejected)");
3338 con = badManager->createTCPConnection(
"localhost", 38115,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3340 unittest::fail(
"server with cert for wronghost.example was ACCEPTED for hostname localhost");
3341 con->endConnection(conid);
3345 unittest::detail(
"hostname mismatch: trusted cert for wrong host correctly rejected");
3347 unittest::progress(85,
"connecting by IP literal (no hostname check; must be accepted)");
3348 con = badManager->createTCPConnection(
"127.0.0.1", 38115,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3350 unittest::fail(
"connection by IP literal FAILED (chain-of-trust only path broken)");
3354 unittest::detail(
"IP literal connect: chain-of-trust only, accepted (hostname check skipped)");
3355 con->endConnection(conid);
3359 delete(goodManager);
3364 EVP_PKEY_free(caKey);
3366 unlink(goodCertPath); unlink(goodKeyPath);
3367 unlink(badCertPath); unlink(badKeyPath);
3396 printf(
"Connecting...\n");
3399 printf(
"Could not connect to host %s:%u...\n\n", data->
host, data->
port);
3405 for (uint32 n=0; n<10; n++) {
3413 printf(
"Could not create request [%u]...\n\n", n);
3418 printf(
"[%u] Sending...\n", n);
3422 if (reply == NULL) {
3423 printf(
"Did not receive reply...\n\n");
3428 printf(
"Server unavailable...\n\n");
3433 printf(
"Server no reply...\n\n");
3438 printf(
"Server malformed reply...\n\n");
3470 unittest::fail(
"NetworkManager websocket test: could not start listening on port 38103");
3478 unittest::fail(
"NetworkManager websocket test: could not connect");
3486 unittest::fail(
"NetworkManager websocket test: could not create websocket upgrade request");
3491 if (reply == NULL) {
3492 unittest::fail(
"NetworkManager websocket test: did not receive handshake reply");
3497 unittest::fail(
"NetworkManager websocket test: reply was not a websocket upgrade");
3522 printf(
"Testing HTTP on %s:%u with random urls...\n\n", host, port);
3525 uint32 numThreads = 5;
3534 printf(
"Could not connect to host %s:%u...\n\n", host, port);
3539 printf(
"Host is available, starting test...\n\n");
3603 uint32 threadIDs[1000];
3605 for (n=0; n<loops; n++) {
3606 for (m=0; m<numThreads; m++) {
3607 testData[m].
host = host;
3608 testData[m].
port = port;
3609 testData[m].
urls = &urls;
3610 testData[m].
manager = manager;
3618 for (m=0; m<numThreads; m++) {
3621 if (testData[m].status > 10) {
3622 printf(
"[%u] Failed\n", m);
3626 printf(
"[%u] Done\n", m);
3629 if (m >= numThreads)
3641 "TCP and UDP message send/receive over loopback",
"network");
3643 "Delayed/greeting TCP connections to a loopback listener",
"network");
3645 "HTTP server/client request/reply over loopback",
"network");
3647 "Websocket upgrade handshake against loopback server",
"network");
3652 "HTTPS (TLS) server/client request/reply over loopback",
"network");
3654 "SSL client certificate verification (secure by default, allowselfsigned opt-out)",
"network");
3656 "SSL hostname verification (SSL_set1_host) and custom CA location (SSL_CTX_load_verify_locations)",
"network");
#define NETWORKERROR_GREETING_ERROR
The initial greeting/handshake data exchange failed.
#define NOENC
Plain, unencrypted transport.
#define SSLENC
SSL/TLS encryption (requires build with _USE_SSL_).
Connection/channel management layer: multi-protocol listeners, typed dispatch, HTTP client — and the ...
#define NETWORKEVENT_PROTOCOL_ERROR
Protocol parsing/framing error on the connection.
#define NETWORKEVENT_CONNECT
Connection established.
#define NETWORKEVENT_DISCONNECT_RETRYING
Connection lost; reconnection attempts in progress.
#define NETWORKEVENT_DISCONNECT
Connection closed for good.
#define NETWORKEVENT_RECONNECT
Connection re-established after a failure (autoreconnect).
#define NETWORKEVENT_UNPROCESSED_DATA
Bytes arrived that no protocol handler consumed.
#define HTTP_MALFORMED_URL
400 Bad Request
#define PROTOCOL_TELNET
Line-based Telnet-style text protocol.
#define HTTP_SERVER_MALFORMED_REPLY
500 (malformed backend reply)
#define PROTOCOL_HTTP_CLIENT
Speak HTTP as a client: send requests, parse replies.
#define HTTP_SWITCH_PROTOCOL
101 Switching Protocols (WebSocket upgrade)
#define HTTP_ACCESS_DENIED
403 Forbidden
#define HTTP_SERVER_NOREPLY
500 (no reply from backend server)
#define PROTOCOL_HTTP_SERVER
Serve HTTP: parse requests, send replies (server role).
#define HTTP_NOT_IMPLEMENTED
501 Not Implemented
#define PROTOCOL_MESSAGE
CMSDK binary DataMessage protocol (size-prefixed frames).
#define HTTP_SERVER_UNAVAILABLE
500 (backend server unavailable)
Small, dependency-free unit test harness used by all CMSDK object tests.
#define thread_ret_val(ret)
#define THREAD_FUNCTION_CALL
THREAD_RET(* THREAD_FUNCTION)(void *)
#define GETIPADDRESSQUADPORT(a)
#define GETIPADDRESSPORT(a, p)
The central Psyclone data container: a self-contained binary message with typed, named user entries.
uint32 getFrom()
getFrom() Get the sender id
bool setString(const char *key, const char *value)
setString(const char* key, const char* value)
DataMessageHeader * data
Pointer to the message's flat memory block (header + user entries).
PsyType getType()
getType()
uint32 getSize()
getSize() Get message size Many types of data of any size can be put into a message as user entries; ...
bool setType(PsyType &type)
setType(PsyType &type)
bool setData(const char *key, const char *value, uint32 size)
setData(const char* key, const char* value, uint32 size)
static bool SendHTTPReply(NetworkConnection *con, HTTPReply *reply)
Serialise and send a reply.
static HTTPReply * ReceiveHTTPReply(NetworkConnection *con, uint32 timeout)
Read a full reply from the connection.
static bool SendHTTPRequest(NetworkConnection *con, HTTPRequest *req)
Serialise and send a request.
static HTTPRequest * ReceiveHTTPRequest(NetworkConnection *con, uint32 timeout)
Read a full request from the connection.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static WebsocketData * ReceiveWebsocketData(NetworkConnection *con, uint32 timeout)
Read one complete WebSocket message (reassembling fragments).
static bool SendWebsocketData(NetworkConnection *con, WebsocketData *wsData)
Send a WebSocket frame.
A parsed or generated HTTP response.
bool isWebsocketUpgrade()
uint8 type
HTTP_* status id of this reply.
static HTTPReply * CreateErrorReply(uint8 type)
Build a canned error reply.
static HTTPReply * CreateWebsocketHTTPReply(const char *key, const char *version)
Build the "101 Switching Protocols" reply for a WebSocket handshake.
bool createPage(uint8 status, uint64 time, const char *serverName, uint64 lastMod, bool keepAlive, bool cache, const char *contentType, const char *content, uint32 contentSize=0, const char *additionalHeaderEntries=NULL)
Build a complete response with headers and content.
A parsed or generated HTTP request (also used for WebSocket upgrade handshakes).
const char * getHeaderEntry(const char *entry)
Look up a header field (case-insensitive).
uint32 headerLength
Length of the header block in data, in bytes.
uint32 contentLength
Body length (from Content-Length / parsing), in bytes.
static HTTPRequest * CreateWebsocketRequest(const char *uri, const char *host, const char *protocolName, const char *origin)
Build a client-side WebSocket upgrade request (RFC 6455 handshake).
bool createWebsocketRequest(const char *uri, const char *host, const char *protocolName, const char *origin)
Fill this object with a WebSocket upgrade handshake request.
bool createRequest(uint8 type, const char *host, const char *uri, const char *content, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince)
Build a simple request with optional raw body.
bool createMultipartRequest(uint8 type, const char *host, const char *uri, std::map< std::string, std::string > &headerEntries, std::map< std::string, HTTPPostEntry * > &bodyEntries, bool keepAlive, uint64 ifModifiedSince)
Build a multipart/form-data request from several named parts.
bool isWebsocketUpgrade()
Minimal HTTP server used by the unit tests: replies with a canned page.
virtual bool receiveHTTPRequest(HTTPRequest *req, NetworkChannel *channel, uint64 conid)
Serve a test page for any request.
static bool SendMessage(NetworkConnection *con, DataMessage *msg, uint64 receiver=0, bool packed=false)
Serialise and send one message.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static DataMessage * ReceiveMessage(NetworkConnection *con, uint32 timeout)
Read one full message frame.
One logical network interface: a group of listeners/connections with shared dispatch.
bool setNewReceiver(NetworkReceiver *recv)
Replace the channel's receiver for async dispatch.
bool isConnected(uint64 conid)
static THREAD_RET THREAD_FUNCTION_CALL TelnetServerRun(THREAD_ARG arg)
Thread entry: Telnet server connection loop.
HTTPReply * sendReceiveHTTPRequest(HTTPRequest *req, uint64 conid, uint32 timeout)
Send an HTTP request and block for its reply on the same connection.
NetworkManager * manager
Owning manager (not owned).
std::queue< HTTPReply * > queueHTTPReplies
bool sendHTTPRequest(HTTPRequest *req, uint64 conid)
Send an HTTP request without waiting for the reply (reply arrives via receiveHTTPReply()/waitForHTTPR...
utils::Mutex channelMutex
uint8 getConnectionType(uint64 conid)
HTTPReply * waitForHTTPReply(uint64 &conid, uint32 ms)
Wait for the next queued HTTP reply (sync-mode HTTP client).
bool enterHTTPReply(HTTPReply *reply, HTTPRequest *req, uint64 conid)
Queue or push an incoming HTTP reply.
uint64 getRemoteAddress(uint64 conid)
std::queue< NetworkEvent * > eventQueue
virtual ~NetworkChannel()
NetworkChannel(NetworkManager *manager)
utils::Semaphore eventQueueSemaphore
uint64 createWebsocketConnection(const char *url, const char *protocolName, const char *origin=NULL, uint32 timeoutMS=5000)
Open a client WebSocket connection from a full URL.
uint32 getOutputSpeed(uint64 conid)
std::map< uint16, NetworkThread * > listeners
TCP listener threads by port (owned).
WebsocketData * waitForWebsocketData(uint64 &conid, uint32 ms)
Wait for the next queued WebSocket message.
bool stopListener(uint16 port, uint8 protocol)
Stop a listener on this channel.
utils::Semaphore queueMessagesSemaphore
uint32 getInputSpeed(uint64 conid)
utils::Mutex queueHTTPRequestsMutex
bool enterTelnetLine(TelnetLine *line, uint64 conid)
Queue or push an incoming Telnet line.
static THREAD_RET THREAD_FUNCTION_CALL MessageConnectionRun(THREAD_ARG arg)
Thread entry: binary DataMessage connection loop.
utils::Mutex queueHTTPRepliesMutex
utils::Semaphore queueHTTPRepliesSemaphore
bool endUDPConnection(uint16 port)
Close the UDP connection bound to port.
bool sendMessage(DataMessage *msg, uint64 conid)
Send a DataMessage.
std::map< uint64, NetworkThread * > connectionThreads
Worker threads by connection id (owned).
utils::Semaphore queueTelnetLinesSemaphore
bool enterMessage(DataMessage *msg, uint64 conid)
Queue or push an incoming DataMessage.
utils::Mutex queueWebsocketDataMutex
bool endConnection(uint64 conid)
Gracefully close a connection.
static THREAD_RET THREAD_FUNCTION_CALL ConnectionAutodetectRun(THREAD_ARG arg)
Thread entry: protocol sniffing for a fresh connection (see autoDetectConnection()).
void applySSLClientPolicy(SSLConnection *con)
std::queue< WebsocketData * > queueWebsocketData
TelnetLine * sendReceiveTelnetLine(TelnetLine *line, uint64 conid, uint32 timeout, uint32 size=0)
Send a Telnet line and block for the response line.
friend class NetworkManager
uint64 addTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint64 &location, uint32 timeoutMS=5000, const char *greetingData=NULL, uint32 greetingSize=0)
Connect with optional greeting bytes (no autoreconnect); see NetworkManager::addTCPConnection().
uint64 startConnection(NetworkConnection *con, uint8 protocol, bool isAsync, bool autoreconnect, uint32 timeoutMS=5000)
Start the protocol worker thread for an already-connected connection.
bool shutdown()
Stop all worker threads and close all listeners/connections of this channel.
NetworkEvent * waitForNetworkEvent(uint32 ms)
Wait for the next connection lifecycle event (sync mode).
static THREAD_RET THREAD_FUNCTION_CALL HTTPClientRun(THREAD_ARG arg)
Thread entry: HTTP client connection loop (send requests, parse replies).
std::queue< DataMessage * > queueMessages
bool enterNetworkEvent(uint8 type, uint8 protocol, uint64 conid)
Queue or push a lifecycle event.
utils::Mutex queueTelnetLinesMutex
utils::Semaphore queueHTTPRequestsSemaphore
NetworkReceiver * receiver
Async dispatch target (not owned; may be NULL).
std::queue< HTTPRequest * > queueHTTPRequests
bool sendTelnetLine(TelnetLine *line, uint64 conid)
Send a Telnet line.
utils::Semaphore queueWebsocketDataSemaphore
bool sendWebsocketData(WebsocketData *wsData, uint64 conid)
Send a WebSocket frame.
static THREAD_RET THREAD_FUNCTION_CALL NetworkListenerRun(THREAD_ARG arg)
Thread entry: accept loop for a TCP listener.
utils::Mutex eventQueueMutex
utils::Mutex queueMessagesMutex
bool enterHTTPRequest(HTTPRequest *req, uint64 conid)
Queue or push an incoming HTTP request.
std::map< uint16, NetworkThread * > udpListeners
UDP listener threads by port (owned).
bool startListener(uint64 cid, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 protocolTimeout=3000, bool isDefaultProtocol=false)
Open a listener on this channel (see NetworkManager::createListener() for semantics).
std::queue< uint64 > queueMessageConIDs
uint64 createTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint64 &location, uint32 timeoutMS=5000)
Connect to a host by name/IP; see NetworkManager::createTCPConnection().
uint64 autoDetectConnection(NetworkConnection *con, uint16 port, uint32 autoProtocols, uint32 autoProtocolTimeout, uint32 defaultProtocol, bool isAsync, bool autoreconnect)
Adopt an incoming connection whose protocol is not yet known: sniff its first bytes against autoProto...
bool sendHTTPReply(HTTPReply *reply, uint64 conid)
Send an HTTP reply on a server connection.
static THREAD_RET THREAD_FUNCTION_CALL HTTPServerRun(THREAD_ARG arg)
Thread entry: HTTP/WebSocket server connection loop — the built-in web server's per-connection worker...
uint32 cid
Channel id within the manager.
uint64 createUDPConnection(uint16 port, uint8 protocol, bool isAsync, bool autoreconnect)
Bind a UDP port on this channel.
HTTPRequest * waitForHTTPRequest(uint64 &conid, uint32 ms)
Wait for the next queued HTTP request (sync-mode HTTP server).
bool enterWebsocketData(WebsocketData *wsData, uint64 conid)
Queue or push an incoming WebSocket message.
TelnetLine * waitForTelnetLine(uint64 &conid, uint32 ms)
Wait for the next queued Telnet line.
DataMessage * waitForMessage(uint64 &conid, uint32 ms)
Wait for the next queued DataMessage.
std::queue< TelnetLine * > queueTelnetLines
Abstract base class for all point-to-point network connections.
virtual bool send(const char *data, uint32 size, uint64 receiver=0)=0
Send raw bytes on the connection.
virtual uint32 getOutputSpeed()
virtual uint32 clearBuffer()
Discard all currently buffered input.
virtual uint64 getRemoteAddress()
bool setConnectTimeout(uint32 timeoutMS)
Set the timeout used by subsequent connect()/reconnect() attempts.
virtual bool receiveAvailable(char *data, uint32 &size, uint32 maxSize, uint32 timeout, bool peek=false)
Receive whatever bytes are available (up to maxSize).
virtual bool disconnect(uint16 error=0)
Close the connection and release the socket.
virtual uint8 getConnectionType()
virtual uint32 getInputSpeed()
char * greetingData
Owned copy of the greeting bytes (NULL if unset).
virtual bool isConnected(int timeout=0)
Test whether the connection is currently alive.
bool setGreetingData(const char *data, uint32 size)
Set greeting bytes sent automatically right after a connection is established (used e....
virtual bool reconnect(uint32 timeoutMS)=0
Re-establish the connection to the previously known remote endpoint.
uint32 greetingSize
Size of greetingData in bytes.
Central owner of all channels, listeners and connections in a process.
static bool WebsocketTest()
WebSocket upgrade + echo self-test.
NetworkChannel * findChannelByID(uint32 channelID)
Look up a channel by CHANNEL id.
friend class NetworkChannel
void unregisterTCPListener(uint16 port)
Unbind a TCP listener port.
utils::Mutex udpOutputConMutex
Serialises use of udpOutputCon.
void registerTCPListener(uint16 port, NetworkChannel *channel)
Bind a TCP listener port to a channel.
uint64 lastConnectionID
Last connection id issued.
bool endConnection(uint64 conid)
Gracefully close a connection (thread is asked to finish; entry kept for reuse).
uint8 getConnectionType(uint64 conid)
void registerUDPListener(uint16 port, NetworkChannel *channel)
Bind a UDP listener port to a channel.
NetworkChannel * getUDPConnectionByPort(uint16 port)
bool endUDPConnection(uint16 port)
Close the UDP connection bound to port.
uint32 allocateChannelID()
Allocate the next unused channel id.
std::map< uint64, NetworkChannel * > channelsByConnection
Channel lookup by connection id.
static bool UnitTestHTTP()
Self-test of the built-in HTTP server and client.
utils::Mutex mapMutex
Serialises ALL four maps above.
NetworkChannel * findChannelByConnection(uint64 conid)
Look up the channel owning a CONNECTION id.
NetworkChannel * addTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 channelID, NetworkReceiver *recv, uint64 &conid, uint64 &location, uint32 timeoutMS=5000, const char *greetingData=NULL, uint32 greetingSize=0)
Like createTCPConnection() but sends optional greeting bytes right after connecting (peer identificat...
void registerConnection(uint64 conid, NetworkChannel *channel)
Map a connection id to its owning channel.
UDPConnection * udpOutputCon
Shared output-only UDP socket for sendUDPMessage().
static bool TestHTTP(const char *host, uint32 port, std::vector< std::string > &urls)
Fetch a list of URLs from a host and report results (manual test helper).
std::string sslKeyPath
PEM private key path for SSL listeners.
static bool UnitTest()
Basic TCP/message-protocol round-trip self-test.
NetworkChannel * createTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint32 channelID, NetworkReceiver *recv, uint64 &conid, uint64 &location, uint32 timeoutMS=5000)
Connect to a remote host by name/IP and start the protocol thread.
bool stopListener(uint16 port, uint8 protocol)
Stop a listener previously opened with createListener().
THREAD_RET THREAD_FUNCTION_CALL NetworkManagerRun(THREAD_ARG arg)
Thread entry point of the manager's supervision loop (do not call directly).
bool setSSLCertificate(const char *sslCertPath, const char *sslKeyPath)
Set the certificate/key used by SSL listeners created via this manager.
std::string sslCertPath
PEM certificate path for SSL listeners.
NetworkChannel * createWebsocketConnection(const char *url, uint32 channelID, NetworkReceiver *recv, uint64 &conid, const char *protocolName=NULL, const char *origin=NULL, uint32 timeoutMS=5000)
Open a client WebSocket connection from a full URL (ws:// or wss://).
void unregisterChannel(uint32 channelID)
Remove a channel id (and nothing else) from the registry.
uint32 lastChannelID
Last channel id issued.
NetworkChannel * getConnection(uint64 conid)
NetworkChannel * getTCPConnectionByPort(uint16 port)
uint64 addConnection(NetworkChannel *channel)
Register an externally created channel with the manager.
NetworkChannel * createListener(uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 protocolTimeout, bool isDefaultProtocol, uint32 channelID, NetworkReceiver *recv)
Open a listening port for one or more protocols.
uint64 getRemoteAddress(uint64 conid)
void registerChannel(uint32 channelID, NetworkChannel *channel)
Register a channel under a channel id, and optionally a TCP listener port.
HTTPReply * makeHTTPRequest(const char *url, uint32 timeout, const char *content=NULL, uint32 contentSize=0)
Blocking HTTP(S) exchange from a URL string (GET, or POST when content given).
std::map< uint32, NetworkChannel * > channels
All channels by channel id (owned).
NetworkChannel * createUDPConnection(uint16 port, uint8 protocol, bool isAsync, bool autoreconnect, uint32 channelID, NetworkReceiver *recv, uint64 &conid)
Bind a UDP port for datagram traffic.
bool removeConnection(uint64 conid)
Close a connection and remove it from the manager's maps entirely.
virtual ~NetworkManager()
std::map< uint16, NetworkChannel * > listeners
TCP listeners by port.
std::map< uint16, NetworkChannel * > udpListeners
UDP listeners by port.
bool sendUDPMessage(DataMessage *msg, uint64 destination)
Send a DataMessage as a UDP datagram via the shared output socket.
static bool UnitTestDelayedConnect()
Self-test of non-blocking (delayed) connect handling.
Callback interface for asynchronous delivery of parsed network traffic.
Bookkeeping for one worker thread of a NetworkChannel (per listener or connection).
bool reaping
Set by whichever teardown path has CLAIMED this object for freeing.
NetworkThread(NetworkChannel *parent, uint64 id)
uint32 defaultProtocol
Fallback PROTOCOL_* when auto-detection is inconclusive.
NetworkChannel * parent
Owning channel (not owned).
TCPListener * listener
Listener served (owned), or NULL for connections.
uint16 port
Local port (listeners) or 0.
uint32 autoProtocolTimeout
Milliseconds allowed for protocol sniffing.
NetworkConnection * con
Connection served (owned), or NULL for listeners.
bool isAsync
Push to receiver (true) or queue for waitFor*() (false).
uint64 id
Connection or listener id served by this thread.
bool autoreconnect
Re-establish the connection automatically on failure.
uint32 threadID
ThreadManager id of the worker thread.
bool isRunning
True while the worker loop is active.
uint32 autoProtocols
PROTOCOL_* bit set to auto-detect among.
bool shouldContinue
Loop control flag: thread exits when false.
HTTPRequest * lastRequest
Last request pending a reply on this HTTP connection.
ThreadHandle hWorker
OS-level handle of the worker thread (0 until started).
SSL/TLS-encrypted TCP connection (OpenSSL) with configurable peer verification.
bool init()
Initialise the OpenSSL context for a client-side connection.
void setCALocation(const char *caFile, const char *caPath)
bool connect(SOCKET s, uint64 localAddr, NetworkDataReceiver *receiver=NULL)
Adopt an already-accepted socket and perform the server-side TLS handshake.
void setAllowSelfSigned(bool allow)
bool delayedConnect(uint64 addr, uint32 timeoutMS, NetworkDataReceiver *receiver)
Begin a non-blocking connect (TLS handshake completes in didConnect()).
Plain TCP stream connection (client-initiated or accepted from a listener).
bool connect(SOCKET s, uint64 localAddr, NetworkDataReceiver *receiver=NULL)
Adopt an already-connected socket (server side, from a TCPListener).
bool delayedConnect(uint64 addr, uint32 timeoutMS, NetworkDataReceiver *receiver)
Begin a non-blocking connect; completion is checked with didConnect().
TCP server socket: binds a port and accepts inbound connections (plain or SSL).
NetworkConnection * acceptConnection(uint32 timeout)
Synchronously wait for and accept one inbound connection.
bool setSSLCertificate(const char *sslCertPath, const char *sslKeyPath)
Set the SSL certificate and private key used for inbound SSL connections.
bool init(uint16 port, uint8 encryption, NetworkConnectionReceiver *receiver=NULL, NetworkDataReceiver *dataReceiver=NULL)
Bind and start listening on a port.
One line of Telnet-style text traffic.
static TelnetLine * ReceiveTelnetLine(NetworkConnection *con, uint32 timeout)
Read one line terminated by CR/LF.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static bool SendTelnetLine(NetworkConnection *con, TelnetLine *line)
Send one line (with its line ending).
static bool GetThreadHandle(uint32 id, ThreadHandle &out)
Copy out the OS-level handle of the thread in a manager slot.
static bool CreateThread(THREAD_FUNCTION func, void *args, uint32 &newID, uint32 reqID=0)
Create a new native thread and start it immediately.
static bool JoinThread(uint32 id)
Wait for a thread to have COMPLETELY finished, then release it - exactly once.
static bool IsThreadRunning(uint32 id)
Check whether the thread is still alive at the OS level.
static bool TerminateThread(uint32 id)
Forcibly terminate the thread and release its slot.
static bool Shutdown()
Terminate all managed threads, then destroy the singleton.
UDP datagram connection (bound port for input, or output-only sender).
bool connect(uint16 port, NetworkDataReceiver *receiver=NULL)
Bind a local UDP port for receiving datagrams.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
One WebSocket frame/message (RFC 6455): parsing, generation and control frames.
const char * getContent(uint64 &size)
Get the decoded (unmasked) payload.
bool isTerminationRequest()
enum cmlabs::WebsocketData::DataType dataType
static WebsocketData * CreateTerminationConfirmation()
bool setData(DataType dataType, bool maskData, const char *data=NULL, uint64 size=0)
Set the payload and build the serialised frame for sending.
Minimal WebSocket echo server used by the unit tests (handles upgrade + echo).
virtual bool receiveHTTPRequest(HTTPRequest *req, NetworkChannel *channel, uint64 conid)
Answer the WebSocket upgrade handshake.
virtual bool receiveWebsocketData(WebsocketData *wsData, NetworkChannel *channel, uint64 conid)
Echo received frames back to the client.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
std::string PrintTimeNowString(bool local=true, bool us=true, bool ms=true)
Format GetTimeNow().
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
bool Sleep(uint32 ms)
Suspend the calling thread.
std::string GetURIFromURL(std::string url)
Extract the URI (path plus query) from a URL.
std::string GetProtocolFromURL(std::string url)
Extract the protocol/scheme from a URL.
std::string GetHostFromURL(std::string url)
Extract the host name (or IP literal) from a URL.
uint16 GetPortFromURL(std::string url)
Extract the port number from a URL.
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
bool SeedRandomValues(uint32 seedvalue=0)
Seed the pseudo-random generator.
double RandomValue()
Uniform random double in [0,1).
bool GetLocalIPAddress(uint32 &address)
Get the primary local IPv4 address.
std::string StringFormat(const char *format,...)
printf into a std::string.
THREAD_RET THREAD_FUNCTION_CALL HTTPServerTest(THREAD_ARG arg)
static struct PsyType CTRL_TEST
void Register_NetworkManager_Tests()
Hierarchical message type identifier — the key used for publish/subscribe matching in Psyclone.
std::vector< std::string > * urls
Notification of a connection lifecycle change (connect, disconnect, buffer state.....
uint64 time
Event timestamp (ms epoch).
uint64 cid
Channel id the event belongs to.
uint8 protocol
PROTOCOL_* of the affected connection.
uint64 conid
Connection id within the channel.
uint8 type
NETWORKEVENT_* event type.