CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
NetworkManager.cpp
Go to the documentation of this file.
1
7#include "NetworkManager.h"
8#include "UnitTestFramework.h"
9
10#ifdef _USE_SSL_
11 // For the HTTPS unit test: generate a throwaway self-signed cert at runtime
12 // (avoids embedding a private key in source, which secret scanners reject).
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>
18#endif // _USE_SSL_
19
20namespace cmlabs{
21
22
23
25// Network Manager
27
29 this->parent = parent;
30 this->id = id;
31
32 shouldContinue = true;
33 port = 0;
34 hWorker = 0;
35 reaping = false;
36 isRunning = false;
37 autoreconnect = false;
38 isAsync = true;
39 autoProtocols = 0;
42
43 con = NULL;
44 listener = NULL;
45 lastRequest = NULL;
46}
47
49 autoreconnect = false;
50 shouldContinue = false;
51 isRunning = false;
52
53 if (listener != NULL)
54 delete(listener);
55 listener = NULL;
56 if (con != NULL)
57 delete(con);
58 con = NULL;
59 if (lastRequest != NULL)
60 delete(lastRequest);
61 lastRequest = NULL;
62
63}
64
65
69
77
79
80 // SNAPSHOT the channels under mapMutex, then shut them down with the lock
81 // RELEASED. NetworkChannel::shutdown() joins its worker threads, and those
82 // workers call back into removeConnection()/getConnection() which need
83 // mapMutex - so holding it across shutdown() deadlocks against the very
84 // threads we are waiting for. Iterating `channels` directly is also unsafe:
85 // a worker exiting mid-loop mutates the map and invalidates the iterator.
86 std::vector<NetworkChannel*> stopChannels;
87 if (mapMutex.enter(2000, "NetworkManager::~NetworkManager")) {
88 // These two only alias channels, which own the objects - just drop them.
89 listeners.clear();
90 udpListeners.clear();
92 std::map<uint32, NetworkChannel*>::iterator it, itEnd;
93 for (it = channels.begin(), itEnd = channels.end(); it != itEnd; ++it) {
94 if (it->second != NULL)
95 stopChannels.push_back(it->second);
96 }
97 channels.clear();
98 mapMutex.leave();
99 }
100 for (size_t i = 0; i < stopChannels.size(); i++) {
101 stopChannels[i]->shutdown();
102 delete(stopChannels[i]);
103 }
104 if (udpOutputCon)
105 delete(udpOutputCon);
106 udpOutputCon = NULL;
107}
108
110 this->sslCertPath = sslCertPath;
111 this->sslKeyPath = sslKeyPath;
112 return true;
113}
114
115
116NetworkChannel* NetworkManager::createListener(uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 protocolTimeout, bool isDefaultProtocol, uint32 channelID, NetworkReceiver* recv) {
117 bool createdConnection = false;
118 // First check if the port is already in use
119 NetworkChannel* channel = getTCPConnectionByPort(port);
120 if (channel) {
121 channelID = channel->cid;
122 }
123 else {
124 if (channelID > 0)
125 channel = findChannelByID(channelID);
126 if (channel == NULL) {
127 channelID = allocateChannelID();
128 channel = new NetworkChannel(this);
129 channel->cid = channelID;
130 registerChannel(channelID, channel);
131 registerTCPListener(port, channel);
132 createdConnection = true;
133 }
134 }
135 // We have got a connection, check that we can bind
136 if (!channel->startListener(channelID, port, encryption, protocol, isAsync, protocolTimeout, isDefaultProtocol)) {
137 if (createdConnection) {
139 unregisterChannel(channelID);
140 delete(channel);
141 }
142 return NULL;
143 }
144
145 if (recv)
146 channel->setNewReceiver(recv);
147 return channel;
148}
149
150bool NetworkManager::stopListener(uint16 port, uint8 protocol) {
151 // Copy the pointer out and RELEASE before delegating: stopListener() joins the
152 // listener worker, and that worker's teardown needs mapMutex itself.
153 NetworkChannel* channel = getTCPConnectionByPort(port);
154 if (!channel)
155 return false;
156 bool res = channel->stopListener(port, protocol);
158 return res;
159}
160
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;
163 NetworkChannel* channel = NULL;
164 if (channelID > 0)
165 channel = findChannelByID(channelID);
166 if (channel == NULL) {
167 channelID = allocateChannelID();
168 channel = new NetworkChannel(this);
169 channel->cid = channelID;
170 registerChannel(channelID, channel);
171 createdConnection = true;
172 }
173
174 // We have got a connection, check that we can bind
175 if ( (conid = channel->createTCPConnection(addr, port, encryption, protocol, isAsync, autoreconnect, location, timeoutMS)) == 0) {
176 if (createdConnection) {
177 unregisterChannel(channelID);
178 delete(channel);
179 }
180 return NULL;
181 }
182 registerConnection(conid, channel);
183
184 if (recv)
185 channel->setNewReceiver(recv);
186
187 LogPrint(0,LOG_NETWORK,2,"New connection %llu to %s:%u created...",
188 conid, addr, port);
189
190 return channel;
191}
192
193NetworkChannel* NetworkManager::createTCPConnection(uint64 location, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint32 channelID, NetworkReceiver* recv, uint64& conid, uint32 timeoutMS) {
194 bool createdConnection = false;
195 NetworkChannel* channel = NULL;
196 if (channelID > 0)
197 channel = findChannelByID(channelID);
198 if (channel == NULL) {
199 // Try creating the connection first
200 TCPConnection* con = new TCPConnection();
201 if (!con->connect(location, timeoutMS, NULL)) {
202 delete(con);
203 return NULL;
204 }
205 channelID = allocateChannelID();
206 channel = new NetworkChannel(this);
207 channel->cid = channelID;
208 if ( (conid = channel->startConnection(con, protocol, isAsync, autoreconnect)) == 0) {
209 delete(channel);
210 return NULL;
211 }
212 registerChannel(channelID, channel);
213 createdConnection = true;
214 }
215 else {
216 // We have got a channel, check that we can connect
217 if ( (conid = channel->createTCPConnection(location, encryption, protocol, isAsync, autoreconnect, timeoutMS)) == 0) {
218 if (createdConnection) {
219 unregisterChannel(channelID);
220 delete(channel);
221 }
222 return NULL;
223 }
224 }
225 registerConnection(conid, channel);
226
227 if (recv)
228 channel->setNewReceiver(recv);
229
230 LogPrint(0,LOG_NETWORK,2,"New connection %llu to %u.%u.%u.%u:%u created...",
231 conid, GETIPADDRESSQUADPORT(location));
232
233 return channel;
234}
235
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;
238 NetworkChannel* channel = NULL;
239 if (channelID > 0)
240 channel = findChannelByID(channelID);
241 if (channel == NULL) {
242 channelID = allocateChannelID();
243 channel = new NetworkChannel(this);
244 channel->cid = channelID;
245 registerChannel(channelID, channel);
246 createdConnection = true;
247 }
248
249 // We have got a connection, check that we can bind
250 if ( (conid = channel->createTCPConnection(addresses, addressCount, port, encryption, protocol, isAsync, autoreconnect, location, timeoutMS)) == 0) {
251 if (createdConnection) {
252 unregisterChannel(channelID);
253 delete(channel);
254 }
255 return NULL;
256 }
257 registerConnection(conid, channel);
258
259 if (recv)
260 channel->setNewReceiver(recv);
261
262 LogPrint(0,LOG_NETWORK,2,"New connection %llu to %u.%u.%u.%u:%u created...",
263 conid, GETIPADDRESSQUADPORT(location));
264
265 return channel;
266}
267
268NetworkChannel* NetworkManager::createWebsocketConnection(const char* url, uint32 channelID, NetworkReceiver* recv, uint64& conid, const char* protocolName, const char* origin, uint32 timeoutMS) {
269 bool createdConnection = false;
270 NetworkChannel* channel = NULL;
271 if (channelID > 0)
272 channel = findChannelByID(channelID);
273 if (channel == NULL) {
274 channelID = allocateChannelID();
275 channel = new NetworkChannel(this);
276 channel->cid = channelID;
277 registerChannel(channelID, channel);
278 createdConnection = true;
279 }
280
281 // We have got a connection, check that we can bind
282 if ((conid = channel->createWebsocketConnection(url, protocolName, origin, timeoutMS)) == 0) {
283 if (createdConnection) {
284 unregisterChannel(channelID);
285 delete(channel);
286 }
287 return NULL;
288 }
289 registerConnection(conid, channel);
290
291 if (recv)
292 channel->setNewReceiver(recv);
293
294 LogPrint(0, LOG_NETWORK, 2, "New Websocket connection %llu to %s created...", conid, url);
295
296 return channel;
297}
298
299NetworkChannel* NetworkManager::createWebsocketConnection(const char* uri, const char* addr, uint16 port, uint8 encryption, uint32 channelID, NetworkReceiver* recv, uint64& conid, const char* protocolName, const char* origin, uint32 timeoutMS) {
300 bool createdConnection = false;
301 NetworkChannel* channel = NULL;
302 if (channelID > 0)
303 channel = findChannelByID(channelID);
304 if (channel == NULL) {
305 channelID = allocateChannelID();
306 channel = new NetworkChannel(this);
307 channel->cid = channelID;
308 registerChannel(channelID, channel);
309 createdConnection = true;
310 }
311
312 // We have got a connection, check that we can bind
313 if ((conid = channel->createWebsocketConnection(uri, addr, port, encryption, protocolName, origin, timeoutMS)) == 0) {
314 if (createdConnection) {
315 unregisterChannel(channelID);
316 delete(channel);
317 }
318 return NULL;
319 }
320 registerConnection(conid, channel);
321
322 if (recv)
323 channel->setNewReceiver(recv);
324
325 LogPrint(0, LOG_NETWORK, 2, "New Websocket connection %llu to %s created...", conid, uri);
326
327 return channel;
328}
329
330
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;
333 NetworkChannel* channel = NULL;
334 if (channelID > 0)
335 channel = findChannelByID(channelID);
336 if (channel == NULL) {
337 channelID = allocateChannelID();
338 channel = new NetworkChannel(this);
339 channel->cid = channelID;
340 registerChannel(channelID, channel);
341 createdConnection = true;
342 }
343
344 // We have got a connection, check that we can bind
345 if ((conid = channel->addTCPConnection(addr, port, encryption, protocol, isAsync, location, timeoutMS, greetingData, greetingSize)) == 0) {
346 if (createdConnection) {
347 unregisterChannel(channelID);
348 delete(channel);
349 }
350 return NULL;
351 }
352 registerConnection(conid, channel);
353
354 if (recv)
355 channel->setNewReceiver(recv);
356
357 LogPrint(0, LOG_NETWORK, 2, "New delayed connection %llu to %s:%u created...",
358 conid, addr, port);
359
360 return channel;
361}
362
363NetworkChannel* NetworkManager::addTCPConnection(uint64 location, uint8 encryption, uint8 protocol, bool isAsync, uint32 channelID, NetworkReceiver* recv, uint64& conid, uint32 timeoutMS, const char* greetingData, uint32 greetingSize) {
364 bool createdConnection = false;
365 NetworkChannel* channel = NULL;
366 if (channelID > 0)
367 channel = findChannelByID(channelID);
368 if (channel == NULL) {
369 channelID = allocateChannelID();
370 channel = new NetworkChannel(this);
371 channel->cid = channelID;
372 registerChannel(channelID, channel);
373 createdConnection = true;
374 }
375
376 // We have got a connection, check that we can bind
377 if ((conid = channel->addTCPConnection(location, encryption, protocol, isAsync, timeoutMS, greetingData, greetingSize)) == 0) {
378 if (createdConnection) {
379 unregisterChannel(channelID);
380 delete(channel);
381 }
382 return NULL;
383 }
384 registerConnection(conid, channel);
385
386 if (recv)
387 channel->setNewReceiver(recv);
388
389 LogPrint(0, LOG_NETWORK, 2, "New delayed connection %llu to %u.%u.%u.%u:%u created...",
390 conid, GETIPADDRESSQUADPORT(location));
391
392 return channel;
393}
394
395
396
397NetworkChannel* NetworkManager::createUDPConnection(uint16 port, uint8 protocol, bool isAsync, bool autoreconnect, uint32 channelID, NetworkReceiver* recv, uint64& conid) {
398 bool createdConnection = false;
399 NetworkChannel* channel = NULL;
400 if (channelID > 0)
401 channel = findChannelByID(channelID);
402 if (channel == NULL) {
403 channelID = allocateChannelID();
404 channel = new NetworkChannel(this);
405 channel->cid = channelID;
406 registerChannel(channelID, channel);
407 createdConnection = true;
408 }
409
410 // We have got a connection, check that we can bind
411 if ( (conid = channel->createUDPConnection(port, protocol, isAsync, autoreconnect)) == 0) {
412 if (createdConnection) {
413 unregisterChannel(channelID);
414 delete(channel);
415 }
416 return NULL;
417 }
418 registerUDPListener(port, channel);
419 registerConnection(conid, channel);
420
421 if (recv)
422 channel->setNewReceiver(recv);
423 return channel;
424}
425
427 // Called from CONNECTION WORKER threads on their way out (endConnection), so it
428 // races the owning thread's create*Connection()/lookups. Must hold mapMutex.
429 if (!mapMutex.enter(2000, "NetworkManager::removeConnection"))
430 return false;
431 channelsByConnection.erase(conid);
432 mapMutex.leave();
433 return true;
434}
435
437 // Copy the pointer out under the lock, then RELEASE before delegating: the
438 // channel's endConnection() joins the worker, and that worker itself calls
439 // removeConnection() which needs mapMutex - holding it here would deadlock
440 // against the very thread we wait for.
441 NetworkChannel* channel = findChannelByConnection(conid);
442 if (!channel)
443 return false;
444 return channel->endConnection(conid);
445}
446
448 // Release before delegating - see endConnection() above (that path joins too).
449 NetworkChannel* channel = getUDPConnectionByPort(port);
450 if (!channel)
451 return false;
452 return channel->endUDPConnection(port);
453}
454
456 if (!mapMutex.enter(2000, "NetworkManager::addConnection"))
457 return 0;
458 uint64 conid = ++lastConnectionID;
459 registerConnection(conid, channel);
460 mapMutex.leave();
461 return conid;
462}
463
465 NetworkChannel* channel = findChannelByConnection(conid);
466 if (!channel)
467 return 0;
468 return channel->getConnectionType(conid);
469}
470
472 NetworkChannel* channel = findChannelByConnection(conid);
473 if (!channel)
474 return 0;
475 return channel->getRemoteAddress(conid);
476}
477
478// ---- Guarded map mutators -------------------------------------------------
479// Every insert/erase on the four manager maps goes through these, so the locking
480// cannot be forgotten in one of the eight near-identical create*Connection()
481// factories. See NetworkManager::mapMutex for why this is required.
482
483void NetworkManager::registerChannel(uint32 channelID, NetworkChannel* channel) {
484 if (!mapMutex.enter(2000, "NetworkManager::registerChannel"))
485 return;
486 channels[channelID] = channel;
487 mapMutex.leave();
488}
489
490void NetworkManager::unregisterChannel(uint32 channelID) {
491 if (!mapMutex.enter(2000, "NetworkManager::unregisterChannel"))
492 return;
493 channels.erase(channelID);
494 mapMutex.leave();
495}
496
498 if (!mapMutex.enter(2000, "NetworkManager::registerConnection"))
499 return;
500 channelsByConnection[conid] = channel;
501 mapMutex.leave();
502}
503
505 if (!mapMutex.enter(2000, "NetworkManager::registerTCPListener"))
506 return;
507 listeners[port] = channel;
508 mapMutex.leave();
509}
510
512 if (!mapMutex.enter(2000, "NetworkManager::unregisterTCPListener"))
513 return;
514 listeners.erase(port);
515 mapMutex.leave();
516}
517
519 if (!mapMutex.enter(2000, "NetworkManager::registerUDPListener"))
520 return;
521 udpListeners[port] = channel;
522 mapMutex.leave();
523}
524
525// Allocate an unused channel id. The ++lastChannelID / "is it taken?" probe must be
526// ONE atomic step: done unlocked, two threads creating channels concurrently can both
527// read the same lastChannelID and both conclude the id is free.
529 if (!mapMutex.enter(2000, "NetworkManager::allocateChannelID"))
530 return 0;
531 uint32 channelID = ++lastChannelID;
532 while (channels.find(channelID) != channels.end())
533 channelID = ++lastChannelID;
534 mapMutex.leave();
535 return channelID;
536}
537
538// Look up the channel owning a connection id, or NULL. Uses find(), NOT operator[]:
539// operator[] INSERTS a NULL entry for a missing key, so a mere lookup mutated the
540// tree - which is both a leak of junk entries and half of the reason a "read" from
541// one thread could corrupt a map being erased by another.
543 if (!mapMutex.enter(2000, "NetworkManager::findChannelByConnection"))
544 return NULL;
545 std::map<uint64, NetworkChannel*>::iterator it = channelsByConnection.find(conid);
546 NetworkChannel* channel = (it != channelsByConnection.end()) ? it->second : NULL;
547 mapMutex.leave();
548 return channel;
549}
550
551// Look up a channel by CHANNEL id (the `channels` map), or NULL. Distinct from
552// findChannelByConnection(): connection ids and channel ids are different id spaces.
554 if (!mapMutex.enter(2000, "NetworkManager::findChannelByID"))
555 return NULL;
556 std::map<uint32, NetworkChannel*>::iterator it = channels.find(channelID);
557 NetworkChannel* channel = (it != channels.end()) ? it->second : NULL;
558 mapMutex.leave();
559 return channel;
560}
561
565
567 if (!mapMutex.enter(2000, "NetworkManager::getTCPConnectionByPort"))
568 return NULL;
569 std::map<uint16, NetworkChannel*>::iterator it = listeners.find(port);
570 NetworkChannel* channel = (it != listeners.end()) ? it->second : NULL;
571 mapMutex.leave();
572 return channel;
573}
574
576 if (!mapMutex.enter(2000, "NetworkManager::getUDPConnectionByPort"))
577 return NULL;
578 std::map<uint16, NetworkChannel*>::iterator it = udpListeners.find(port);
579 NetworkChannel* channel = (it != udpListeners.end()) ? it->second : NULL;
580 mapMutex.leave();
581 return channel;
582}
583
584bool NetworkManager::sendUDPMessage(DataMessage* msg, uint64 destination) {
585 if (!udpOutputConMutex.enter(3000, __FUNCTION__))
586 return false;
587
588 if (!udpOutputCon) {
590 if (!udpOutputCon->initForOutputOnly()) {
591 delete(udpOutputCon);
592 udpOutputCon = NULL;
593 udpOutputConMutex.leave();
594 return false;
595 }
596 }
597
598 bool res = MessageProtocol::SendMessage(udpOutputCon, msg, destination);
599 udpOutputConMutex.leave();
600 return res;
601}
602
603
604HTTPReply* NetworkManager::makeHTTPRequest(const char* url, uint32 timeout, const char* content, uint32 contentSize) {
605 // http://localhost:8000/getstatus.php?count=10
606
607 std::string protocolString = html::GetProtocolFromURL(url);
608 int8 encryption = NOENC;
609 if (protocolString == "http") {}
610 else if (protocolString == "https")
611 encryption = SSLENC;
612 else
614
615 std::string hostString = html::GetHostFromURL(url);
616 if (!hostString.size())
618
619 uint16 port = html::GetPortFromURL(url);
620 if (!port) {
621 if (encryption == SSLENC)
622 port = 443;
623 else
624 port = 80;
625 }
626
627 std::string uriString = html::GetURIFromURL(url);
628 if (!uriString.size())
629 uriString = "/";
630
631 HTTPRequest* req = new HTTPRequest();
632 if (content && contentSize)
633 req->createRequest(HTTP_POST, "", uriString.c_str(), content, contentSize, false, 0);
634 else
635 req->createRequest(HTTP_GET, "", uriString.c_str(), NULL, 0, false, 0);
636 HTTPReply* reply = makeHTTPRequest(req, hostString.c_str(), port, encryption, timeout);
637 delete(req);
638 return reply;
639}
640
641HTTPReply* NetworkManager::makeHTTPRequest(HTTPRequest* req, const char* addr, uint16 port, uint8 encryption, uint32 timeout) {
642
643 HTTPReply* reply = NULL;
644
645 uint64 conid, location;
646 NetworkChannel* channel;
647
648 if (encryption == SSLENC)
649 channel = createTCPConnection(addr, port, SSLENC, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
650 else
651 channel = createTCPConnection(addr, port, NOENC, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
652
653 //uint32 contentSize = 0;
654 //utils::WriteAFile("d:/request2.dat", req->getRawContent(contentSize), contentSize, true);
655
656 if (!channel)
658
659 reply = channel->sendReceiveHTTPRequest(req, conid, timeout);
660 channel->endConnection(conid);
661 return reply;
662}
663
664HTTPReply* NetworkManager::makeHTTPRequest(uint8 ops, std::string url, uint32 timeout,
665 std::map<std::string, std::string>& headerEntries, const char* content, const char* contentType, uint32 contentSize,
666 bool keepAlive, uint64 ifModifiedSince) {
667
668 if (!url.length())
670
671 uint64 conid, location;
672 NetworkChannel* channel;
673
674 std::string host = html::GetHostFromURL(url);
675 std::string protocol = html::GetProtocolFromURL(url);
676 uint16 port = html::GetPortFromURL(url);
677
678 if (!host.length() || !protocol.length())
680
681 int8 encryption = NOENC;
682 if (stricmp(protocol.c_str(), "https") == 0) {
683 encryption = SSLENC;
684 if (!port)
685 port = 443;
686 }
687 else if (!port)
688 port = 80;
689
690 HTTPRequest* req = new HTTPRequest();
691 HTTPReply* reply = NULL;
692
693 if (contentSize) {
694 req->createRequest(ops, host.c_str(), html::GetURIFromURL(url).c_str(), headerEntries, content, contentType, contentSize, keepAlive, ifModifiedSince);
695 }
696 else {
697 req->createRequest(ops, host.c_str(), html::GetURIFromURL(url).c_str(), headerEntries, keepAlive, ifModifiedSince);
698 }
699
700 //uint32 contentSize = 0;
701 //utils::WriteAFile("d:/request2.dat", req->getRawContent(contentSize), contentSize, true);
702
703 channel = createTCPConnection(host.c_str(), port, encryption, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
704
705 if (!channel) {
706 delete(req);
708 }
709
710 reply = channel->sendReceiveHTTPRequest(req, conid, timeout);
711 channel->endConnection(conid);
712 delete(req);
713 return reply;
714}
715
716
717HTTPReply* NetworkManager::makeHTTPRequest(uint8 ops, std::string url, uint32 timeout,
718 std::map<std::string, std::string>& headerEntries, std::map<std::string, HTTPPostEntry*>& bodyEntries,
719 bool keepAlive, uint64 ifModifiedSince) {
720
721 if (!url.length())
723
724 uint64 conid, location;
725 NetworkChannel* channel;
726
727 std::string host = html::GetHostFromURL(url);
728 std::string protocol = html::GetProtocolFromURL(url);
729 uint16 port = html::GetPortFromURL(url);
730
731 if (!host.length() || !protocol.length())
733
734 int8 encryption = NOENC;
735 if (stricmp(protocol.c_str(), "https") == 0) {
736 encryption = SSLENC;
737 if (!port)
738 port = 443;
739 }
740 else if (!port)
741 port = 80;
742
743 HTTPRequest* req = new HTTPRequest();
744 HTTPReply* reply = NULL;
745
746 if (bodyEntries.size() > 1) {
747 req->createMultipartRequest(ops, host.c_str(), html::GetURIFromURL(url).c_str(), headerEntries, bodyEntries, keepAlive, ifModifiedSince);
748 }
749 else if (bodyEntries.size() == 1) {
750 req->createRequest(ops, host.c_str(), html::GetURIFromURL(url).c_str(), headerEntries, bodyEntries.at(0), keepAlive, ifModifiedSince);
751 }
752 else {
753 req->createRequest(ops, host.c_str(), html::GetURIFromURL(url).c_str(), headerEntries, keepAlive, ifModifiedSince);
754 }
755
756 //uint32 contentSize = 0;
757 //utils::WriteAFile("d:/request2.dat", req->getRawContent(contentSize), contentSize, true);
758
759 channel = createTCPConnection(host.c_str(), port, encryption, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
760
761 if (!channel) {
762 delete(req);
764 }
765
766 reply = channel->sendReceiveHTTPRequest(req, conid, timeout);
767 channel->endConnection(conid);
768 delete(req);
769 return reply;
770}
771
772
773
774
776// Connections
778
780 receiver = NULL;
781 this->manager = manager;
783 sslCASet = false;
784}
785
786// Effective SSL client verification policy for connections made through this
787// channel: per-channel (e.g. per-interface) setting wins over the manager
788// setting, which wins over the process-wide default. Values: -1 inherit,
789// 0 verify peer, 1 allow self-signed/untrusted.
791 if (!con)
792 return;
793 if (sslAllowSelfSigned >= 0)
795 else if (manager && (manager->sslAllowSelfSigned >= 0))
796 con->setAllowSelfSigned(manager->sslAllowSelfSigned != 0);
797 // else: keep the process default the connection was constructed with
798
799 // Custom CA location: per-channel setting wins over the manager setting,
800 // which wins over the process-wide default the connection inherited
801 if (sslCASet)
802 con->setCALocation(sslCAFile.c_str(), sslCAPath.c_str());
803 else if (manager && manager->sslCASet)
804 con->setCALocation(manager->sslCAFile.c_str(), manager->sslCAPath.c_str());
805}
806
811
812bool NetworkChannel::isConnected(uint64 conid) {
813 NetworkThread* thread = connectionThreads[conid];
814 if (thread == NULL)
815 return false;
816 bool res = ( thread->con && thread->con->isConnected() );
817 return res;
818}
819
821 NetworkThread* thread;
822 std::map<uint16, NetworkThread*>::iterator it, itEnd;
823
824 // Detach every thread from the maps under the lock and signal it to stop,
825 // but do NOT wait for / terminate it while holding channelMutex: a
826 // connection worker calls endConnection() on its way out, which also takes
827 // channelMutex, so waiting here under the lock deadlocks against it.
828 std::vector<NetworkThread*> stopListeners;
829 std::vector<NetworkThread*> stopConnections;
830
831 // Use a generous bounded wait here: a worker in endConnection() can hold
832 // channelMutex for over a second while waiting out its thread. If the two
833 // paths ever proceed concurrently they both delete the same NetworkThread,
834 // double-destroying its Mutex/Semaphore members and double-closing their
835 // kernel handles - which can invalidate (or recycle) handles owned by
836 // OTHER live objects and hang teardown forever.
837 if (!channelMutex.enter(10000, "NetworkChannel::shutdown")) {
838 // Never proceed without the lock (see comment above): an unsynchronised
839 // shutdown racing endConnection() deletes the same NetworkThread twice.
840 LogPrint(0, LOG_NETWORK, 1, "NetworkChannel::shutdown could not acquire channelMutex within 10s; aborting shutdown to avoid double-delete...");
841 return false;
842 }
843 for (it = listeners.begin(), itEnd = listeners.end(); it != itEnd; ++it) {
844 if ( (thread = it->second) != NULL) {
845 thread->shouldContinue = false;
846 thread->reaping = true; // CLAIM under the lock: we own these frees
847 stopListeners.push_back(thread);
848 }
849 }
850 listeners.clear();
851
852 std::map<uint64, NetworkThread*>::iterator it2, it2End;
853 for (it2 = connectionThreads.begin(), it2End = connectionThreads.end(); it2 != it2End; ++it2) {
854 if ( (thread = it2->second) != NULL) {
855 thread->autoreconnect = false;
856 thread->shouldContinue = false;
857 thread->reaping = true; // CLAIM under the lock: we own these frees
858 stopConnections.push_back(thread);
859 }
860 }
861 connectionThreads.clear();
862 channelMutex.leave();
863
864 // Now wait for / terminate the threads with channelMutex released, so a
865 // worker exiting via endConnection() can acquire it and finish cleanly.
866 for (size_t i = 0; i < stopListeners.size(); i++) {
867 thread = stopListeners[i];
868 // Cooperative-only shutdown: the listener loop polls shouldContinue on a
869 // short accept timeout, so a healthy thread always exits by itself well
870 // within this window. Force-terminate is a LAST RESORT (see below).
871 for (int waited = 0; thread->isRunning && waited < 3000; waited += 5)
872 utils::Sleep(5);
873 if (thread->isRunning) {
874 // Should be unreachable: cancelling a worker can kill it while it
875 // holds a non-cancel-safe lock (use-after-free on delete). Kept only
876 // so teardown can never hang forever; if this fires it is a defect.
877 LogPrint(0, LOG_NETWORK, 1, "NetworkChannel::shutdown FORCE-terminating listener thread %u after 3s cooperative wait - this should never happen, investigate", thread->threadID);
879 thread->isRunning = false;
880 }
881 // isRunning is NOT proof the worker has left: false means EITHER "not started
882 // yet" OR "already leaving", and the startup case can still dereference this
883 // object (HTTPClientRun reads thread->con as its first statement). JOIN the
884 // real OS thread - unconditionally - before freeing. channelMutex is already
885 // released here, which is required: the exiting worker needs it itself.
887 delete(thread);
888 }
889 for (size_t i = 0; i < stopConnections.size(); i++) {
890 thread = stopConnections[i];
891 // Cooperative-only shutdown: connection workers poll shouldContinue on
892 // short (<=500ms) bounded receive timeouts, so a healthy worker always
893 // exits by itself well within this window (5s allows a worker that is
894 // mid-body on a slow read to finish or time out). Never pthread_cancel
895 // a worker that may hold con->mutex: the cancel can land inside
896 // SSLConnection::receiveAvailable with the (non-cancel-safe) mutex held
897 // and the subsequent delete(con) frees it under the worker's trailing
898 // mutex.leave() -> use-after-free SIGSEGV (observed on macOS).
899 for (int waited = 0; thread->isRunning && waited < 5000; waited += 5)
900 utils::Sleep(5);
901 if (thread->isRunning) {
902 // LAST RESORT, should be unreachable; kept only so teardown can
903 // never hang forever. If this fires it is a defect - investigate.
904 LogPrint(0, LOG_NETWORK, 1, "NetworkChannel::shutdown FORCE-terminating connection thread %u after 5s cooperative wait - this should never happen, investigate", thread->threadID);
905 utils::Sleep(50);
907 utils::Sleep(50);
908 thread->isRunning = false;
909 }
910 // isRunning is NOT proof the worker has left: false means EITHER "not started
911 // yet" OR "already leaving", and the startup case can still dereference this
912 // object (HTTPClientRun reads thread->con as its first statement). JOIN the
913 // real OS thread - unconditionally - before freeing. channelMutex is already
914 // released here, which is required: the exiting worker needs it itself.
916 delete(thread);
917 }
918
919 // Teardown drains use bounded waits: after the thread-teardown storm above
920 // a mutex handle may have been invalidated/recycled (see comment above), in
921 // which case an INFINITE WaitForSingleObject can block on the wrong kernel
922 // object forever. A bounded wait guarantees shutdown always completes.
923 queueHTTPRequestsMutex.enter(5000, "NetworkChannel::shutdown queueHTTPRequests");
924 while (!queueHTTPRequests.empty()) {
925 delete(queueHTTPRequests.front());
926 queueHTTPRequests.pop();
927 }
929
930 queueHTTPRepliesMutex.enter(5000, "NetworkChannel::shutdown queueHTTPReplies");
931 while (!queueHTTPReplies.empty()) {
932 delete(queueHTTPReplies.front());
933 queueHTTPReplies.pop();
934 }
935 queueHTTPRepliesMutex.leave();
936
937 queueMessagesMutex.enter(5000, "NetworkChannel::shutdown queueMessages");
938 while (!queueMessages.empty()) {
939 delete(queueMessages.front());
940 queueMessages.pop();
941 }
942 while (!queueMessageConIDs.empty())
943 queueMessageConIDs.pop();
944 queueMessagesMutex.leave();
945
946 queueTelnetLinesMutex.enter(5000, "NetworkChannel::shutdown queueTelnetLines");
947 while (!queueTelnetLines.empty()) {
948 delete(queueTelnetLines.front());
949 queueTelnetLines.pop();
950 }
951 queueTelnetLinesMutex.leave();
952
953 eventQueueMutex.enter(5000, "NetworkChannel::shutdown eventQueue");
954 while (!eventQueue.empty()) {
955 delete(eventQueue.front());
956 eventQueue.pop();
957 }
958 eventQueueMutex.leave();
959
960 queueWebsocketDataMutex.enter(5000, "NetworkChannel::shutdown queueWebsocketData");
961 while (!queueWebsocketData.empty()) {
962 delete(queueWebsocketData.front());
963 queueWebsocketData.pop();
964 }
966
967 return true;
968}
969
970bool NetworkChannel::startListener(uint64 cid, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 protocolTimeout, bool isDefaultProtocol) {
971 channelMutex.enter(1000);
972 NetworkThread* thread = listeners[port];
973 bool created = false;
974 if (thread == NULL) {
975 uint64 conid = cid;
976 if (!conid)
977 conid = manager->addConnection(this);
978 thread = new NetworkThread(this, conid);
979 created = true;
980 // Try binding to the port
981 thread->port = port;
982 thread->listener = new TCPListener();
983 if (manager->sslCertPath.size())
984 thread->listener->setSSLCertificate(manager->sslCertPath.c_str(), manager->sslKeyPath.c_str());
985 thread->isAsync = isAsync;
986 if (!thread->listener->init(port, encryption)) {
987 if (created)
988 delete(thread);
989 channelMutex.leave();
990 return false;
991 }
992
993 if (isDefaultProtocol)
994 thread->defaultProtocol = protocol;
995 thread->autoProtocolTimeout = protocolTimeout;
996
997 if ( isDefaultProtocol && (protocolTimeout == 0) )
998 thread->autoProtocols = 0;
999 else
1000 thread->autoProtocols |= protocol;
1001
1002 // Start the thread
1004 if (created)
1005 delete(thread);
1006 channelMutex.leave();
1007 return false;
1008 }
1009 if (!ThreadManager::GetThreadHandle(thread->threadID, thread->hWorker))
1010 LogPrint(0, LOG_NETWORK, 1, "Could not fetch ThreadHandle for listener thread %u...", thread->threadID);
1011
1012 listeners[port] = thread;
1013 }
1014 else {
1015 // Already running, just add the protocol
1016 thread->isAsync = isAsync;
1017 if (isDefaultProtocol)
1018 thread->defaultProtocol = protocol;
1019 thread->autoProtocolTimeout = protocolTimeout;
1020
1021 if ( isDefaultProtocol && (protocolTimeout == 0) )
1022 thread->autoProtocols = 0;
1023 else
1024 thread->autoProtocols |= protocol;
1025 }
1026
1027 channelMutex.leave();
1028 return true;
1029}
1030
1031bool NetworkChannel::stopListener(uint16 port, uint8 protocol) {
1032 channelMutex.enter(1000);
1033 std::map<uint16, NetworkThread*>::iterator lisIt = listeners.find(port);
1034 NetworkThread* thread = (lisIt != listeners.end()) ? lisIt->second : NULL;
1035 if (thread == NULL) {
1036 channelMutex.leave();
1037 return true;
1038 }
1039 thread->autoProtocols &= ~protocol;
1040 if (thread->defaultProtocol == protocol)
1041 thread->defaultProtocol = 0;
1042 if ((thread->autoProtocols == 0) && (thread->defaultProtocol == 0)) {
1043 if (thread->isRunning) {
1044 thread->shouldContinue = false;
1045 // Cooperative-only shutdown; force-terminate is a last resort that
1046 // should be unreachable (cancelling a lock holder -> UAF, see shutdown()).
1047 for (int waited = 0; thread->isRunning && waited < 3000; waited += 5)
1048 utils::Sleep(5);
1049 if (thread->isRunning) {
1050 LogPrint(0, LOG_NETWORK, 1, "NetworkChannel::stopListener FORCE-terminating listener thread %u after 3s cooperative wait - this should never happen, investigate", thread->threadID);
1052 thread->isRunning = false;
1053 }
1054 }
1055 listeners.erase(port);
1056 uint32 workerTMID = thread->threadID;
1057 thread->reaping = true; // CLAIM: this caller solely owns the free
1058 // JOIN with channelMutex RELEASED and BEFORE the free (the exiting worker
1059 // needs channelMutex itself), then free WITHOUT re-entering it - the claim
1060 // flag already gives exclusive ownership, and re-acquiring risks entering a
1061 // mutex inside a channel another thread has freed meanwhile.
1062 channelMutex.leave();
1063 ThreadManager::JoinThread(workerTMID);
1064 delete(thread);
1065 return true;
1066 }
1067 channelMutex.leave();
1068 return true;
1069}
1070
1071uint64 NetworkChannel::createTCPConnection(const char* addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint64& location, uint32 timeoutMS) {
1072 NetworkConnection* con = NULL;
1073 if (encryption == NOENC) {
1074 TCPConnection* tcpCon = new TCPConnection();
1075 if (!tcpCon->connect(addr, port, location, timeoutMS, NULL)) {
1076 delete(tcpCon);
1077 return false;
1078 }
1079 con = tcpCon;
1080 }
1081 else if (encryption == SSLENC) {
1082 SSLConnection* sslCon = new SSLConnection();
1083 applySSLClientPolicy(sslCon);
1084 if (!sslCon->init()) {
1085 delete(sslCon);
1086 return false;
1087 }
1088 if (!sslCon->connect(addr, port, location, timeoutMS, NULL)) {
1089 delete(sslCon);
1090 return false;
1091 }
1092 con = sslCon;
1093 }
1094 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
1095}
1096
1097uint64 NetworkChannel::createTCPConnection(uint64 location, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint32 timeoutMS) {
1098 NetworkConnection* con = NULL;
1099 if (encryption == NOENC) {
1100 TCPConnection* tcpCon = new TCPConnection();
1101 if (!tcpCon->connect(location, timeoutMS, NULL)) {
1102 delete(tcpCon);
1103 return false;
1104 }
1105 con = tcpCon;
1106 }
1107 else if (encryption == SSLENC) {
1108 SSLConnection* sslCon = new SSLConnection();
1109 applySSLClientPolicy(sslCon);
1110 if (!sslCon->connect(location, timeoutMS, NULL)) {
1111 delete(sslCon);
1112 return false;
1113 }
1114 con = sslCon;
1115 }
1116 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
1117}
1118
1119uint64 NetworkChannel::createTCPConnection(const uint32* addresses, uint16 addressCount, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint64& location, uint32 timeoutMS) {
1120 NetworkConnection* con = NULL;
1121 if (encryption == NOENC) {
1122 TCPConnection* tcpCon = new TCPConnection();
1123 if (!tcpCon->connect(addresses, addressCount, port, location, timeoutMS, NULL)) {
1124 delete(tcpCon);
1125 return false;
1126 }
1127 con = tcpCon;
1128 }
1129 else if (encryption == SSLENC) {
1130 SSLConnection* sslCon = new SSLConnection();
1131 applySSLClientPolicy(sslCon);
1132 if (!sslCon->connect(addresses, addressCount, port, location, timeoutMS, NULL)) {
1133 delete(sslCon);
1134 return false;
1135 }
1136 con = sslCon;
1137 }
1138 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
1139}
1140
1141uint64 NetworkChannel::addTCPConnection(const char* addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint64& location, uint32 timeoutMS, const char* greetingData, uint32 greetingSize) {
1142 NetworkConnection* con = NULL;
1143 if (encryption == NOENC) {
1144 TCPConnection* tcpCon = new TCPConnection();
1145 if (greetingData && greetingSize)
1146 tcpCon->setGreetingData(greetingData, greetingSize);
1147 if (!tcpCon->delayedConnect(addr, port, location, timeoutMS, NULL)) {
1148 delete(tcpCon);
1149 return false;
1150 }
1151 con = tcpCon;
1152 }
1153 else if (encryption == SSLENC) {
1154 SSLConnection* sslCon = new SSLConnection();
1155 applySSLClientPolicy(sslCon);
1156 if (!sslCon->init()) {
1157 delete(sslCon);
1158 return false;
1159 }
1160 if (greetingData && greetingSize)
1161 sslCon->setGreetingData(greetingData, greetingSize);
1162 if (!sslCon->delayedConnect(addr, port, location, timeoutMS, NULL)) {
1163 delete(sslCon);
1164 return false;
1165 }
1166 con = sslCon;
1167 }
1168 return startConnection(con, protocol, isAsync, true, timeoutMS);
1169}
1170
1171uint64 NetworkChannel::addTCPConnection(uint64 location, uint8 encryption, uint8 protocol, bool isAsync, uint32 timeoutMS, const char* greetingData, uint32 greetingSize) {
1172 NetworkConnection* con = NULL;
1173 if (encryption == NOENC) {
1174 TCPConnection* tcpCon = new TCPConnection();
1175 if (greetingData && greetingSize)
1176 tcpCon->setGreetingData(greetingData, greetingSize);
1177 if (!tcpCon->delayedConnect(location, timeoutMS, NULL)) {
1178 delete(tcpCon);
1179 return false;
1180 }
1181 con = tcpCon;
1182 }
1183 else if (encryption == SSLENC) {
1184 SSLConnection* sslCon = new SSLConnection();
1185 applySSLClientPolicy(sslCon);
1186 if (!sslCon->init()) {
1187 delete(sslCon);
1188 return false;
1189 }
1190 if (greetingData && greetingSize)
1191 sslCon->setGreetingData(greetingData, greetingSize);
1192 if (!sslCon->delayedConnect(location, timeoutMS, NULL)) {
1193 delete(sslCon);
1194 return false;
1195 }
1196 con = sslCon;
1197 }
1198 return startConnection(con, protocol, isAsync, true, timeoutMS);
1199}
1200
1201
1202uint64 NetworkChannel::createWebsocketConnection(const char* url, const char* protocolName, const char* origin, uint32 timeoutMS) {
1203
1204 // http://localhost:8000/mypage
1205
1206 std::string protocolString = html::GetProtocolFromURL(url);
1207 int8 encryption = NOENC;
1208 if (protocolString == "http") {}
1209 else if (protocolString == "https")
1210 encryption = SSLENC;
1211 else
1212 return 0;
1213
1214 std::string hostString = html::GetHostFromURL(url);
1215 if (!hostString.size())
1216 return 0;
1217
1218 uint16 port = html::GetPortFromURL(url);
1219 if (!port) {
1220 if (encryption == SSLENC)
1221 port = 443;
1222 else
1223 port = 80;
1224 }
1225
1226 std::string uriString = html::GetURIFromURL(url);
1227 if (!uriString.size())
1228 uriString = "/";
1229
1230 return createWebsocketConnection(uriString.c_str(), hostString.c_str(), port, encryption, protocolName, origin, timeoutMS);
1231}
1232
1233uint64 NetworkChannel::createWebsocketConnection(const char* uri, const char* addr, uint16 port, uint8 encryption, const char* protocolName, const char* origin, uint32 timeoutMS) {
1234 uint64 conID = 0;
1235 uint64 location;
1236 NetworkConnection* con = NULL;
1237 if (encryption == NOENC) {
1238 TCPConnection* tcpCon = new TCPConnection();
1239 if (!tcpCon->connect(addr, port, location, timeoutMS, NULL)) {
1240 delete(tcpCon);
1241 return 0;
1242 }
1243 con = tcpCon;
1244 }
1245 else if (encryption == SSLENC) {
1246 SSLConnection* sslCon = new SSLConnection();
1247 applySSLClientPolicy(sslCon);
1248 if (!sslCon->connect(addr, port, location, timeoutMS, NULL)) {
1249 delete(sslCon);
1250 return 0;
1251 }
1252 con = sslCon;
1253 }
1254 conID = startConnection(con, PROTOCOL_HTTP_CLIENT, true, true, timeoutMS);
1255 if (!conID)
1256 return 0;
1257
1258 HTTPReply* reply = NULL;
1259 HTTPRequest* req = HTTPRequest::CreateWebsocketRequest(uri, addr, protocolName, origin);
1260
1261 if (!HTTPProtocol::SendHTTPRequest(con, req)) {
1262 delete req;
1263 endConnection(conID);
1264 return 0;
1265 }
1266 delete req;
1267
1268 reply = waitForHTTPReply(conID, timeoutMS);
1269 //reply = HTTPProtocol::ReceiveHTTPReply(con, timeoutMS);
1270 if (!reply || (reply->type != HTTP_SWITCH_PROTOCOL)) {
1271 delete reply;
1272 endConnection(conID);
1273 return 0;
1274 }
1275 delete reply;
1276 return conID;
1277}
1278
1279
1280uint64 NetworkChannel::createUDPConnection(uint16 port, uint8 protocol, bool isAsync, bool autoreconnect) {
1281 UDPConnection* con = new UDPConnection();
1282 if (!con->connect(port)) {
1283 delete(con);
1284 return false;
1285 }
1286 uint64 conid = startConnection(con, protocol, isAsync, autoreconnect);
1287 if (!conid)
1288 return false;
1289
1290 channelMutex.enter(1000);
1291 NetworkThread* thread = connectionThreads[conid];
1292 if (thread == NULL) {
1293 channelMutex.leave();
1294 return false;
1295 }
1296 udpListeners[port] = thread;
1297 channelMutex.leave();
1298 return conid; // was `return true`, so every caller got connection id 1
1299}
1300
1302 channelMutex.enter(1000);
1303 std::map<uint16, NetworkThread*>::iterator udpIt = udpListeners.find(port);
1304 NetworkThread* thread = (udpIt != udpListeners.end()) ? udpIt->second : NULL;
1305 if (!thread) {
1306 channelMutex.leave();
1307 return false;
1308 }
1309 uint64 conid = thread->id;
1310 udpListeners.erase(port);
1311 // RELEASE before calling endConnection(): channelMutex is RECURSIVE, so calling it
1312 // while still holding it would leave the mutex held (depth 2->1) across
1313 // endConnection()'s join, deadlocking against the exiting worker that needs
1314 // channelMutex in its own endConnection(). NEVER call endConnection() while
1315 // holding channelMutex.
1316 channelMutex.leave();
1317 return endConnection(conid);
1318}
1319
1321 NetworkThread* thread = connectionThreads[conid];
1322 if (!thread || !thread->con)
1323 return 0;
1324 return thread->con->getConnectionType();
1325}
1326
1328 NetworkThread* thread = connectionThreads[conid];
1329 if (!thread || !thread->con)
1330 return 0;
1331 return thread->con->getRemoteAddress();
1332}
1333
1335 if (!channelMutex.enter(2000, "NetworkChannel::endConnection")) {
1336 // Never proceed without the lock: racing NetworkChannel::shutdown here
1337 // leads to the same NetworkThread being deleted twice (double CloseHandle
1338 // of its Mutex/Semaphore handles -> invalid/recycled handles elsewhere).
1339 return false;
1340 }
1341 std::map<uint64, NetworkThread*>::iterator conIt = connectionThreads.find(conid);
1342 NetworkThread* thread = (conIt != connectionThreads.end()) ? conIt->second : NULL;
1343 if (thread == NULL) {
1344 channelMutex.leave();
1345 return false;
1346 }
1347 if (thread->reaping) {
1348 // CLAIM already taken: another teardown path (shutdown/stopListener/a
1349 // concurrent endConnection) solely owns the free of this object. Map
1350 // absence is not proof of ownership, which is why this flag exists.
1351 channelMutex.leave();
1352 return true;
1353 }
1354 thread->reaping = true; // CLAIM: this caller now solely owns the free
1355 thread->autoreconnect = false;
1356 thread->shouldContinue = false;
1357 bool forced = false;
1358 if (thread->isRunning) {
1359 // Cooperative-only shutdown: the worker polls shouldContinue on short
1360 // (<=500ms) bounded receive timeouts and exits by itself, calling this
1361 // very function on its way out; 5s covers a worker mid-body on a slow
1362 // read. Never pthread_cancel a worker that may hold con->mutex (the
1363 // cancel can land inside SSLConnection::receiveAvailable with the
1364 // non-cancel-safe mutex held; delete(con) below then frees it under
1365 // the worker's trailing mutex.leave() -> UAF SIGSEGV, seen on macOS).
1366 uint32 timeleft = 5000;
1367 while (thread->isRunning) {
1368 utils::Sleep(5);
1369 if ( (timeleft -= 5) <= 0)
1370 break;
1371 }
1372 if (thread->isRunning) {
1373 // LAST RESORT, should be unreachable; kept only so teardown can
1374 // never hang forever. If this fires it is a defect - investigate.
1375 LogPrint(0, LOG_NETWORK, 1, "NetworkChannel::endConnection FORCE-terminating connection thread %u after 5s cooperative wait - this should never happen, investigate", thread->threadID);
1376 utils::Sleep(50);
1378 utils::Sleep(50);
1379 thread->isRunning = false;
1380 forced = true;
1381 }
1382 }
1383 connectionThreads.erase(conid);
1384 manager->removeConnection(conid);
1385 uint32 workerTMID = thread->threadID;
1386 channelMutex.leave();
1387 // JOIN with channelMutex RELEASED, and BEFORE the free. The cooperative wait
1388 // above is NOT proof the worker has left: isRunning==false is ambiguous - it
1389 // means EITHER "not started yet" OR "already leaving". The startup case is the
1390 // one ASan actually caught (HTTPClientRun reads thread->con as its very FIRST
1391 // statement, before it ever sets isRunning=true), so a brand-new worker can
1392 // still dereference this object after we free it. The join is therefore
1393 // UNCONDITIONAL - never gated on isRunning - and waits on the real OS thread.
1394 // The lock must be released: the exiting worker needs channelMutex for its own
1395 // endConnection(), so joining under it deadlocks against the thread we wait for.
1396 ThreadManager::JoinThread(workerTMID);
1397 // DELETE without re-entering channelMutex. The claim flag above already gives
1398 // us exclusive ownership, and re-acquiring here is a use-after-free in its own
1399 // right: a caller on another thread can free this whole NetworkChannel while
1400 // the lock is released, and we would then enter a mutex inside a freed object
1401 // (measured: 2/60 network_http under ASan, tree ops on freed map nodes).
1402 delete(thread);
1403 // A caller on another thread (per-request teardown, destructors) may free
1404 // this whole channel right after we return, but a worker that exited
1405 // GRACEFULLY clears isRunning just BEFORE its own final endConnection()
1406 // call - so it can still be executing inside this channel. Wait (bounded,
1407 // short) for its OS thread to exit before returning, unless WE are that
1408 // worker (self-teardown must not wait on itself). Force-terminated threads
1409 // are the MOST likely to still be unwinding (cancellation cleanup), so they
1410 // get a bounded wait too - bounded, because a force-cancelled thread stuck
1411 // in a blocking call may never exit and must not stall teardown forever.
1412 (void)forced;
1413 return true;
1414}
1415
1417 receiver = recv;
1418 return true;
1419}
1420
1421
1423 uint64 start = GetTimeNow();
1424 int32 spent = 0;
1425
1426 while (eventQueue.empty()) {
1427 eventQueueSemaphore.wait((int32)ms-spent);
1428 if ( eventQueue.empty() && ( (spent = GetTimeAgeMS(start)) >= (int32)ms) )
1429 return NULL;
1430 }
1431
1432 eventQueueMutex.enter();
1433 NetworkEvent* e = eventQueue.front();
1434 eventQueue.pop();
1435 eventQueueMutex.leave();
1436 return e;
1437}
1438
1440 uint64 start = GetTimeNow();
1441 int32 spent = 0;
1442
1443 while (queueHTTPRequests.empty()) {
1444 queueHTTPRequestsSemaphore.wait((int32)ms-spent);
1445 if ( queueHTTPRequests.empty() && ( (spent = GetTimeAgeMS(start)) >= (int32)ms) )
1446 return NULL;
1447 }
1448
1449 queueHTTPRequestsMutex.enter();
1450 HTTPRequest* req = queueHTTPRequests.front();
1451 queueHTTPRequests.pop();
1452 queueHTTPRequestsMutex.leave();
1453 return req;
1454}
1455
1457 uint64 start = GetTimeNow();
1458 int32 spent = 0;
1459
1460 while (queueWebsocketData.empty()) {
1461 queueWebsocketDataSemaphore.wait((int32)ms - spent);
1462 if (queueWebsocketData.empty() && ((spent = GetTimeAgeMS(start)) >= (int32)ms))
1463 return NULL;
1464 }
1465
1467 WebsocketData* wsData = queueWebsocketData.front();
1468 queueWebsocketData.pop();
1470 return wsData;
1471}
1472
1474 uint64 start = GetTimeNow();
1475 int32 spent = 0;
1476
1477 while (queueTelnetLines.empty()) {
1478 queueTelnetLinesSemaphore.wait((int32)ms-spent);
1479 if ( queueTelnetLines.empty() && ( (spent = GetTimeAgeMS(start)) >= (int32)ms) )
1480 return NULL;
1481 }
1482
1483 queueTelnetLinesMutex.enter();
1484 TelnetLine* line = queueTelnetLines.front();
1485 queueTelnetLines.pop();
1486 queueTelnetLinesMutex.leave();
1487 return line;
1488}
1489
1491 uint64 start = GetTimeNow();
1492 int32 spent = 0;
1493
1494 while (queueMessages.empty()) {
1495 queueMessagesSemaphore.wait((int32)ms-spent);
1496 if ( queueMessages.empty() && ( (spent = GetTimeAgeMS(start)) >= (int32)ms) )
1497 return NULL;
1498 }
1499
1500 // LogPrint(0,0,0,"Dequeue!!!");
1501 queueMessagesMutex.enter();
1502 DataMessage* msg = queueMessages.front();
1503 queueMessages.pop();
1504 if (!queueMessageConIDs.empty()) {
1505 conid = queueMessageConIDs.front(); // report the originating connection so the caller can reply
1506 queueMessageConIDs.pop();
1507 }
1508 queueMessagesMutex.leave();
1509
1510 return msg;
1511}
1512
1514 uint64 start = GetTimeNow();
1515 int32 spent = 0;
1516
1517 while (queueHTTPReplies.empty()) {
1518 queueHTTPRepliesSemaphore.wait((int32)ms-spent);
1519 if ( queueHTTPReplies.empty() && ( (spent = GetTimeAgeMS(start)) >= (int32)ms) )
1520 return NULL;
1521 }
1522
1523 queueHTTPRepliesMutex.enter();
1524 HTTPReply* reply = queueHTTPReplies.front();
1525 queueHTTPReplies.pop();
1526 queueHTTPRepliesMutex.leave();
1527 return reply;
1528}
1529
1530
1531
1533 NetworkThread* thread = connectionThreads[conid];
1534 if ((thread == NULL) || (thread->con == NULL))
1535 return false;
1536 bool res = HTTPProtocol::SendWebsocketData(thread->con, wsData);
1537 return res;
1538}
1539
1540bool NetworkChannel::sendHTTPReply(HTTPReply* reply, uint64 conid) {
1541 NetworkThread* thread = connectionThreads[conid];
1542 if ((thread == NULL) || (thread->con == NULL))
1543 return false;
1544 bool res = HTTPProtocol::SendHTTPReply(thread->con, reply);
1545 return res;
1546}
1547
1549 NetworkThread* thread = connectionThreads[conid];
1550 if ((thread == NULL) || (thread->con == NULL))
1551 return false;
1552 bool res = TelnetProtocol::SendTelnetLine(thread->con, line);
1553 return res;
1554}
1555
1556TelnetLine* NetworkChannel::sendReceiveTelnetLine(TelnetLine* line, uint64 conid, uint32 timeout, uint32 size) {
1557 NetworkThread* thread = connectionThreads[conid];
1558 if ((thread == NULL) || (thread->con == NULL))
1559 return NULL;
1560
1561 if (!thread->isAsync) {
1562 // clear buffer before sending
1563 thread->con->clearBuffer();
1564 }
1565
1566 if (!TelnetProtocol::SendTelnetLine(thread->con, line))
1567 return NULL;
1568
1569 if (size)
1570 return TelnetProtocol::ReceiveTelnetLine(thread->con, timeout, size);
1571 else
1572 return TelnetProtocol::ReceiveTelnetLine(thread->con, timeout);
1573}
1574
1575bool NetworkChannel::sendMessage(DataMessage* msg, uint64 conid, uint64 receiver, bool packed) {
1576 NetworkThread* thread = NULL;
1577 if (channelMutex.enter(1000, __FUNCTION__)) {
1578 std::map<uint64, NetworkThread*>::iterator it = connectionThreads.find(conid);
1579 if (it != connectionThreads.end())
1580 thread = it->second;
1581 channelMutex.leave();
1582 }
1583 if ((thread == NULL) || (thread->con == NULL))
1584 return false;
1585 return MessageProtocol::SendMessage(thread->con, msg, receiver, packed);
1586}
1587
1589 NetworkThread* thread = connectionThreads[conid];
1590 if ((thread == NULL) || (thread->con == NULL))
1591 return false;
1592 bool res = MessageProtocol::SendMessage(thread->con, msg);
1593 if (!res)
1594 int a = 1;
1595 return res;
1596}
1597
1599 NetworkThread* thread = connectionThreads[conid];
1600 if ((thread == NULL) || (thread->con == NULL))
1602
1603 if (!HTTPProtocol::SendHTTPRequest(thread->con, req))
1605
1606 return waitForHTTPReply(conid, timeout);
1607 //return HTTPProtocol::ReceiveHTTPReply(thread->con, timeout);
1608}
1609
1611 NetworkThread* thread = connectionThreads[conid];
1612 if ((thread == NULL) || (thread->con == NULL))
1613 return 0;
1614 return thread->con->getOutputSpeed();
1615}
1616
1617uint32 NetworkChannel::getInputSpeed(uint64 conid) {
1618 NetworkThread* thread = connectionThreads[conid];
1619 if ((thread == NULL) || (thread->con == NULL))
1620 return 0;
1621 return thread->con->getInputSpeed();
1622}
1623
1625 NetworkThread* thread = connectionThreads[conid];
1626 if ((thread == NULL) || (thread->con == NULL))
1627 return false;
1628
1629 if (HTTPProtocol::SendHTTPRequest(thread->con, req)) {
1630 // if (thread->lastRequest != NULL)
1631 // delete(thread->lastRequest);
1632 // thread->lastRequest = new HTTPRequest(req);
1633 return true;
1634 }
1635 else {
1636 return false;
1637 }
1638}
1639
1641 if (!receiver || !receiver->receiveHTTPRequest(req, this, conid)) {
1642 queueHTTPRequestsMutex.enter();
1643 queueHTTPRequests.push(req);
1645 queueHTTPRequestsMutex.leave();
1646 }
1647 return true;
1648}
1649
1651 if (!receiver || !receiver->receiveWebsocketData(wsData, this, conid)) {
1653 queueWebsocketData.push(wsData);
1656 }
1657 return true;
1658}
1659
1660bool NetworkChannel::enterHTTPReply(HTTPReply* reply, HTTPRequest* req, uint64 conid) {
1661 if (!receiver || !receiver->receiveHTTPReply(reply, req, this, conid)) {
1662 queueHTTPRepliesMutex.enter();
1663 queueHTTPReplies.push(reply);
1665 queueHTTPRepliesMutex.leave();
1666 }
1667 return true;
1668}
1669
1671 if (!receiver || !receiver->receiveMessage(msg, this, conid)) {
1672 queueMessagesMutex.enter();
1673 queueMessages.push(msg);
1674 queueMessageConIDs.push(conid); // remember which connection it arrived on
1675 // LogPrint(0,0,0,"Queue size is now: %u (%u)", (uint32)queueMessages.size(), msg->getType()[15]);
1676 queueMessagesSemaphore.signal();
1677 queueMessagesMutex.leave();
1678 }
1679 return true;
1680}
1681
1683 if (!receiver || !receiver->receiveTelnetLine(line, this, conid)) {
1684 queueTelnetLinesMutex.enter();
1685 queueTelnetLines.push(line);
1687 queueTelnetLinesMutex.leave();
1688 }
1689 return true;
1690}
1691
1692
1693bool NetworkChannel::enterNetworkEvent(uint8 type, uint8 protocol, uint64 conid) {
1694 NetworkEvent* ev = new NetworkEvent;
1695 ev->cid = this->cid;
1696 ev->conid = conid;
1697 ev->time = GetTimeNow();
1698 ev->type = type;
1699 ev->protocol = protocol;
1700 if (!receiver || !receiver->receiveNetworkEvent(ev, this, conid)) {
1701 eventQueueMutex.enter();
1702 eventQueue.push(ev);
1703 eventQueueSemaphore.signal();
1704 eventQueueMutex.leave();
1705 }
1706 return true;
1707}
1708
1709uint64 NetworkChannel::autoDetectConnection(NetworkConnection* con, uint16 port, uint32 autoProtocols, uint32 autoProtocolTimeout, uint32 defaultProtocol, bool isAsync, bool autoreconnect) {
1710 // Multiplexes several protocols on one listening port: registers the fresh
1711 // connection and spawns ConnectionAutodetectRun, which peeks the first
1712 // bytes and asks each enabled protocol's CheckBufferForCompatibility()
1713 // (HTTP request line? DataMessage signature? plain text?) before handing
1714 // the connection to the matching per-protocol thread (HTTPServerRun,
1715 // MessageConnectionRun, ...). Falls back to defaultProtocol when nothing
1716 // matches within autoProtocolTimeout ms.
1717 channelMutex.enter(1000);
1718
1719 uint64 conid = manager->addConnection(this);
1720 if (!conid) {
1721 channelMutex.leave();
1722 return 0;
1723 }
1724 NetworkThread* thread = new NetworkThread(this, conid);
1725 thread->defaultProtocol = defaultProtocol;
1726 thread->autoProtocols = autoProtocols;
1727 thread->autoProtocolTimeout = autoProtocolTimeout;
1728 thread->autoreconnect = autoreconnect;
1729 thread->isAsync = isAsync;
1730 thread->parent = this;
1731 // Try connecting...
1732 thread->con = con;
1733
1734 connectionThreads[conid] = thread;
1735
1736 // Start the thread
1738 connectionThreads.erase(conid);
1739 // delete the thread object, but leave the con object alone to be managed by the calling function
1740 thread->con = NULL;
1741 delete(thread);
1742 channelMutex.leave();
1743 return 0;
1744 }
1745 if (!ThreadManager::GetThreadHandle(thread->threadID, thread->hWorker))
1746 LogPrint(0, LOG_NETWORK, 1, "Could not fetch ThreadHandle for autodetect thread %u...", thread->threadID);
1747
1748 channelMutex.leave();
1749 return conid;
1750}
1751
1752uint64 NetworkChannel::startConnection(NetworkConnection* con, uint8 protocol, bool isAsync, bool autoreconnect, uint32 timeoutMS) {
1753 channelMutex.enter(1000);
1754 uint64 conid = manager->addConnection(this);
1755 NetworkThread* thread = new NetworkThread(this, conid);
1756 thread->defaultProtocol = protocol;
1757 thread->autoreconnect = autoreconnect;
1758 thread->isAsync = isAsync;
1759 // Try connecting...
1760 thread->con = con;
1761 thread->con->setConnectTimeout(timeoutMS);
1762
1763 THREAD_FUNCTION func = NULL;
1764 switch(protocol) {
1766 func = HTTPServerRun;
1767 break;
1769 func = HTTPClientRun;
1770 break;
1771 case PROTOCOL_MESSAGE:
1772 func = MessageConnectionRun;
1773 break;
1774 case PROTOCOL_TELNET:
1775 func = TelnetServerRun;
1776 break;
1777 default:
1778 break;
1779 }
1780
1781 connectionThreads[conid] = thread;
1782 // Start the thread
1783 if (func) {
1784 if (!ThreadManager::CreateThread(func, thread, thread->threadID)) {
1785 connectionThreads.erase(conid);
1786 delete(thread);
1787 channelMutex.leave();
1788 return 0;
1789 }
1790 if (!ThreadManager::GetThreadHandle(thread->threadID, thread->hWorker))
1791 LogPrint(0, LOG_NETWORK, 1, "Could not fetch ThreadHandle for connection thread %u...", thread->threadID);
1792 }
1793
1794 channelMutex.leave();
1795 return conid;
1796}
1797
1799
1800 NetworkThread* thread = (NetworkThread*) arg;
1801 if ((thread == NULL) || (thread->listener == NULL))
1802 thread_ret_val(1);
1803 thread->isRunning = true;
1804
1805 uint64 conID;
1806 NetworkConnection* con;
1807 while (thread->shouldContinue) {
1808 if ( (con = thread->listener->acceptConnection(50)) != NULL) {
1809 conID = thread->parent->autoDetectConnection(con, thread->port, thread->autoProtocols, thread->autoProtocolTimeout, thread->defaultProtocol, thread->isAsync, false);
1810 if (conID == 0) {
1811 delete(con);
1812 con = NULL;
1813 }
1814 }
1815 }
1816
1817 thread->isRunning = false;
1818 thread_ret_val(0);
1819}
1820
1821
1823
1824 NetworkThread* thread = (NetworkThread*) arg;
1825 if ((thread == NULL) || (thread->con == NULL))
1826 thread_ret_val(1);
1827
1828 thread->isRunning = true;
1829
1830 uint8 protocol = 0;
1831 uint32 maxSize = 1024;
1832 char* buffer = new char[maxSize];
1833 uint32 size = 0;
1834
1835 uint64 start = GetTimeNow();
1836
1837 if (thread->autoProtocols > 0) {
1838 // printf("Autodetecting protocol");
1839 do {
1840 //printf(".");
1841 if (!thread->con->receiveAvailable(buffer, size, maxSize, 10, true)) {
1842 delete(thread->con);
1843 thread->con = NULL;
1844 thread->isRunning = false;
1845 delete [] buffer;
1846 thread_ret_val(1);
1847 }
1848 if (size > 0) {
1849 // utils::PrintBinary(buffer, size, false, "Autodetection");
1850 // printf(" [%u]", size);
1851
1852 if ( (thread->autoProtocols & PROTOCOL_HTTP_SERVER)
1854 protocol = PROTOCOL_HTTP_SERVER;
1855 // printf("Autodetected HTTP protocol after %u ms...\n\n", GetTimeAgeMS(start));
1856 }
1857 else if ( (thread->autoProtocols & PROTOCOL_MESSAGE)
1859 protocol = PROTOCOL_MESSAGE;
1860 // printf("Autodetected Message protocol after %u ms...\n\n", GetTimeAgeMS(start));
1861 }
1862 else if ( (thread->autoProtocols & PROTOCOL_TELNET)
1864 protocol = PROTOCOL_TELNET;
1865 // printf("Autodetected Telnet protocol after %u ms...\n\n", GetTimeAgeMS(start));
1866 }
1867 }
1868 // else
1869 // printf(".", size);
1870 } while (thread->con && (!protocol) && (GetTimeAgeMS(start) < (int32)thread->autoProtocolTimeout) && thread->shouldContinue);
1871 //printf("\n\n");
1872 }
1873
1874 delete [] buffer;
1875
1876 if (!thread->shouldContinue) {
1877 delete(thread->con);
1878 thread->con = NULL;
1879 thread->isRunning = false;
1880 thread_ret_val(1);
1881 }
1882
1883 if (protocol == 0) {
1884 if (thread->defaultProtocol == 0) {
1885 LogPrint(0,LOG_NETWORK,2,"No valid protocol detected for incoming network connection, disconnecting...\n\n");
1886 // con->disconnect();
1887 delete(thread->con);
1888 thread->con = NULL;
1889 thread->isRunning = false;
1890 thread_ret_val(1);
1891 }
1892 else {
1893 protocol = thread->defaultProtocol;
1894 switch(protocol) {
1896 //printf("Choosing default HTTP SERVER protocol after %u ms...\n\n", GetTimeAgeMS(start));
1897 break;
1899 //printf("Choosing default HTTP CLIENT protocol after %u ms...\n\n", GetTimeAgeMS(start));
1900 break;
1901 case PROTOCOL_MESSAGE:
1902 //printf("Choosing default MESSAGE protocol after %u ms...\n\n", GetTimeAgeMS(start));
1903 break;
1904 case PROTOCOL_TELNET:
1905 //printf("Choosing default TELNET protocol after %u ms...\n\n", GetTimeAgeMS(start));
1906 break;
1907 default:
1908 //printf("Choosing default unknown protocol after %u ms...\n\n", GetTimeAgeMS(start));
1909 break;
1910 }
1911 }
1912 }
1913
1914 thread->parent->enterNetworkEvent(NETWORKEVENT_CONNECT, protocol, thread->id);
1915
1916 thread->defaultProtocol = protocol;
1917
1918 THREAD_FUNCTION func = NULL;
1919 switch(protocol) {
1921 return HTTPServerRun(thread);
1923 return HTTPClientRun(thread);
1924 case PROTOCOL_MESSAGE:
1925 return MessageConnectionRun(thread);
1926 case PROTOCOL_TELNET:
1927 return TelnetServerRun(thread);
1928 default:
1929 delete(thread->con);
1930 thread->con = NULL;
1931 thread->isRunning = false;
1932 thread_ret_val(1);
1933 }
1934
1935
1936}
1937
1938
1940
1941 NetworkThread* thread = (NetworkThread*) arg;
1942 if ((thread == NULL) || (thread->con == NULL))
1943 thread_ret_val(1);
1944 thread->isRunning = true;
1945
1946 bool disconnected = false;
1947
1948 bool upgradedToWebsocket = false;
1949
1950 HTTPReply* reply;
1951 WebsocketData* wsData;
1952
1953 // The main job here is to check for disconnects and to auto-reconnect
1954
1955 while (thread->shouldContinue) {
1956 if (thread->con->isConnected()) {
1957 //utils::Sleep(50);
1958 if (upgradedToWebsocket) {
1959 wsData = HTTPProtocol::ReceiveWebsocketData(thread->con, 500);
1960 if (wsData) {
1961 if (wsData->isTerminationRequest()) {
1962 delete(wsData);
1963 // We can now terminate the connection
1965 { // capture before clearing isRunning: once false, shutdown()/endConnection()
1966 // may free this NetworkThread while we are still on our way out
1967 NetworkChannel* endParent = thread->parent;
1968 uint64 endConid = thread->id;
1969 thread->isRunning = false;
1970 endParent->endConnection(endConid);
1971 }
1972 thread_ret_val(1);
1973 }
1974 else {
1975 //printf("--- HTTP Server thread got new Websocket data...\n");
1976 if (!thread->parent->enterWebsocketData(wsData, thread->id)) {
1978 delete(wsData);
1979 }
1980 }
1981 }
1982 }
1983 else {
1984 // printf("--- HTTP Server thread receiving...\n");
1985 reply = HTTPProtocol::ReceiveHTTPReply(thread->con, 100);
1986 if (reply) {
1987 // Check for Websocket upgrade
1988 if (reply->isWebsocketUpgrade()) {
1989 // Consider the connection upgraded
1990 upgradedToWebsocket = true;
1991 // keep the reply so the main thread knows that the upgrade was successful
1992 }
1993 // printf("--- HTTP Client thread got new reply (%s)...\n", reply);
1994 if (!thread->parent->enterHTTPReply(reply, NULL, thread->id)) {
1996 delete(reply);
1997 }
1998 }
1999 }
2000 }
2001 else if (thread->autoreconnect) {
2002 if (!disconnected) {
2004 disconnected = true;
2005 }
2006 if (!thread->con->reconnect(1000)) {
2007 utils::Sleep(50);
2008 }
2009 else {
2010 // if we have greetingData send it now
2011 if (thread->con->greetingData && thread->con->greetingSize) {
2012 if (!thread->con->send(thread->con->greetingData, thread->con->greetingSize)) {
2014 continue;
2015 }
2016 }
2018 disconnected = false;
2019 }
2020 }
2021 else {
2023 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2024 // may free this NetworkThread while we are still on our way out
2025 NetworkChannel* endParent = thread->parent;
2026 uint64 endConid = thread->id;
2027 thread->isRunning = false;
2028 endParent->endConnection(endConid);
2029 }
2030 thread_ret_val(1);
2031 }
2032 }
2033
2034 // printf("9");
2035 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2036 // may free this NetworkThread while we are still on our way out
2037 NetworkChannel* endParent = thread->parent;
2038 uint64 endConid = thread->id;
2039 thread->isRunning = false;
2040 endParent->endConnection(endConid);
2041 }
2042 thread_ret_val(0);
2043}
2044
2046 // Per-connection HTTP server thread. Loops receiving HTTPRequests (500ms
2047 // poll) and dispatching them to the channel owner; when a request turns
2048 // out to be an RFC 6455 upgrade handshake the thread answers with the 101
2049 // Sec-WebSocket-Accept reply and flips into WebSocket mode for the rest of
2050 // the connection's life, thereafter parsing frames instead of requests and
2051 // answering CLOSE with a termination confirmation. So one listener port
2052 // serves both plain HTTP and long-lived WebSocket sessions.
2053
2054 // printf("--- Starting new HTTP Server thread...\n");
2055 NetworkThread* thread = (NetworkThread*) arg;
2056 if ((thread == NULL) || (thread->con == NULL))
2057 thread_ret_val(1);
2058 thread->isRunning = true;
2059
2060 bool disconnected = false;
2061
2062 bool upgradedToWebsocket = false;
2063 std::string wsOrigin;
2064
2065 HTTPRequest* req = NULL;
2066 HTTPReply* reply = NULL;
2067 WebsocketData* wsData, *wsDataReply;
2068 while (thread->shouldContinue) {
2069 if (thread->con->isConnected()) {
2070
2071 if (upgradedToWebsocket) {
2072 wsData = HTTPProtocol::ReceiveWebsocketData(thread->con, 500);
2073 if (wsData) {
2074 if (wsData->isTerminationRequest()) {
2075 LogPrint(0, LOG_NETWORK, 2, "Client requested termination of Websocket %llu", thread->id);
2077 if (!thread->parent->sendWebsocketData(wsDataReply, thread->id)) {
2078 }
2079 delete(wsDataReply);
2080 delete(wsData);
2081 // For now, leave the connection running until the client terminates
2082 }
2083 else {
2084 //printf("--- HTTP Server thread got new Websocket data...\n");
2085 if (!thread->parent->enterWebsocketData(wsData, thread->id)) {
2087 delete(req);
2088 }
2089 }
2090 }
2091 }
2092 else {
2093 // printf("--- HTTP Server thread receiving...\n");
2094 req = HTTPProtocol::ReceiveHTTPRequest(thread->con, 500);
2095 if (req) {
2096 LogPrint(0, LOG_NETWORK, 5, "Received incoming HTTP request, header size %u, content length: %u", req->headerLength, req->contentLength);
2097 // Check for Websocket upgrade
2098 if (req->isWebsocketUpgrade()) {
2099 const char* origin = req->getHeaderEntry("Origin");
2100 const char* key = req->getHeaderEntry("Sec-WebSocket-Key");
2101 const char* version = req->getHeaderEntry("Sec-WebSocket-Version");
2102 if (key && version) {
2103 if (origin)
2104 wsOrigin = origin;
2105
2106 // reply with confirmation
2107 reply = HTTPReply::CreateWebsocketHTTPReply(key, version);
2108 if (!reply)
2110 if (!thread->parent->sendHTTPReply(reply, thread->id)) {
2111 LogPrint(0, LOG_NETWORK, 1, "Unable to upgrade HTTP Server %llu to Websocket", thread->id);
2113 }
2114 // Consider the connection upgraded
2115 LogPrint(0, LOG_NETWORK, 2, "Upgraded HTTP Server %llu to Websocket", thread->id);
2116 upgradedToWebsocket = true;
2117 }
2118 else {
2119 // reply with error
2121 if (!thread->parent->sendHTTPReply(reply, thread->id)) {
2122 LogPrint(0, LOG_NETWORK, 1, "Unable to upgrade HTTP Server %llu to Websocket, key and/or version not provided", thread->id);
2124 }
2125 }
2126 delete(req);
2127 }
2128 else {
2129 // printf("--- HTTP Server thread got new request (%s)...\n", req->getRequest());
2130 if (!thread->parent->enterHTTPRequest(req, thread->id)) {
2132 delete(req);
2133 }
2134 }
2135 }
2136 }
2137 }
2138 else {
2140 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2141 // may free this NetworkThread while we are still on our way out
2142 NetworkChannel* endParent = thread->parent;
2143 uint64 endConid = thread->id;
2144 thread->isRunning = false;
2145 // printf("--- Disconnect, HTTP Server thread exit...\n");
2146 endParent->endConnection(endConid);
2147 }
2148 thread_ret_val(1);
2149 }
2150 }
2151
2152 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2153 // may free this NetworkThread while we are still on our way out
2154 NetworkChannel* endParent = thread->parent;
2155 uint64 endConid = thread->id;
2156 thread->isRunning = false;
2157 // printf("--- Finish, HTTP Server thread exit...\n");
2158 endParent->endConnection(endConid);
2159 }
2160 thread_ret_val(0);
2161}
2162
2164 // Per-connection binary-message thread: short (30ms) blocking receives so
2165 // the shouldContinue flag is honoured promptly, pushing every complete
2166 // DataMessage into the channel (async callback or sync wait queue). When
2167 // the connection was created with autoreconnect, a drop is surfaced as
2168 // NETWORKEVENT_DISCONNECT_RETRYING and the thread itself keeps calling
2169 // reconnect() until the link is back (then NETWORKEVENT_RECONNECT) — this
2170 // is the transparent-reconnect machinery the Request* classes rely on.
2171
2172 //uint32 tt;
2173 //utils::GetCurrentThreadOSID(tt);
2174
2175 //LogPrint(0, 0, 0, "%u ************** ReceiveMessage starting ******************", tt);
2176 NetworkThread* thread = (NetworkThread*) arg;
2177 if ((thread == NULL) || (thread->con == NULL))
2178 thread_ret_val(1);
2179 thread->isRunning = true;
2180 uint64 remoteAddr;
2181 bool disconnected = false;
2182 bool wasConnected = false;
2183
2184 uint64 t;
2185 DataMessage* msg;
2186 while (thread->shouldContinue) {
2187 if (thread->con->isConnected()) {
2188 //printf("[%llu]", thread->id);
2189 wasConnected = true;
2190 t = GetTimeNow();
2191 msg = MessageProtocol::ReceiveMessage(thread->con, 30);
2192 //if (GetTimeAge(t) > 200) {
2193 //if (msg)
2194 // LogPrint(0,0,0,"%u ************** ReceiveMessage msg (ref %llu) took %s ******************", tt, msg->getReference(), PrintTimeDifString(GetTimeAge(t)).c_str());
2195 //else
2196 // LogPrint(0,0,0,"%u ************** ReceiveMessage NULL took %s ******************", PrintTimeDifString(GetTimeAge(t)).c_str());
2197 //}
2198 if (msg) {
2199 //t = GetTimeNow();
2200 if (!thread->parent->enterMessage(msg, thread->id)) {
2202 delete(msg);
2203 }
2204 }
2205 }
2206 else if (thread->autoreconnect) {
2207 //LogPrint(0,0,0,"************** ReceiveMessage auto reconnect ******************");
2208 if (!disconnected && wasConnected) {
2210 disconnected = true;
2211 }
2212 remoteAddr = thread->con->getRemoteAddress();
2213 //LogPrint(0,0,0,"************** ReceiveMessage %llu auto reconnecting to %u.%u.%u.%u:%u ******************",
2214 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2215 t = GetTimeNow();
2216 if (!thread->con->reconnect(1000)) {
2217 //printf("-%llu*%d-", thread->id, GetTimeAgeMS(t));
2218 //LogPrint(0, 0, 0, "-------------- ReceiveMessage %llu FAILED reconnecting to %u.%u.%u.%u:%u --------------",
2219 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2220 utils::Sleep(50);
2221 }
2222 else {
2223 //printf("<%llu>", thread->id);
2224 utils::Sleep(20);
2225 if (thread->con->isConnected()) {
2226 // ######### if we have greetingData send it now
2227 if (thread->con->greetingData && thread->con->greetingSize) {
2228 if (!thread->con->send(thread->con->greetingData, thread->con->greetingSize)) {
2230 //LogPrint(0, 0, 0, "-------------- ReceiveMessage %llu FAILED sending greeting to %u.%u.%u.%u:%u --------------",
2231 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2232 continue;
2233 }
2234 else {
2235 //LogPrint(0, 0, 0, "!!!!!!!!!!!!!! ReceiveMessage %llu SUCCESS sending greeting to %u.%u.%u.%u:%u !!!!!!!!!!!!!!",
2236 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2237 }
2238 }
2239 else {
2240 // LogPrint(0, 0, 0, "!!!!!!!!!!!!!! ReceiveMessage %llu SUCCESS reconnecting to %u.%u.%u.%u:%u !!!!!!!!!!!!!!",
2241 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2242 }
2244 disconnected = false;
2245 }
2246 // otherwise, it didn't work anyway...
2247 }
2248 }
2249 else {
2250 //printf("!%llu!", thread->id);
2251 //LogPrint(0,0,0,"%u ************** ReceiveMessage disconnect ******************", tt);
2253 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2254 // may free this NetworkThread while we are still on our way out
2255 NetworkChannel* endParent = thread->parent;
2256 uint64 endConid = thread->id;
2257 thread->isRunning = false;
2258 endParent->endConnection(endConid);
2259 }
2260 thread_ret_val(1);
2261 }
2262 }
2263
2264 //LogPrint(0,0,0,"%u ************** ReceiveMessage returning ******************", tt);
2265 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2266 // may free this NetworkThread while we are still on our way out
2267 NetworkChannel* endParent = thread->parent;
2268 uint64 endConid = thread->id;
2269 thread->isRunning = false;
2270 endParent->endConnection(endConid);
2271 }
2272 thread_ret_val(0);
2273}
2274
2276
2277 NetworkThread* thread = (NetworkThread*) arg;
2278 if ((thread == NULL) || (thread->con == NULL))
2279 thread_ret_val(1);
2280 thread->isRunning = true;
2281
2282 bool disconnected = false;
2283
2284 TelnetLine* line;
2285 while (thread->shouldContinue) {
2286 if (thread->con->isConnected(thread->isAsync ? 0 : 50)) {
2287 if (thread->isAsync) {
2288 line = TelnetProtocol::ReceiveTelnetLine(thread->con, 50);
2289 if (line) {
2290 if (!thread->parent->enterTelnetLine(line, thread->id)) {
2292 delete(line);
2293 }
2294 }
2295 }
2296 else {
2297 utils::Sleep(50);
2298 }
2299 //else {
2300 // if (thread->con->peekStream() < 0) {
2301 // thread->parent->enterNetworkEvent(NETWORKEVENT_DISCONNECT, thread->defaultProtocol, thread->id);
2302 // thread->isRunning = false;
2303 // thread_ret_val(1);
2304 // }
2305 //}
2306 }
2307 else {
2309 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2310 // may free this NetworkThread while we are still on our way out
2311 NetworkChannel* endParent = thread->parent;
2312 uint64 endConid = thread->id;
2313 thread->isRunning = false;
2314 endParent->endConnection(endConid);
2315 }
2316 thread_ret_val(1);
2317 }
2318 }
2319
2320 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2321 // may free this NetworkThread while we are still on our way out
2322 NetworkChannel* endParent = thread->parent;
2323 uint64 endConid = thread->id;
2324 thread->isRunning = false;
2325 endParent->endConnection(endConid);
2326 }
2327 thread_ret_val(0);
2328}
2329
2331
2332 // Self-contained loopback test: start a local listener, then open several
2333 // delayed (greeting) TCP connections to it on a high localhost port and
2334 // confirm the listener receives the greeting message on each connection.
2335 const uint16 PORT = 38101;
2336 const uint32 CONNECTIONS = 3;
2337
2338 unittest::progress(0, "starting delayed-connect listener");
2339
2340 NetworkManager* manager = new NetworkManager();
2341
2342 NetworkChannel* listen = manager->createListener(PORT, NOENC, PROTOCOL_MESSAGE, true, 0, true, 0, NULL);
2343 if (!listen) {
2344 unittest::fail("NetworkManager delayed-connect test: could not start listening on port %u", PORT);
2345 delete(manager);
2347 return false;
2348 }
2349
2350 DataMessage* msgConnect = new DataMessage();
2351 msgConnect->setString("URI", "ExecutorConnect");
2352
2353 std::vector<uint64> conIDs;
2354 uint64 conid = 0;
2355 uint64 location = 0;
2356 NetworkChannel* channel = NULL;
2357
2358 unittest::progress(25, "opening delayed connections");
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");
2362 goto err;
2363 }
2364 conIDs.push_back(conid);
2365 for (uint32 n = 1; n < CONNECTIONS; n++) {
2366 conid = channel->addTCPConnection("localhost", PORT, NOENC, PROTOCOL_MESSAGE, true, location, 1000, (char*)msgConnect->data, msgConnect->getSize());
2367 if (!conid) {
2368 unittest::fail("NetworkManager delayed-connect test: could not add connection %u", n + 1);
2369 goto err;
2370 }
2371 conIDs.push_back(conid);
2372 }
2373
2374 unittest::progress(60, "receiving greeting messages");
2375 for (uint32 n = 0; n < CONNECTIONS; n++) {
2376 uint64 recvid = 0;
2377 DataMessage* in = listen->waitForMessage(recvid, 2000);
2378 if (!in) {
2379 unittest::fail("NetworkManager delayed-connect test: greeting %u not received", n + 1);
2380 goto err;
2381 }
2382 unittest::detail("delayed-connect: received greeting %u", n + 1);
2383 delete(in);
2384 }
2385
2386 unittest::progress(90, "shutting down");
2387 delete msgConnect;
2388 delete(manager);
2390 unittest::progress(100, "done");
2391 return true;
2392err:
2393 delete msgConnect;
2394 delete(manager);
2396 return false;
2397
2398}
2399
2401
2402 uint32 size = 70000;
2403
2404 char* dat;
2405 uint64 conid = 0;
2406 uint64 conid2 = 0;
2407 uint32 address;
2408 uint64 destination;
2409 DataMessage* msg, *msg2;
2410 uint64 start = GetTimeNow();
2411 uint32 count = 100, subcount = 100, subcount2 = 10, n, m, k;
2412 uint64 t, end;
2413 NetworkChannel* con = NULL;
2414 PsyType type = { { 1,10001,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };
2415
2416 uint64 recvid;
2417
2418 unittest::progress(0, "starting TCP listener");
2419
2420 NetworkManager* manager = new NetworkManager();
2421
2422 NetworkChannel* listen;
2423
2424 // Retry the bind briefly: when this test is run repeatedly back-to-back the
2425 // previous run's port can still be releasing, so a single bind attempt can
2426 // transiently fail with "address in use". Only sleeps on failure, so a normal
2427 // run is unaffected.
2428 listen = NULL;
2429 for (int tries = 0; tries < 20 && !listen; tries++) {
2430 listen = manager->createListener(38100, NOENC, PROTOCOL_MESSAGE, true, 0, true, 0, NULL);
2431 if (!listen) utils::Sleep(100);
2432 }
2433 if (!listen) {
2434 unittest::fail("NetworkManager test: could not start listening on port 38100");
2435 goto err;
2436 }
2437
2438 uint64 location;
2439 con = manager->createTCPConnection("localhost", 38100, NOENC, PROTOCOL_MESSAGE, true, false, 0, NULL, conid, location);
2440 if (!con) {
2441 unittest::fail("NetworkManager test: could not connect");
2442 goto err;
2443 }
2444 dat = new char[size];
2445 memset(dat, 0, size);
2446 msg = new DataMessage(CTRL_TEST, 20);
2447 msg->setData("Test", dat, size);
2448 delete [] dat;
2449 if (!con->sendMessage(msg, conid)) {
2450 unittest::fail("NetworkManager test: could not send message");
2451 goto err;
2452 }
2453 msg2 = listen->waitForMessage(recvid, 10000);
2454 if (!msg2) {
2455 unittest::fail("NetworkManager test: no message received");
2456 goto err;
2457 }
2458 else if ( (msg->getType() != msg2->getType()) || (msg->getFrom() != msg2->getFrom()) ) {
2459 unittest::fail("NetworkManager test: message sent and received mismatch");
2460 goto err;
2461 }
2462 delete(msg2);
2463
2464 unittest::progress(10, "TCP single message round-trip");
2465 start = GetTimeNow();
2466 if (!con->sendMessage(msg, conid)) {
2467 unittest::fail("NetworkManager test: could not send message");
2468 goto err;
2469 }
2470 t = GetTimeNow();
2471 msg2 = listen->waitForMessage(recvid, 10000);
2472
2473 if (!msg2) {
2474 unittest::fail("NetworkManager test: no message received");
2475 goto err;
2476 }
2477 else if ( (msg->getType() != msg2->getType()) || (msg->getFrom() != msg2->getFrom()) ) {
2478 unittest::fail("NetworkManager test: message sent and received mismatch");
2479 goto err;
2480 }
2481 delete(msg2);
2482 end = GetTimeNow();
2483 unittest::detail("TCP single message send %s, receive %s, total %s",
2484 PrintTimeDifString(t - start).c_str(),
2485 PrintTimeDifString(end - t).c_str(),
2486 PrintTimeDifString(end - start).c_str() );
2487
2488 unittest::progress(20, "TCP throughput loop");
2489 t = 0;
2490 count = 10, subcount = 10, subcount2 = 15;
2491 for (m = 0; m < count; m++) {
2492 // LogPrint(0,0,0,"[%u] Starting to send %u msgs...\n", (m+1)*subcount, subcount);
2493 start = GetTimeNow();
2494 for (n = 0; n < subcount; n++) {
2495 type.levels[15] = n;
2496 msg->setType(type);
2497 // t = GetTimeNow();
2498 for (k=0; k<subcount2; k++) {
2499 if (!con->sendMessage(msg, conid)) {
2500 unittest::fail("NetworkManager test: could not send TCP message %u", n);
2501 goto err;
2502 }
2503 }
2504 // printf("SendMessage: %lu\n", GetTimeAgeMS(t));
2505// }
2506 // LogPrint(0,0,0,"[%u] Sent %u msgs...\n", (m+1)*subcount, subcount);
2507 // utils::Sleep(1000);
2508 // printf("Msg Queue size: %u\n\n", (uint32)listen->queueMessages.size());
2509// for (n = 0; n < subcount; n++) {
2510 for (k=0; k<subcount2; k++) {
2511 msg2 = listen->waitForMessage(recvid, 10000);
2512 type.levels[15] = n;
2513 if (!msg2) {
2514 unittest::fail("NetworkManager test: [%u/%u/%u] no TCP message received", n, m, k);
2515 goto err;
2516 }
2517 else if ( (msg2->getType() != type) || (msg->getFrom() != msg2->getFrom()) ) {
2518 unittest::fail("NetworkManager test: [%u] TCP message sent and received mismatch", n);
2519 goto err;
2520 }
2521 delete(msg2);
2522 }
2523 }
2524 end = GetTimeNow();
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));
2529 t += end - start;
2530 }
2531
2532 {
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);
2537 unittest::metric("tcp_avg_latency", tcpUs / tcpMsgs, "us", false);
2538 unittest::metric("tcp_throughput", ((double)tcpMsgs * size) / tcpUs, "MB/s", true);
2539 }
2540
2541 con->endConnection(conid);
2542 delete(msg);
2543
2544 unittest::progress(55, "starting UDP listener");
2545 t = 0;
2546
2547 listen = manager->createUDPConnection(38101, PROTOCOL_MESSAGE, true, true, 0, NULL, conid2);
2548 if (!listen) {
2549 unittest::fail("NetworkManager test: could not start UDP listening on port 38101");
2550 goto err;
2551 }
2552
2553 // Target the loopback address (the UDP listener binds INADDR_ANY) so this
2554 // self-contained test works on any machine. Sending to the LAN interface
2555 // address can silently fail to loop back (firewall / routing), which would
2556 // otherwise hang this phase until the per-test timeout.
2557 address = LOCALHOSTIP;
2558 destination = GETIPADDRESSPORT(address, 38101);
2559
2560 size = 1024;
2561 dat = new char[size];
2562 memset(dat, 0, size);
2563 msg = new DataMessage(CTRL_TEST, 20);
2564 msg->setData("Test", dat, size);
2565 delete [] dat;
2566 // UDP is best-effort: a single datagram can be dropped even on loopback, so
2567 // a strict one-shot round-trip is inherently flaky. Retry a few times and
2568 // require that at least one round-trip succeeds - that verifies UDP
2569 // send/receive works without occasionally failing on a lost packet.
2570 unittest::progress(65, "UDP single message round-trip");
2571 start = GetTimeNow();
2572 {
2573 bool udpOk = false;
2574 for (int attempt = 0; attempt < 10 && !udpOk; attempt++) {
2575 if (!manager->sendUDPMessage(msg, destination)) {
2576 unittest::fail("NetworkManager test: could not send UDP message");
2577 goto err;
2578 }
2579 msg2 = listen->waitForMessage(recvid, 500);
2580 if (!msg2)
2581 continue; // datagram lost - retry
2582 if ( (msg->getType() != msg2->getType()) || (msg->getFrom() != msg2->getFrom()) ) {
2583 delete(msg2);
2584 unittest::fail("NetworkManager test: UDP message sent and received mismatch");
2585 goto err;
2586 }
2587 delete(msg2);
2588 udpOk = true;
2589 }
2590 if (!udpOk) {
2591 unittest::fail("NetworkManager test: no UDP round-trip on port 38101 after 10 attempts (loopback UDP may be blocked here)");
2592 goto err;
2593 }
2594 }
2595 end = GetTimeNow();
2596 unittest::detail("UDP single message round-trip ok in %s", PrintTimeDifString(end - start).c_str());
2597
2598 // UDP is best-effort: dropped datagrams are expected and tolerated (not a
2599 // failure). Cap the whole phase with a time budget and use a short per-read
2600 // wait so a burst of packet loss can never stall the test to the per-test
2601 // timeout.
2602 unittest::progress(70, "UDP throughput loop");
2603 t = 0;
2604 count = 10, subcount = 10, subcount2 = 15;
2605 { // scope the budget locals so the earlier goto err's don't cross them
2606 uint64 udpLoopStart = GetTimeNow();
2607 const int32 udpBudgetMs = 5000;
2608 for (m = 0; m < count && GetTimeAgeMS(udpLoopStart) < udpBudgetMs; m++) {
2609 // LogPrint(0,0,0,"[%u] Starting to send %u msgs...\n", (m+1)*subcount, subcount);
2610 start = GetTimeNow();
2611 for (n = 0; n < subcount; n++) {
2612 type.levels[15] = n;
2613 msg->setType(type);
2614 // t = GetTimeNow();
2615 for (k=0; k<subcount2; k++) {
2616 if (!manager->sendUDPMessage(msg, destination)) {
2617 unittest::fail("NetworkManager test: could not send UDP message %u", n);
2618 goto err;
2619 }
2620 }
2621 // printf("SendMessage: %lu\n", GetTimeAgeMS(t));
2622// }
2623 // LogPrint(0,0,0,"[%u] Sent %u msgs...\n", (m+1)*subcount, subcount);
2624 // utils::Sleep(1000);
2625 // printf("Msg Queue size: %u\n\n", (uint32)listen->queueMessages.size());
2626// for (n = 0; n < subcount; n++) {
2627 for (k=0; k<subcount2; k++) {
2628 if (GetTimeAgeMS(udpLoopStart) >= udpBudgetMs)
2629 break; // stay within the phase budget regardless of UDP loss
2630 msg2 = listen->waitForMessage(recvid, 50);
2631 if (!msg2) {
2632 t -= 50000; // discount the wait so the rate reflects delivered msgs
2633 continue;
2634 }
2635 delete(msg2);
2636 }
2637 }
2638 end = GetTimeNow();
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));
2643 t += end - start;
2644 }
2645 } // end budget-local scope
2646
2647 delete(msg);
2648 {
2649 uint32 udpMsgs = count*subcount*subcount2;
2650 double udpUs = (double)t;
2651 if (udpUs > 0) {
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);
2654 unittest::metric("udp_avg_latency", udpUs / udpMsgs, "us", false);
2655 }
2656 }
2657
2658 unittest::progress(95, "shutting down");
2659 delete(manager);
2661 unittest::progress(100, "done");
2662 return true;
2663err:
2664 delete(manager);
2666 return false;
2667}
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2681
2684
2686// printf("HTTPTestServer received HTTPRequest...\n\n");
2687
2688 char text[512];
2689 snprintf(text, 512, "Hello World %llu", GetTimeNow());
2690
2691 uint64 localAddr = 0;
2692 utils::GetLocalIPAddress(*(uint32*)&localAddr);
2693
2694 HTTPReply* reply = new HTTPReply(localAddr);
2695 if (!reply->createPage(HTTP_OK, GetTimeNow(), "MyServer", GetTimeNow(), true, false, "text/html", text)) {
2696 printf("Error generating HTML page...\n\n");
2697 }
2698
2699 if (!channel->sendHTTPReply(reply, conid))
2700 printf("Error sending response...\n\n");
2701// else
2702// printf("HTTPTestServer sent response...\n\n");
2703
2704 delete(reply);
2705 return true;
2706}
2707
2708
2709
2710
2711
2712
2713
2714
2717
2720
2722 printf("WebsocketTestServer received HTTPRequest...\n\n");
2723 delete(req);
2724 return true;
2725}
2726
2728 uint64 size = 0;
2729 const char* data = wsData->getContent(size);
2730 if (wsData->dataType == wsData->TEXT) {
2731 std::string str = utils::StringFormat("%s - %s", PrintTimeNowString().c_str(), data);
2732 WebsocketData* wsDataOut = new WebsocketData();
2733 wsDataOut->setData(wsData->TEXT, false, str.c_str(), str.length());
2734 channel->sendWebsocketData(wsDataOut, conid);
2735 delete wsDataOut;
2736 }
2737 else {
2738 WebsocketData* wsDataOut = new WebsocketData();
2739 wsDataOut->setData(wsData->BINARY, false, data, size);
2740 channel->sendWebsocketData(wsDataOut, conid);
2741 delete wsDataOut;
2742 }
2743 //printf("WebsocketTestServer received data...\n\n");
2744 delete wsData;
2745 return true;
2746}
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2760
2761// char buffer1[] =
2762//"GET /path/file.html HTTP/1.0\n\
2763//From: someuser@jmarshall.com\n\
2764//User-Agent: HTTPTool/1.0\n\
2765//if-modified-since: Sat, 29 Oct 1994 19:43:31 GMT\n\n";
2766//
2767// char buffer[] =
2768//"POST /path/script.cgi HTTP/1.0\n\
2769//From: frog@jmarshall.com\n\
2770//User-Agent: HTTPTool/1.0\n\
2771//Content-Type: application/x-www-form-urlencoded\n\
2772//Content-Length: 35\n\n\
2773//home=Cosby&favorite+flavor=fl%26ies";
2774//
2775// HTTPRequest* req = new HTTPRequest(0);
2776// if (!req->processHeader(buffer, strlen(buffer)))
2777// return false;
2778//
2779// if (!req->processContent("home=Cosby&favorite+flavor=fl%26ies", 35))
2780// return false;
2781
2782 uint64 conid;
2783 NetworkChannel* con;
2784 HTTPRequest* req;
2785 HTTPReply* reply;
2786 uint32 n, count;
2787 uint64 httpStart, httpEnd;
2788
2789 unittest::progress(0, "starting HTTP server");
2790
2791 HTTPTestServer* testServer = new HTTPTestServer();
2792 NetworkManager* manager = new NetworkManager();
2793
2794 NetworkChannel* listen = manager->createListener(38102, NOENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
2795 if (!listen) {
2796 unittest::fail("NetworkManager HTTP test: could not start listening on port 38102");
2797 goto err;
2798 }
2799
2800 uint64 location;
2801 con = manager->createTCPConnection("localhost", 38102, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
2802 if (!con) {
2803 unittest::fail("NetworkManager HTTP test: could not connect");
2804 goto err;
2805 }
2806 unittest::progress(15, "HTTP single request round-trip");
2807
2808 req = new HTTPRequest((uint64)0);
2809 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
2810 delete(req);
2811 unittest::fail("NetworkManager HTTP test: could not create request");
2812 goto err;
2813 }
2814 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
2815 delete(req);
2816 if (reply == NULL) {
2817 unittest::fail("NetworkManager HTTP test: did not receive reply");
2818 goto err;
2819 }
2820 delete(reply);
2821
2822 unittest::progress(30, "HTTP request loop");
2823 count = 500;
2824 httpStart = GetTimeNow();
2825 for (n=0; n<count; n++) {
2826 if (n && !(n%10)) {
2827 con->endConnection(conid);
2828 con = manager->createTCPConnection("localhost", 38102, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
2829 if (!con) {
2830 unittest::fail("NetworkManager HTTP test: could not reconnect [%u]", n);
2831 goto err;
2832 }
2833 }
2834
2835 req = new HTTPRequest((uint64)0);
2836 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
2837 delete(req);
2838 unittest::fail("NetworkManager HTTP test: could not create request [%u]", n);
2839 goto err;
2840 }
2841 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
2842 delete(req);
2843 if (reply == NULL) {
2844 unittest::fail("NetworkManager HTTP test: did not receive reply [%u]", n);
2845 goto err;
2846 }
2847 delete(reply);
2848 }
2849 httpEnd = GetTimeNow();
2850 {
2851 double httpUs = (double)(httpEnd - httpStart);
2852 unittest::detail("HTTP: %u requests in %.3fms", count, httpUs / 1000.0);
2853 unittest::metric("http_request_rate", (double)count / httpUs * 1e6, "req/s", true);
2854 unittest::metric("http_avg_latency", httpUs / count, "us", false);
2855 }
2856
2857 con->endConnection(conid);
2858
2859
2860 //NetworkChannel* con2 = manager->createConnection("cmlabs.com", 80, PROTOCOL_HTTP_CLIENT, false, 0, NULL, conid);
2861 //if (!con2) {
2862 // printf("Could not connect to cmlabs.com...\n\n");
2863 // goto err;
2864 //}
2865 //printf("Connected...\n\n");
2866
2867 //req = new HTTPRequest((uint64)0);
2868 //if (!req->createRequest(HTTP_GET, "cmlabs.com", "/", NULL, 0, true, 0)) {
2869 // delete(req);
2870 // printf("Could not create request...\n\n");
2871 // goto err;
2872 //}
2873 //printf("Request:\n%s\n", req->data);
2874 //if (!con2->sendHTTPRequest(req, conid)) {
2875 // delete(req);
2876 // printf("Could not send request...\n\n");
2877 // goto err;
2878 //}
2879 //delete(req);
2880
2881 //reply = con2->waitForHTTPReply(conid2, 10000);
2882 //if (reply == NULL) {
2883 // printf("Did not receive reply...\n\n");
2884 // goto err;
2885 //}
2886 //printf("Reply:\n%s\n", reply->data);
2887 //delete(reply);
2888
2889
2890
2891
2892
2893
2894 unittest::progress(95, "shutting down");
2895 delete(manager);
2896 delete(testServer);
2898 unittest::progress(100, "done");
2899 return true;
2900err:
2901 delete(manager);
2902 delete(testServer);
2904 return false;
2905
2906}
2907
2908
2909#ifdef _USE_SSL_
2910
2911// Generate a throwaway RSA self-signed certificate (CN=localhost) and write the
2912// cert and private key to PEM files. Used only by the HTTPS unit test; the SSL
2913// listener loads cert/key from files (SSL_CTX_use_certificate_chain_file /
2914// SSL_CTX_use_PrivateKey_file). Generated at runtime so no key is committed.
2915// Cross-platform temp path for throwaway test cert/key PEM files (/tmp is
2916// POSIX-only; on Windows use %TEMP%).
2917static std::string sslTestTempPath(const char* filename) {
2918#ifdef WINDOWS
2919 const char* t = getenv("TEMP");
2920 if (!t) t = getenv("TMP");
2921 if (!t) t = ".";
2922 return std::string(t) + "\\" + filename;
2923#else
2924 return std::string("/tmp/") + filename;
2925#endif
2926}
2927
2928static bool generateSelfSignedCert(const char* certPath, const char* keyPath) {
2929 bool ok = false;
2930 EVP_PKEY* pkey = EVP_RSA_gen(2048); // OpenSSL 3 one-shot RSA keygen
2931 if (!pkey)
2932 return false;
2933 X509* x509 = X509_new();
2934 if (x509) {
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); // ~10 years
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); // self-signed: issuer == subject
2943 if (X509_sign(x509, pkey, EVP_sha256())) {
2944 FILE* kf = fopen(keyPath, "wb");
2945 FILE* cf = fopen(certPath, "wb");
2946 if (kf && cf &&
2947 PEM_write_PrivateKey(kf, pkey, NULL, NULL, 0, NULL, NULL) == 1 &&
2948 PEM_write_X509(cf, x509) == 1)
2949 ok = true;
2950 if (kf) fclose(kf);
2951 if (cf) fclose(cf);
2952 }
2953 X509_free(x509);
2954 }
2955 EVP_PKEY_free(pkey);
2956 return ok;
2957}
2958
2959// HTTPS end-to-end test: a self-signed SSL HTTP server + an SSL HTTP client
2960// exchanging real requests over TLS on loopback. Only built/registered when
2961// compiled with `make ssl` (-D _USE_SSL_); proves the OpenSSL-backed SSLENC
2962// path (handshake + encrypted read/write) actually works.
2963bool NetworkManager::UnitTestHTTPS() {
2964
2965 uint64 conid;
2966 NetworkChannel* con = NULL;
2967 HTTPRequest* req;
2968 HTTPReply* reply;
2969 uint32 n, count;
2970 uint64 location;
2971 uint64 httpsStart, httpsEnd;
2972
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();
2977
2978 unittest::progress(0, "generating self-signed certificate");
2979 if (!generateSelfSignedCert(certPath, keyPath)) {
2980 unittest::fail("NetworkManager HTTPS test: could not generate self-signed certificate");
2981 return false;
2982 }
2983
2984 HTTPTestServer* testServer = new HTTPTestServer();
2985 NetworkManager* manager = new NetworkManager();
2986 manager->setSSLCertificate(certPath, keyPath);
2987 // The test server uses a throwaway self-signed cert, so the client side
2988 // must explicitly opt out of peer verification (verification itself is
2989 // covered by the network_sslverify test).
2990 manager->setSSLAllowSelfSigned(true);
2991
2992 unittest::progress(10, "starting HTTPS (SSL) server");
2993 NetworkChannel* listen = manager->createListener(38112, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
2994 if (!listen) {
2995 unittest::fail("NetworkManager HTTPS test: could not start SSL listener on port 38112");
2996 goto err;
2997 }
2998
2999 unittest::progress(25, "HTTPS client connect + TLS handshake");
3000 con = manager->createTCPConnection("localhost", 38112, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3001 if (!con) {
3002 unittest::fail("NetworkManager HTTPS test: could not connect / TLS handshake failed");
3003 goto err;
3004 }
3005
3006 unittest::progress(45, "HTTPS single request round-trip");
3007 req = new HTTPRequest((uint64)0);
3008 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
3009 delete(req);
3010 unittest::fail("NetworkManager HTTPS test: could not create request");
3011 goto err;
3012 }
3013 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3014 delete(req);
3015 if (reply == NULL) {
3016 unittest::fail("NetworkManager HTTPS test: did not receive reply over TLS");
3017 goto err;
3018 }
3019 delete(reply);
3020
3021 unittest::progress(60, "HTTPS request loop");
3022 count = 50;
3023 httpsStart = GetTimeNow();
3024 for (n=0; n<count; n++) {
3025 if (n && !(n%10)) {
3026 con->endConnection(conid);
3027 con = manager->createTCPConnection("localhost", 38112, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3028 if (!con) {
3029 unittest::fail("NetworkManager HTTPS test: could not reconnect over TLS [%u]", n);
3030 goto err;
3031 }
3032 }
3033 req = new HTTPRequest((uint64)0);
3034 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
3035 delete(req);
3036 unittest::fail("NetworkManager HTTPS test: could not create request [%u]", n);
3037 goto err;
3038 }
3039 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3040 delete(req);
3041 if (reply == NULL) {
3042 unittest::fail("NetworkManager HTTPS test: did not receive reply over TLS [%u]", n);
3043 goto err;
3044 }
3045 delete(reply);
3046 }
3047 httpsEnd = GetTimeNow();
3048 {
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);
3052 unittest::metric("https_avg_latency", httpsUs / count, "us", false);
3053 }
3054 con->endConnection(conid);
3055
3056 unittest::progress(95, "shutting down");
3057 delete(manager);
3058 delete(testServer);
3060 unlink(certPath);
3061 unlink(keyPath);
3062 unittest::progress(100, "done");
3063 return true;
3064err:
3065 delete(manager);
3066 delete(testServer);
3068 unlink(certPath);
3069 unlink(keyPath);
3070 return false;
3071}
3072
3073#endif // _USE_SSL_
3074
3075#ifdef _USE_SSL_
3076// SSL client certificate verification test:
3077// 1) default policy (verify peer) must REJECT a server presenting an
3078// untrusted (self-signed) certificate,
3079// 2) with allowselfsigned enabled the same connection must be ACCEPTED,
3080// 3) the SSL_CTX verify mode must reflect the policy in both cases.
3081bool NetworkManager::UnitTestSSLVerify() {
3082
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;
3088 NetworkChannel* con = NULL;
3089 bool ok = true;
3090
3091 unittest::progress(0, "checking ctx verify modes");
3092 {
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());
3096 delete(c1);
3097 return false;
3098 }
3099 delete(c1);
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());
3104 delete(c2);
3105 return false;
3106 }
3107 delete(c2);
3108 unittest::detail("ctx verify modes correct (PEER by default, NONE when allowselfsigned)");
3109 }
3110
3111 unittest::progress(20, "generating self-signed certificate");
3112 if (!generateSelfSignedCert(certPath, keyPath)) {
3113 unittest::fail("could not generate self-signed certificate");
3114 return false;
3115 }
3116
3117 HTTPTestServer* testServer = new HTTPTestServer();
3118 NetworkManager* manager = new NetworkManager();
3119 manager->setSSLCertificate(certPath, keyPath);
3120
3121 unittest::progress(35, "starting SSL server with self-signed cert");
3122 NetworkChannel* listen = manager->createListener(38113, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3123 if (!listen) {
3124 unittest::fail("could not start SSL listener on port 38113");
3125 ok = false;
3126 goto done;
3127 }
3128
3129 unittest::progress(50, "connecting with verification ON (must be rejected)");
3130 // default: sslAllowSelfSigned = -1 -> process default (false) -> verify peer
3131 con = manager->createTCPConnection("localhost", 38113, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3132 if (con) {
3133 unittest::fail("connection to untrusted self-signed server was ACCEPTED with verification on");
3134 con->endConnection(conid);
3135 ok = false;
3136 goto done;
3137 }
3138 unittest::detail("verification on: TLS handshake to self-signed server correctly rejected");
3139
3140 unittest::progress(75, "connecting with allowselfsigned=true (must be accepted)");
3141 manager->setSSLAllowSelfSigned(true);
3142 con = manager->createTCPConnection("localhost", 38113, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3143 if (!con) {
3144 unittest::fail("connection to self-signed server FAILED despite allowselfsigned=true");
3145 ok = false;
3146 goto done;
3147 }
3148 unittest::detail("allowselfsigned: TLS handshake to self-signed server accepted");
3149 con->endConnection(conid);
3150
3151done:
3152 unittest::progress(95, "shutting down");
3153 delete(manager);
3154 delete(testServer);
3156 unlink(certPath);
3157 unlink(keyPath);
3158 unittest::progress(100, "done");
3159 return ok;
3160}
3161#endif // _USE_SSL_
3162
3163#ifdef _USE_SSL_
3164// Helpers for the hostname/CA verification test: generate a throwaway CA and
3165// leaf certificates signed by it (with a SAN), all at runtime in temp files.
3166static EVP_PKEY* sslTestGenKey() {
3167 return EVP_RSA_gen(2048);
3168}
3169
3170static bool sslTestWritePEM(X509* cert, EVP_PKEY* key, const char* certPath, const char* keyPath) {
3171 bool ok = false;
3172 FILE* cf = fopen(certPath, "wb");
3173 FILE* kf = keyPath ? fopen(keyPath, "wb") : NULL;
3174 if (cf && PEM_write_X509(cf, cert) == 1)
3175 ok = true;
3176 if (ok && keyPath)
3177 ok = (kf && PEM_write_PrivateKey(kf, key, NULL, NULL, 0, NULL, NULL) == 1);
3178 if (cf) fclose(cf);
3179 if (kf) fclose(kf);
3180 return ok;
3181}
3182
3183static bool sslTestAddExt(X509* cert, X509* issuer, int nid, const char* value) {
3184 X509V3_CTX ctx;
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);
3188 if (!ext)
3189 return false;
3190 X509_add_ext(cert, ext, -1);
3191 X509_EXTENSION_free(ext);
3192 return true;
3193}
3194
3195// Create a self-signed CA certificate (CA:TRUE) and return the key/cert
3196static bool sslTestGenCA(const char* caCertPath, EVP_PKEY** caKeyOut, X509** caCertOut) {
3197 EVP_PKEY* key = sslTestGenKey();
3198 if (!key)
3199 return false;
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; }
3216 *caKeyOut = key;
3217 *caCertOut = x;
3218 return true;
3219}
3220
3221// Create a leaf (server) certificate with the given CN and SAN, signed by the CA
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();
3225 if (!key)
3226 return false;
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);
3242 X509_free(x);
3243 EVP_PKEY_free(key);
3244 return ok;
3245}
3246
3247// SSL hostname + custom CA verification test:
3248// 1) a CA-signed server is REJECTED when the CA is not provided (verify on),
3249// 2) the same server is ACCEPTED when its CA is given via setSSLCALocation (SSL_CTX_load_verify_locations) and the cert matches the hostname,
3250// 3) a trusted (CA-signed) server whose cert is for a DIFFERENT hostname is
3251// REJECTED when connecting by hostname (SSL_set1_host check),
3252// 4) the same mismatching server is ACCEPTED when connecting by IP literal
3253// (no hostname known -> chain-of-trust check only), proving 3) failed on
3254// the hostname check and IP/raw-address paths are not broken.
3255bool NetworkManager::UnitTestSSLHostCA() {
3256
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;
3270 NetworkChannel* con = NULL;
3271 HTTPTestServer* testServer = NULL;
3272 NetworkManager* goodManager = NULL;
3273 NetworkManager* badManager = NULL;
3274 NetworkChannel* listen = NULL;
3275 bool ok = true;
3276
3277 unittest::progress(0, "generating test CA and CA-signed certificates");
3278 if (!sslTestGenCA(caCertPath, &caKey, &caCert)) {
3279 unittest::fail("could not generate test CA");
3280 return false;
3281 }
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);
3286 unlink(caCertPath);
3287 return false;
3288 }
3289 unittest::detail("CA + leaf certs generated (SAN localhost / wronghost.example)");
3290
3291 testServer = new HTTPTestServer();
3292
3293 // --- Server presenting the CORRECT hostname cert (CN/SAN localhost) ---
3294 goodManager = new NetworkManager();
3295 goodManager->setSSLCertificate(goodCertPath, goodKeyPath);
3296 unittest::progress(20, "starting SSL server with CA-signed localhost cert");
3297 listen = goodManager->createListener(38114, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3298 if (!listen) {
3299 unittest::fail("could not start SSL listener on port 38114");
3300 ok = false;
3301 goto done;
3302 }
3303
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);
3306 if (con) {
3307 unittest::fail("CA-signed server was ACCEPTED although its CA was not provided");
3308 con->endConnection(conid);
3309 ok = false;
3310 goto done;
3311 }
3312 unittest::detail("no CA provided: CA-signed server correctly rejected");
3313
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);
3317 if (!con) {
3318 unittest::fail("connection FAILED despite custom CA provided and matching hostname");
3319 ok = false;
3320 goto done;
3321 }
3322 unittest::detail("custom CA + matching hostname: TLS handshake accepted");
3323 con->endConnection(conid);
3324
3325 // --- Server presenting a MISMATCHING hostname cert (CN/SAN wronghost.example) ---
3326 badManager = new NetworkManager();
3327 badManager->setSSLCertificate(badCertPath, badKeyPath);
3328 badManager->setSSLCALocation(caCertPath, NULL);
3329 unittest::progress(60, "starting SSL server with CA-signed wronghost cert");
3330 listen = badManager->createListener(38115, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3331 if (!listen) {
3332 unittest::fail("could not start SSL listener on port 38115");
3333 ok = false;
3334 goto done;
3335 }
3336
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);
3339 if (con) {
3340 unittest::fail("server with cert for wronghost.example was ACCEPTED for hostname localhost");
3341 con->endConnection(conid);
3342 ok = false;
3343 goto done;
3344 }
3345 unittest::detail("hostname mismatch: trusted cert for wrong host correctly rejected");
3346
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);
3349 if (!con) {
3350 unittest::fail("connection by IP literal FAILED (chain-of-trust only path broken)");
3351 ok = false;
3352 goto done;
3353 }
3354 unittest::detail("IP literal connect: chain-of-trust only, accepted (hostname check skipped)");
3355 con->endConnection(conid);
3356
3357done:
3358 unittest::progress(95, "shutting down");
3359 delete(goodManager);
3360 delete(badManager);
3361 delete(testServer);
3363 X509_free(caCert);
3364 EVP_PKEY_free(caKey);
3365 unlink(caCertPath);
3366 unlink(goodCertPath); unlink(goodKeyPath);
3367 unlink(badCertPath); unlink(badKeyPath);
3368 unittest::progress(100, "done");
3369 return ok;
3370}
3371#endif // _USE_SSL_
3372
3374 const char* host;
3375 uint32 port;
3376 std::vector<std::string>* urls;
3378 uint32 status;
3379};
3380
3382 if (arg == NULL) thread_ret_val(1);
3383
3385 data->status = 1;
3386
3387 uint64 conid;
3388 NetworkChannel* con = NULL;
3389 HTTPRequest* req;
3390 HTTPReply* reply;
3391 uint64 location;
3392 uint32 e;
3393
3395
3396 printf("Connecting...\n");
3397 con = data->manager->createTCPConnection(data->host, data->port, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3398 if (!con) {
3399 printf("Could not connect to host %s:%u...\n\n", data->host, data->port);
3400 data->status = 90;
3401 goto err;
3402 }
3403 data->status = 2;
3404
3405 for (uint32 n=0; n<10; n++) {
3406 // choose a random url...
3407 e = (uint32)utils::RandomValue((double)(data->urls->size()-1));
3408 // printf("Getting %u url: %s...\n\n", e, data->urls->at(e).c_str());
3409
3410 req = new HTTPRequest((uint64)0);
3411 if (!req->createRequest(HTTP_GET, data->host, data->urls->at(e).c_str(), NULL, 0, true, 0)) {
3412 delete(req);
3413 printf("Could not create request [%u]...\n\n", n);
3414 data->status = 98;
3415 goto err;
3416 }
3417
3418 printf("[%u] Sending...\n", n);
3419 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
3420 delete(req);
3421
3422 if (reply == NULL) {
3423 printf("Did not receive reply...\n\n");
3424 data->status = 97;
3425 goto err;
3426 }
3427 else if (reply->type == HTTP_SERVER_UNAVAILABLE) {
3428 printf("Server unavailable...\n\n");
3429 data->status = 96;
3430 goto err;
3431 }
3432 else if (reply->type == HTTP_SERVER_NOREPLY) {
3433 printf("Server no reply...\n\n");
3434 data->status = 95;
3435 goto err;
3436 }
3437 else if (reply->type == HTTP_SERVER_MALFORMED_REPLY) {
3438 printf("Server malformed reply...\n\n");
3439 data->status = 94;
3440 goto err;
3441 }
3442
3443 delete(reply);
3444 }
3445
3446 data->status = 10;
3447 thread_ret_val(0);
3448err:
3449 thread_ret_val(1);
3450}
3451
3453
3454 // Self-contained loopback test: start the websocket-capable HTTP server,
3455 // connect a loopback client, perform a websocket upgrade handshake and
3456 // verify the server replies with a websocket (101) upgrade. This exercises
3457 // the server-side websocket path without waiting for external clients.
3458 uint64 conid;
3459 NetworkChannel* con;
3460 HTTPRequest* req;
3461 HTTPReply* reply;
3462
3463 unittest::progress(0, "starting websocket server");
3464
3465 WebsocketTestServer* testServer = new WebsocketTestServer();
3466 NetworkManager* manager = new NetworkManager();
3467
3468 NetworkChannel* listen = manager->createListener(38103, NOENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3469 if (!listen) {
3470 unittest::fail("NetworkManager websocket test: could not start listening on port 38103");
3471 goto err;
3472 }
3473
3474 unittest::progress(30, "connecting websocket client");
3475 uint64 location;
3476 con = manager->createTCPConnection("localhost", 38103, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3477 if (!con) {
3478 unittest::fail("NetworkManager websocket test: could not connect");
3479 goto err;
3480 }
3481
3482 unittest::progress(55, "sending websocket upgrade handshake");
3483 req = new HTTPRequest((uint64)0);
3484 if (!req->createWebsocketRequest("/", "localhost", NULL, NULL)) {
3485 delete(req);
3486 unittest::fail("NetworkManager websocket test: could not create websocket upgrade request");
3487 goto err;
3488 }
3489 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
3490 delete(req);
3491 if (reply == NULL) {
3492 unittest::fail("NetworkManager websocket test: did not receive handshake reply");
3493 goto err;
3494 }
3495 if (!reply->isWebsocketUpgrade()) {
3496 delete(reply);
3497 unittest::fail("NetworkManager websocket test: reply was not a websocket upgrade");
3498 goto err;
3499 }
3500 unittest::detail("websocket: server confirmed upgrade handshake");
3501 delete(reply);
3502
3503 con->endConnection(conid);
3504
3505 unittest::progress(95, "shutting down");
3506 delete(manager);
3507 delete(testServer);
3509 unittest::progress(100, "done");
3510 return true;
3511err:
3512 delete(manager);
3513 delete(testServer);
3515 return false;
3516
3517}
3518
3519
3520bool NetworkManager::TestHTTP(const char* host, uint32 port, std::vector<std::string> &urls) {
3521
3522 printf("Testing HTTP on %s:%u with random urls...\n\n", host, port);
3523
3524 uint32 loops = 10;
3525 uint32 numThreads = 5;
3526 uint64 conid;
3527 uint64 location;
3528
3529 HTTPServerTestData* data = NULL;
3530
3531 NetworkManager* manager = new NetworkManager();
3532 NetworkChannel* testChannel = manager->createTCPConnection(host, port, NOENC, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
3533 if (!testChannel) {
3534 printf("Could not connect to host %s:%u...\n\n", host, port);
3535 delete(manager);
3536 return false;
3537 }
3538 testChannel->endConnection(conid);
3539 printf("Host is available, starting test...\n\n");
3540
3541 //HTTPRequest* req;
3542 //HTTPReply* reply;
3543
3544 //req = new HTTPRequest((uint64)0);
3545 //if (!req->createRequest(HTTP_GET, host, "/", NULL, 0, true, 0)) {
3546 // delete(req);
3547 // printf("Could not create request...\n\n");
3548 // delete(manager);
3549 // return false;
3550 //}
3551
3552 //reply = testChannel->sendReceiveHTTPRequest(req, conid, 3000);
3553 //delete(req);
3554
3555 //if (reply == NULL) {
3556 // printf("Did not receive reply...\n\n");
3557 // delete(manager);
3558 // return false;
3559 //}
3560 //delete(reply);
3561
3562// for (uint32 n=0; n<100; n++) {
3563// for (uint32 m=0; m<100; m++) {
3564// // choose a random url...
3565// uint32 e = (uint32)utils::RandomValue(urls.size()-1);
3566// printf("Getting url: %s... ", urls.at(e).c_str());
3567//
3568// req = new HTTPRequest((uint64)0);
3569// if (!req->createRequest(HTTP_GET, host, urls.at(e).c_str(), NULL, 0, true, 0)) {
3570// delete(req);
3571// printf("Could not create request [%u]...\n\n", n);
3572// goto err;
3573// }
3574//
3575// reply = testChannel->sendReceiveHTTPRequest(req, conid, 3000);
3576// delete(req);
3577//
3578// if (reply == NULL) {
3579// printf("Did not receive reply...\n\n");
3580// goto err;
3581// }
3582// printf("SUCCESS\n");
3583// delete(reply);
3584// }
3585// testChannel->endConnection(conid);
3586// testChannel = manager->createTCPConnection(host, port, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
3587// if (!testChannel) {
3588// printf("Could not connect %u to host %s:%u...\n\n", n, host, port);
3589// delete(manager);
3590// return false;
3591// }
3592// }
3593//
3594//
3595//err:
3596// delete(manager);
3597// return false;
3598
3599 //testChannel->endConnection(conid);
3600 //printf("Host is available, starting test...\n\n");
3601
3602 uint32 n, m;
3603 uint32 threadIDs[1000];
3604 HTTPServerTestData testData[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;
3611 testData[m].status = 0;
3612 if (!ThreadManager::CreateThread(HTTPServerTest, &testData[m], threadIDs[m])) {
3613 LogPrint(0, LOG_SYSTEM, 0, "Could not start tester thread...");
3614 return false;
3615 }
3616 }
3617 while (true) {
3618 for (m=0; m<numThreads; m++) {
3619 if (ThreadManager::IsThreadRunning(threadIDs[m]))
3620 break;
3621 if (testData[m].status > 10) {
3622 printf("[%u] Failed\n", m);
3623 return false;
3624 }
3625 else {
3626 printf("[%u] Done\n", m);
3627 }
3628 }
3629 if (m >= numThreads)
3630 break;
3631 utils::Sleep(100);
3632 }
3633 }
3634
3635 return true;
3636}
3637
3638
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");
3648#ifdef _USE_SSL_
3649 // Only present in SSL builds (make ssl). Exercises the OpenSSL-backed
3650 // HTTPS/TLS request-reply path end-to-end over loopback.
3651 UnitTestRunner::instance().registerTest("network_https", NetworkManager::UnitTestHTTPS,
3652 "HTTPS (TLS) server/client request/reply over loopback", "network");
3653 UnitTestRunner::instance().registerTest("network_sslverify", NetworkManager::UnitTestSSLVerify,
3654 "SSL client certificate verification (secure by default, allowselfsigned opt-out)", "network");
3655 UnitTestRunner::instance().registerTest("network_sslhostca", NetworkManager::UnitTestSSLHostCA,
3656 "SSL hostname verification (SSL_set1_host) and custom CA location (SSL_CTX_load_verify_locations)", "network");
3657#endif // _USE_SSL_
3658}
3659
3660
3661}
3662
3663
#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_OK
200 OK
#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_GET
GET.
#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_POST
POST.
#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 LOG_SYSTEM
Definition Utils.h:197
#define thread_ret_val(ret)
Definition Utils.h:131
#define THREAD_RET
Definition Utils.h:127
#define stricmp
Definition Utils.h:132
#define LOCALHOSTIP
Definition Utils.h:1642
#define THREAD_FUNCTION_CALL
Definition Utils.h:129
#define LogPrint
Definition Utils.h:313
THREAD_RET(* THREAD_FUNCTION)(void *)
Definition Utils.h:128
#define GETIPADDRESSQUADPORT(a)
Definition Utils.h:1650
#define LOG_NETWORK
Definition Utils.h:198
#define GETIPADDRESSPORT(a, p)
Definition Utils.h:1648
#define THREAD_ARG
Definition Utils.h:130
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.
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.
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...
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
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.
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 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 clearBuffer()
Discard all currently buffered input.
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.
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.
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.
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.
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.
Definition PsyTime.cpp:69
std::string PrintTimeNowString(bool local=true, bool us=true, bool ms=true)
Format GetTimeNow().
Definition PsyTime.cpp:672
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
Definition PsyTime.cpp:722
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
Definition PsyTime.cpp:35
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:3121
std::string GetURIFromURL(std::string url)
Extract the URI (path plus query) from a URL.
Definition HTML.cpp:349
std::string GetProtocolFromURL(std::string url)
Extract the protocol/scheme from a URL.
Definition HTML.cpp:334
std::string GetHostFromURL(std::string url)
Extract the host name (or IP literal) from a URL.
Definition HTML.cpp:291
uint16 GetPortFromURL(std::string url)
Extract the port number from a URL.
Definition HTML.cpp:314
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.
Definition Utils.cpp:9126
double RandomValue()
Uniform random double in [0,1).
Definition Utils.cpp:9132
bool GetLocalIPAddress(uint32 &address)
Get the primary local IPv4 address.
Definition Utils.cpp:7061
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:8080
THREAD_RET THREAD_FUNCTION_CALL HTTPServerTest(THREAD_ARG arg)
static struct PsyType CTRL_TEST
Definition ObjectIDs.h:84
void Register_NetworkManager_Tests()
Hierarchical message type identifier — the key used for publish/subscribe matching in Psyclone.
Definition Types.h:123
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.