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))
1577 if ((thread == NULL) || (thread->
con == NULL))
1587 if ((thread == NULL) || (thread->
con == NULL))
1599 if ((thread == NULL) || (thread->
con == NULL))
1606 if ((thread == NULL) || (thread->
con == NULL))
1613 if ((thread == NULL) || (thread->
con == NULL))
1706 uint64 conid =
manager->addConnection(
this);
1741 uint64 conid =
manager->addConnection(
this);
1788 if ((thread == NULL) || (thread->
listener == NULL))
1812 if ((thread == NULL) || (thread->
con == NULL))
1818 uint32 maxSize = 1024;
1819 char* buffer =
new char[maxSize];
1829 delete(thread->
con);
1864 delete(thread->
con);
1870 if (protocol == 0) {
1872 LogPrint(0,
LOG_NETWORK,2,
"No valid protocol detected for incoming network connection, disconnecting...\n\n");
1874 delete(thread->
con);
1916 delete(thread->
con);
1929 if ((thread == NULL) || (thread->
con == NULL))
1933 bool disconnected =
false;
1935 bool upgradedToWebsocket =
false;
1945 if (upgradedToWebsocket) {
1955 uint64 endConid = thread->
id;
1977 upgradedToWebsocket =
true;
1989 if (!disconnected) {
1991 disconnected =
true;
2005 disconnected =
false;
2013 uint64 endConid = thread->
id;
2025 uint64 endConid = thread->
id;
2043 if ((thread == NULL) || (thread->
con == NULL))
2047 bool disconnected =
false;
2049 bool upgradedToWebsocket =
false;
2050 std::string wsOrigin;
2058 if (upgradedToWebsocket) {
2066 delete(wsDataReply);
2088 const char* version = req->
getHeaderEntry(
"Sec-WebSocket-Version");
2089 if (key && version) {
2103 upgradedToWebsocket =
true;
2109 LogPrint(0,
LOG_NETWORK, 1,
"Unable to upgrade HTTP Server %llu to Websocket, key and/or version not provided", thread->
id);
2130 uint64 endConid = thread->
id;
2142 uint64 endConid = thread->
id;
2164 if ((thread == NULL) || (thread->
con == NULL))
2168 bool disconnected =
false;
2169 bool wasConnected =
false;
2176 wasConnected =
true;
2195 if (!disconnected && wasConnected) {
2197 disconnected =
true;
2231 disconnected =
false;
2243 uint64 endConid = thread->
id;
2255 uint64 endConid = thread->
id;
2265 if ((thread == NULL) || (thread->
con == NULL))
2269 bool disconnected =
false;
2299 uint64 endConid = thread->
id;
2310 uint64 endConid = thread->
id;
2322 const uint16 PORT = 38101;
2323 const uint32 CONNECTIONS = 3;
2331 unittest::fail(
"NetworkManager delayed-connect test: could not start listening on port %u", PORT);
2338 msgConnect->
setString(
"URI",
"ExecutorConnect");
2340 std::vector<uint64> conIDs;
2342 uint64 location = 0;
2346 channel = manager->
addTCPConnection(
"localhost", PORT,
NOENC,
PROTOCOL_MESSAGE,
true, 0, NULL, conid, location, 1000, (
char*)msgConnect->
data, msgConnect->
getSize());
2347 if (!channel || !conid) {
2348 unittest::fail(
"NetworkManager delayed-connect test: could not create channel for connection 1");
2351 conIDs.push_back(conid);
2352 for (uint32 n = 1; n < CONNECTIONS; n++) {
2355 unittest::fail(
"NetworkManager delayed-connect test: could not add connection %u", n + 1);
2358 conIDs.push_back(conid);
2362 for (uint32 n = 0; n < CONNECTIONS; n++) {
2366 unittest::fail(
"NetworkManager delayed-connect test: greeting %u not received", n + 1);
2389 uint32 size = 70000;
2398 uint32 count = 100, subcount = 100, subcount2 = 10, n, m, k;
2401 PsyType type = { { 1,10001,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };
2416 for (
int tries = 0; tries < 20 && !listen; tries++) {
2421 unittest::fail(
"NetworkManager test: could not start listening on port 38100");
2431 dat =
new char[size];
2432 memset(dat, 0, size);
2434 msg->
setData(
"Test", dat, size);
2446 unittest::fail(
"NetworkManager test: message sent and received mismatch");
2465 unittest::fail(
"NetworkManager test: message sent and received mismatch");
2477 count = 10, subcount = 10, subcount2 = 15;
2478 for (m = 0; m < count; m++) {
2481 for (n = 0; n < subcount; n++) {
2482 type.levels[15] = n;
2485 for (k=0; k<subcount2; k++) {
2487 unittest::fail(
"NetworkManager test: could not send TCP message %u", n);
2497 for (k=0; k<subcount2; k++) {
2499 type.levels[15] = n;
2501 unittest::fail(
"NetworkManager test: [%u/%u/%u] no TCP message received", n, m, k);
2505 unittest::fail(
"NetworkManager test: [%u] TCP message sent and received mismatch", n);
2512 unittest::detail(
"[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2513 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2514 subcount*subcount2, subcount*subcount2 * size,
2515 ((
double)(end-start))/(subcount*subcount2));
2520 uint32 tcpMsgs = count*subcount*subcount2;
2521 double tcpUs = (double)t;
2522 unittest::detail(
"Total: Sent and received %u TCP msgs, %.3fus per msg", tcpMsgs, tcpUs/tcpMsgs);
2523 unittest::metric(
"tcp_msg_throughput", (
double)tcpMsgs / tcpUs * 1e6,
"msg/s",
true);
2525 unittest::metric(
"tcp_throughput", ((
double)tcpMsgs * size) / tcpUs,
"MB/s",
true);
2536 unittest::fail(
"NetworkManager test: could not start UDP listening on port 38101");
2548 dat =
new char[size];
2549 memset(dat, 0, size);
2551 msg->
setData(
"Test", dat, size);
2561 for (
int attempt = 0; attempt < 10 && !udpOk; attempt++) {
2563 unittest::fail(
"NetworkManager test: could not send UDP message");
2571 unittest::fail(
"NetworkManager test: UDP message sent and received mismatch");
2578 unittest::fail(
"NetworkManager test: no UDP round-trip on port 38101 after 10 attempts (loopback UDP may be blocked here)");
2591 count = 10, subcount = 10, subcount2 = 15;
2594 const int32 udpBudgetMs = 5000;
2595 for (m = 0; m < count &&
GetTimeAgeMS(udpLoopStart) < udpBudgetMs; m++) {
2598 for (n = 0; n < subcount; n++) {
2599 type.levels[15] = n;
2602 for (k=0; k<subcount2; k++) {
2604 unittest::fail(
"NetworkManager test: could not send UDP message %u", n);
2614 for (k=0; k<subcount2; k++) {
2626 unittest::detail(
"[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2627 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2628 subcount*subcount2, subcount*subcount2 * size,
2629 ((
double)(end-start))/(subcount*subcount2));
2636 uint32 udpMsgs = count*subcount*subcount2;
2637 double udpUs = (double)t;
2639 unittest::detail(
"Total UDP: Sent and received %u msgs, %.3fus per msg", udpMsgs, udpUs/udpMsgs);
2640 unittest::metric(
"udp_msg_throughput", (
double)udpMsgs / udpUs * 1e6,
"msg/s",
true);
2676 snprintf(text, 512,
"Hello World %llu",
GetTimeNow());
2678 uint64 localAddr = 0;
2683 printf(
"Error generating HTML page...\n\n");
2687 printf(
"Error sending response...\n\n");
2709 printf(
"WebsocketTestServer received HTTPRequest...\n\n");
2720 wsDataOut->
setData(wsData->
TEXT,
false, str.c_str(), str.length());
2774 uint64 httpStart, httpEnd;
2783 unittest::fail(
"NetworkManager HTTP test: could not start listening on port 38102");
2798 unittest::fail(
"NetworkManager HTTP test: could not create request");
2803 if (reply == NULL) {
2804 unittest::fail(
"NetworkManager HTTP test: did not receive reply");
2812 for (n=0; n<count; n++) {
2817 unittest::fail(
"NetworkManager HTTP test: could not reconnect [%u]", n);
2825 unittest::fail(
"NetworkManager HTTP test: could not create request [%u]", n);
2830 if (reply == NULL) {
2831 unittest::fail(
"NetworkManager HTTP test: did not receive reply [%u]", n);
2838 double httpUs = (double)(httpEnd - httpStart);
2840 unittest::metric(
"http_request_rate", (
double)count / httpUs * 1e6,
"req/s",
true);
2904static std::string sslTestTempPath(
const char* filename) {
2906 const char* t = getenv(
"TEMP");
2907 if (!t) t = getenv(
"TMP");
2909 return std::string(t) +
"\\" + filename;
2911 return std::string(
"/tmp/") + filename;
2915static bool generateSelfSignedCert(
const char* certPath,
const char* keyPath) {
2917 EVP_PKEY* pkey = EVP_RSA_gen(2048);
2920 X509* x509 = X509_new();
2922 ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
2923 X509_gmtime_adj(X509_getm_notBefore(x509), 0);
2924 X509_gmtime_adj(X509_getm_notAfter(x509), (
long)60 * 60 * 24 * 3650);
2925 X509_set_pubkey(x509, pkey);
2926 X509_NAME* name = X509_get_subject_name(x509);
2927 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
2928 (
const unsigned char*)
"localhost", -1, -1, 0);
2929 X509_set_issuer_name(x509, name);
2930 if (X509_sign(x509, pkey, EVP_sha256())) {
2931 FILE* kf = fopen(keyPath,
"wb");
2932 FILE* cf = fopen(certPath,
"wb");
2934 PEM_write_PrivateKey(kf, pkey, NULL, NULL, 0, NULL, NULL) == 1 &&
2935 PEM_write_X509(cf, x509) == 1)
2942 EVP_PKEY_free(pkey);
2950bool NetworkManager::UnitTestHTTPS() {
2958 uint64 httpsStart, httpsEnd;
2960 std::string certPathS = sslTestTempPath(
"psytest_https_cert.pem");
2961 std::string keyPathS = sslTestTempPath(
"psytest_https_key.pem");
2962 const char* certPath = certPathS.c_str();
2963 const char* keyPath = keyPathS.c_str();
2966 if (!generateSelfSignedCert(certPath, keyPath)) {
2967 unittest::fail(
"NetworkManager HTTPS test: could not generate self-signed certificate");
2971 HTTPTestServer* testServer =
new HTTPTestServer();
2973 manager->setSSLCertificate(certPath, keyPath);
2977 manager->setSSLAllowSelfSigned(
true);
2982 unittest::fail(
"NetworkManager HTTPS test: could not start SSL listener on port 38112");
2989 unittest::fail(
"NetworkManager HTTPS test: could not connect / TLS handshake failed");
2994 req =
new HTTPRequest((uint64)0);
2995 if (!req->createRequest(
HTTP_GET,
"",
"/", NULL, 0,
true, 0)) {
2997 unittest::fail(
"NetworkManager HTTPS test: could not create request");
3000 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3002 if (reply == NULL) {
3003 unittest::fail(
"NetworkManager HTTPS test: did not receive reply over TLS");
3011 for (n=0; n<count; n++) {
3013 con->endConnection(conid);
3016 unittest::fail(
"NetworkManager HTTPS test: could not reconnect over TLS [%u]", n);
3020 req =
new HTTPRequest((uint64)0);
3021 if (!req->createRequest(
HTTP_GET,
"",
"/", NULL, 0,
true, 0)) {
3023 unittest::fail(
"NetworkManager HTTPS test: could not create request [%u]", n);
3026 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3028 if (reply == NULL) {
3029 unittest::fail(
"NetworkManager HTTPS test: did not receive reply over TLS [%u]", n);
3036 double httpsUs = (double)(httpsEnd - httpsStart);
3037 unittest::detail(
"HTTPS: %u TLS requests in %.3fms", count, httpsUs / 1000.0);
3038 unittest::metric(
"https_request_rate", (
double)count / httpsUs * 1e6,
"req/s",
true);
3041 con->endConnection(conid);
3068bool NetworkManager::UnitTestSSLVerify() {
3070 std::string certPathS = sslTestTempPath(
"psytest_sslverify_cert.pem");
3071 std::string keyPathS = sslTestTempPath(
"psytest_sslverify_key.pem");
3072 const char* certPath = certPathS.c_str();
3073 const char* keyPath = keyPathS.c_str();
3074 uint64 conid = 0, location = 0;
3080 SSLConnection* c1 =
new SSLConnection();
3081 if (!c1->init() || (c1->getVerifyMode() != SSL_VERIFY_PEER)) {
3082 unittest::fail(
"default client ctx verify mode is not SSL_VERIFY_PEER (got %d)", c1->getVerifyMode());
3087 SSLConnection* c2 =
new SSLConnection();
3088 c2->setAllowSelfSigned(
true);
3089 if (!c2->init() || (c2->getVerifyMode() != SSL_VERIFY_NONE)) {
3090 unittest::fail(
"allowselfsigned client ctx verify mode is not SSL_VERIFY_NONE (got %d)", c2->getVerifyMode());
3095 unittest::detail(
"ctx verify modes correct (PEER by default, NONE when allowselfsigned)");
3099 if (!generateSelfSignedCert(certPath, keyPath)) {
3104 HTTPTestServer* testServer =
new HTTPTestServer();
3106 manager->setSSLCertificate(certPath, keyPath);
3118 con = manager->createTCPConnection(
"localhost", 38113,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3120 unittest::fail(
"connection to untrusted self-signed server was ACCEPTED with verification on");
3121 con->endConnection(conid);
3125 unittest::detail(
"verification on: TLS handshake to self-signed server correctly rejected");
3128 manager->setSSLAllowSelfSigned(
true);
3129 con = manager->createTCPConnection(
"localhost", 38113,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3131 unittest::fail(
"connection to self-signed server FAILED despite allowselfsigned=true");
3135 unittest::detail(
"allowselfsigned: TLS handshake to self-signed server accepted");
3136 con->endConnection(conid);
3153static EVP_PKEY* sslTestGenKey() {
3154 return EVP_RSA_gen(2048);
3157static bool sslTestWritePEM(X509* cert, EVP_PKEY* key,
const char* certPath,
const char* keyPath) {
3159 FILE* cf = fopen(certPath,
"wb");
3160 FILE* kf = keyPath ? fopen(keyPath,
"wb") : NULL;
3161 if (cf && PEM_write_X509(cf, cert) == 1)
3164 ok = (kf && PEM_write_PrivateKey(kf, key, NULL, NULL, 0, NULL, NULL) == 1);
3170static bool sslTestAddExt(X509* cert, X509* issuer,
int nid,
const char* value) {
3172 X509V3_set_ctx_nodb(&ctx);
3173 X509V3_set_ctx(&ctx, issuer, cert, NULL, NULL, 0);
3174 X509_EXTENSION* ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
3177 X509_add_ext(cert, ext, -1);
3178 X509_EXTENSION_free(ext);
3183static bool sslTestGenCA(
const char* caCertPath, EVP_PKEY** caKeyOut, X509** caCertOut) {
3184 EVP_PKEY* key = sslTestGenKey();
3187 X509* x = X509_new();
3188 if (!x) { EVP_PKEY_free(key);
return false; }
3189 X509_set_version(x, 2);
3190 ASN1_INTEGER_set(X509_get_serialNumber(x), 1000);
3191 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3192 X509_gmtime_adj(X509_getm_notAfter(x), (
long)60 * 60 * 24 * 365);
3193 X509_set_pubkey(x, key);
3194 X509_NAME* name = X509_get_subject_name(x);
3195 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
3196 (
const unsigned char*)
"Psyclone Test CA", -1, -1, 0);
3197 X509_set_issuer_name(x, name);
3198 bool ok = sslTestAddExt(x, x, NID_basic_constraints,
"critical,CA:TRUE") &&
3199 sslTestAddExt(x, x, NID_key_usage,
"critical,keyCertSign,cRLSign") &&
3200 (X509_sign(x, key, EVP_sha256()) != 0) &&
3201 sslTestWritePEM(x, NULL, caCertPath, NULL);
3202 if (!ok) { X509_free(x); EVP_PKEY_free(key);
return false; }
3209static bool sslTestGenLeaf(
const char* certPath,
const char* keyPath,
const char* cn,
3210 const char* san, X509* caCert, EVP_PKEY* caKey,
long serial) {
3211 EVP_PKEY* key = sslTestGenKey();
3214 X509* x = X509_new();
3215 if (!x) { EVP_PKEY_free(key);
return false; }
3216 X509_set_version(x, 2);
3217 ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
3218 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3219 X509_gmtime_adj(X509_getm_notAfter(x), (
long)60 * 60 * 24 * 365);
3220 X509_set_pubkey(x, key);
3221 X509_NAME* name = X509_get_subject_name(x);
3222 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
3223 (
const unsigned char*)cn, -1, -1, 0);
3224 X509_set_issuer_name(x, X509_get_subject_name(caCert));
3225 bool ok = sslTestAddExt(x, caCert, NID_basic_constraints,
"critical,CA:FALSE") &&
3226 sslTestAddExt(x, caCert, NID_subject_alt_name, san) &&
3227 (X509_sign(x, caKey, EVP_sha256()) != 0) &&
3228 sslTestWritePEM(x, key, certPath, keyPath);
3242bool NetworkManager::UnitTestSSLHostCA() {
3244 std::string caCertPathS = sslTestTempPath(
"psytest_sslhostca_ca.pem");
3245 std::string goodCertPathS = sslTestTempPath(
"psytest_sslhostca_good_cert.pem");
3246 std::string goodKeyPathS = sslTestTempPath(
"psytest_sslhostca_good_key.pem");
3247 std::string badCertPathS = sslTestTempPath(
"psytest_sslhostca_bad_cert.pem");
3248 std::string badKeyPathS = sslTestTempPath(
"psytest_sslhostca_bad_key.pem");
3249 const char* caCertPath = caCertPathS.c_str();
3250 const char* goodCertPath = goodCertPathS.c_str();
3251 const char* goodKeyPath = goodKeyPathS.c_str();
3252 const char* badCertPath = badCertPathS.c_str();
3253 const char* badKeyPath = badKeyPathS.c_str();
3254 EVP_PKEY* caKey = NULL;
3255 X509* caCert = NULL;
3256 uint64 conid = 0, location = 0;
3258 HTTPTestServer* testServer = NULL;
3265 if (!sslTestGenCA(caCertPath, &caKey, &caCert)) {
3269 if (!sslTestGenLeaf(goodCertPath, goodKeyPath,
"localhost",
"DNS:localhost", caCert, caKey, 1001) ||
3270 !sslTestGenLeaf(badCertPath, badKeyPath,
"wronghost.example",
"DNS:wronghost.example", caCert, caKey, 1002)) {
3271 unittest::fail(
"could not generate CA-signed server certificates");
3272 X509_free(caCert); EVP_PKEY_free(caKey);
3276 unittest::detail(
"CA + leaf certs generated (SAN localhost / wronghost.example)");
3278 testServer =
new HTTPTestServer();
3282 goodManager->setSSLCertificate(goodCertPath, goodKeyPath);
3291 unittest::progress(30,
"connecting with verify on, custom CA NOT provided (must be rejected)");
3292 con = goodManager->createTCPConnection(
"localhost", 38114,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3294 unittest::fail(
"CA-signed server was ACCEPTED although its CA was not provided");
3295 con->endConnection(conid);
3301 unittest::progress(45,
"connecting with custom CA provided + matching hostname (must be accepted)");
3302 goodManager->setSSLCALocation(caCertPath, NULL);
3303 con = goodManager->createTCPConnection(
"localhost", 38114,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3305 unittest::fail(
"connection FAILED despite custom CA provided and matching hostname");
3310 con->endConnection(conid);
3314 badManager->setSSLCertificate(badCertPath, badKeyPath);
3315 badManager->setSSLCALocation(caCertPath, NULL);
3324 unittest::progress(70,
"connecting by hostname to server with mismatching cert (must be rejected)");
3325 con = badManager->createTCPConnection(
"localhost", 38115,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3327 unittest::fail(
"server with cert for wronghost.example was ACCEPTED for hostname localhost");
3328 con->endConnection(conid);
3332 unittest::detail(
"hostname mismatch: trusted cert for wrong host correctly rejected");
3334 unittest::progress(85,
"connecting by IP literal (no hostname check; must be accepted)");
3335 con = badManager->createTCPConnection(
"127.0.0.1", 38115,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3337 unittest::fail(
"connection by IP literal FAILED (chain-of-trust only path broken)");
3341 unittest::detail(
"IP literal connect: chain-of-trust only, accepted (hostname check skipped)");
3342 con->endConnection(conid);
3346 delete(goodManager);
3351 EVP_PKEY_free(caKey);
3353 unlink(goodCertPath); unlink(goodKeyPath);
3354 unlink(badCertPath); unlink(badKeyPath);
3383 printf(
"Connecting...\n");
3386 printf(
"Could not connect to host %s:%u...\n\n", data->
host, data->
port);
3392 for (uint32 n=0; n<10; n++) {
3400 printf(
"Could not create request [%u]...\n\n", n);
3405 printf(
"[%u] Sending...\n", n);
3409 if (reply == NULL) {
3410 printf(
"Did not receive reply...\n\n");
3415 printf(
"Server unavailable...\n\n");
3420 printf(
"Server no reply...\n\n");
3425 printf(
"Server malformed reply...\n\n");
3457 unittest::fail(
"NetworkManager websocket test: could not start listening on port 38103");
3465 unittest::fail(
"NetworkManager websocket test: could not connect");
3473 unittest::fail(
"NetworkManager websocket test: could not create websocket upgrade request");
3478 if (reply == NULL) {
3479 unittest::fail(
"NetworkManager websocket test: did not receive handshake reply");
3484 unittest::fail(
"NetworkManager websocket test: reply was not a websocket upgrade");
3509 printf(
"Testing HTTP on %s:%u with random urls...\n\n", host, port);
3512 uint32 numThreads = 5;
3521 printf(
"Could not connect to host %s:%u...\n\n", host, port);
3526 printf(
"Host is available, starting test...\n\n");
3590 uint32 threadIDs[1000];
3592 for (n=0; n<loops; n++) {
3593 for (m=0; m<numThreads; m++) {
3594 testData[m].
host = host;
3595 testData[m].
port = port;
3596 testData[m].
urls = &urls;
3597 testData[m].
manager = manager;
3605 for (m=0; m<numThreads; m++) {
3608 if (testData[m].status > 10) {
3609 printf(
"[%u] Failed\n", m);
3613 printf(
"[%u] Done\n", m);
3616 if (m >= numThreads)
3628 "TCP and UDP message send/receive over loopback",
"network");
3630 "Delayed/greeting TCP connections to a loopback listener",
"network");
3632 "HTTP server/client request/reply over loopback",
"network");
3634 "Websocket upgrade handshake against loopback server",
"network");
3639 "HTTPS (TLS) server/client request/reply over loopback",
"network");
3641 "SSL client certificate verification (secure by default, allowselfsigned opt-out)",
"network");
3643 "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)
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.