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 true;
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
1576 NetworkThread* thread = connectionThreads[conid];
1577 if ((thread == NULL) || (thread->con == NULL))
1578 return false;
1579 bool res = MessageProtocol::SendMessage(thread->con, msg);
1580 if (!res)
1581 int a = 1;
1582 return res;
1583}
1584
1586 NetworkThread* thread = connectionThreads[conid];
1587 if ((thread == NULL) || (thread->con == NULL))
1589
1590 if (!HTTPProtocol::SendHTTPRequest(thread->con, req))
1592
1593 return waitForHTTPReply(conid, timeout);
1594 //return HTTPProtocol::ReceiveHTTPReply(thread->con, timeout);
1595}
1596
1598 NetworkThread* thread = connectionThreads[conid];
1599 if ((thread == NULL) || (thread->con == NULL))
1600 return 0;
1601 return thread->con->getOutputSpeed();
1602}
1603
1604uint32 NetworkChannel::getInputSpeed(uint64 conid) {
1605 NetworkThread* thread = connectionThreads[conid];
1606 if ((thread == NULL) || (thread->con == NULL))
1607 return 0;
1608 return thread->con->getInputSpeed();
1609}
1610
1612 NetworkThread* thread = connectionThreads[conid];
1613 if ((thread == NULL) || (thread->con == NULL))
1614 return false;
1615
1616 if (HTTPProtocol::SendHTTPRequest(thread->con, req)) {
1617 // if (thread->lastRequest != NULL)
1618 // delete(thread->lastRequest);
1619 // thread->lastRequest = new HTTPRequest(req);
1620 return true;
1621 }
1622 else {
1623 return false;
1624 }
1625}
1626
1628 if (!receiver || !receiver->receiveHTTPRequest(req, this, conid)) {
1629 queueHTTPRequestsMutex.enter();
1630 queueHTTPRequests.push(req);
1632 queueHTTPRequestsMutex.leave();
1633 }
1634 return true;
1635}
1636
1638 if (!receiver || !receiver->receiveWebsocketData(wsData, this, conid)) {
1640 queueWebsocketData.push(wsData);
1643 }
1644 return true;
1645}
1646
1647bool NetworkChannel::enterHTTPReply(HTTPReply* reply, HTTPRequest* req, uint64 conid) {
1648 if (!receiver || !receiver->receiveHTTPReply(reply, req, this, conid)) {
1649 queueHTTPRepliesMutex.enter();
1650 queueHTTPReplies.push(reply);
1652 queueHTTPRepliesMutex.leave();
1653 }
1654 return true;
1655}
1656
1658 if (!receiver || !receiver->receiveMessage(msg, this, conid)) {
1659 queueMessagesMutex.enter();
1660 queueMessages.push(msg);
1661 queueMessageConIDs.push(conid); // remember which connection it arrived on
1662 // LogPrint(0,0,0,"Queue size is now: %u (%u)", (uint32)queueMessages.size(), msg->getType()[15]);
1663 queueMessagesSemaphore.signal();
1664 queueMessagesMutex.leave();
1665 }
1666 return true;
1667}
1668
1670 if (!receiver || !receiver->receiveTelnetLine(line, this, conid)) {
1671 queueTelnetLinesMutex.enter();
1672 queueTelnetLines.push(line);
1674 queueTelnetLinesMutex.leave();
1675 }
1676 return true;
1677}
1678
1679
1680bool NetworkChannel::enterNetworkEvent(uint8 type, uint8 protocol, uint64 conid) {
1681 NetworkEvent* ev = new NetworkEvent;
1682 ev->cid = this->cid;
1683 ev->conid = conid;
1684 ev->time = GetTimeNow();
1685 ev->type = type;
1686 ev->protocol = protocol;
1687 if (!receiver || !receiver->receiveNetworkEvent(ev, this, conid)) {
1688 eventQueueMutex.enter();
1689 eventQueue.push(ev);
1690 eventQueueSemaphore.signal();
1691 eventQueueMutex.leave();
1692 }
1693 return true;
1694}
1695
1696uint64 NetworkChannel::autoDetectConnection(NetworkConnection* con, uint16 port, uint32 autoProtocols, uint32 autoProtocolTimeout, uint32 defaultProtocol, bool isAsync, bool autoreconnect) {
1697 // Multiplexes several protocols on one listening port: registers the fresh
1698 // connection and spawns ConnectionAutodetectRun, which peeks the first
1699 // bytes and asks each enabled protocol's CheckBufferForCompatibility()
1700 // (HTTP request line? DataMessage signature? plain text?) before handing
1701 // the connection to the matching per-protocol thread (HTTPServerRun,
1702 // MessageConnectionRun, ...). Falls back to defaultProtocol when nothing
1703 // matches within autoProtocolTimeout ms.
1704 channelMutex.enter(1000);
1705
1706 uint64 conid = manager->addConnection(this);
1707 if (!conid) {
1708 channelMutex.leave();
1709 return 0;
1710 }
1711 NetworkThread* thread = new NetworkThread(this, conid);
1712 thread->defaultProtocol = defaultProtocol;
1713 thread->autoProtocols = autoProtocols;
1714 thread->autoProtocolTimeout = autoProtocolTimeout;
1715 thread->autoreconnect = autoreconnect;
1716 thread->isAsync = isAsync;
1717 thread->parent = this;
1718 // Try connecting...
1719 thread->con = con;
1720
1721 connectionThreads[conid] = thread;
1722
1723 // Start the thread
1725 connectionThreads.erase(conid);
1726 // delete the thread object, but leave the con object alone to be managed by the calling function
1727 thread->con = NULL;
1728 delete(thread);
1729 channelMutex.leave();
1730 return 0;
1731 }
1732 if (!ThreadManager::GetThreadHandle(thread->threadID, thread->hWorker))
1733 LogPrint(0, LOG_NETWORK, 1, "Could not fetch ThreadHandle for autodetect thread %u...", thread->threadID);
1734
1735 channelMutex.leave();
1736 return conid;
1737}
1738
1739uint64 NetworkChannel::startConnection(NetworkConnection* con, uint8 protocol, bool isAsync, bool autoreconnect, uint32 timeoutMS) {
1740 channelMutex.enter(1000);
1741 uint64 conid = manager->addConnection(this);
1742 NetworkThread* thread = new NetworkThread(this, conid);
1743 thread->defaultProtocol = protocol;
1744 thread->autoreconnect = autoreconnect;
1745 thread->isAsync = isAsync;
1746 // Try connecting...
1747 thread->con = con;
1748 thread->con->setConnectTimeout(timeoutMS);
1749
1750 THREAD_FUNCTION func = NULL;
1751 switch(protocol) {
1753 func = HTTPServerRun;
1754 break;
1756 func = HTTPClientRun;
1757 break;
1758 case PROTOCOL_MESSAGE:
1759 func = MessageConnectionRun;
1760 break;
1761 case PROTOCOL_TELNET:
1762 func = TelnetServerRun;
1763 break;
1764 default:
1765 break;
1766 }
1767
1768 connectionThreads[conid] = thread;
1769 // Start the thread
1770 if (func) {
1771 if (!ThreadManager::CreateThread(func, thread, thread->threadID)) {
1772 connectionThreads.erase(conid);
1773 delete(thread);
1774 channelMutex.leave();
1775 return 0;
1776 }
1777 if (!ThreadManager::GetThreadHandle(thread->threadID, thread->hWorker))
1778 LogPrint(0, LOG_NETWORK, 1, "Could not fetch ThreadHandle for connection thread %u...", thread->threadID);
1779 }
1780
1781 channelMutex.leave();
1782 return conid;
1783}
1784
1786
1787 NetworkThread* thread = (NetworkThread*) arg;
1788 if ((thread == NULL) || (thread->listener == NULL))
1789 thread_ret_val(1);
1790 thread->isRunning = true;
1791
1792 uint64 conID;
1793 NetworkConnection* con;
1794 while (thread->shouldContinue) {
1795 if ( (con = thread->listener->acceptConnection(50)) != NULL) {
1796 conID = thread->parent->autoDetectConnection(con, thread->port, thread->autoProtocols, thread->autoProtocolTimeout, thread->defaultProtocol, thread->isAsync, false);
1797 if (conID == 0) {
1798 delete(con);
1799 con = NULL;
1800 }
1801 }
1802 }
1803
1804 thread->isRunning = false;
1805 thread_ret_val(0);
1806}
1807
1808
1810
1811 NetworkThread* thread = (NetworkThread*) arg;
1812 if ((thread == NULL) || (thread->con == NULL))
1813 thread_ret_val(1);
1814
1815 thread->isRunning = true;
1816
1817 uint8 protocol = 0;
1818 uint32 maxSize = 1024;
1819 char* buffer = new char[maxSize];
1820 uint32 size = 0;
1821
1822 uint64 start = GetTimeNow();
1823
1824 if (thread->autoProtocols > 0) {
1825 // printf("Autodetecting protocol");
1826 do {
1827 //printf(".");
1828 if (!thread->con->receiveAvailable(buffer, size, maxSize, 10, true)) {
1829 delete(thread->con);
1830 thread->con = NULL;
1831 thread->isRunning = false;
1832 delete [] buffer;
1833 thread_ret_val(1);
1834 }
1835 if (size > 0) {
1836 // utils::PrintBinary(buffer, size, false, "Autodetection");
1837 // printf(" [%u]", size);
1838
1839 if ( (thread->autoProtocols & PROTOCOL_HTTP_SERVER)
1841 protocol = PROTOCOL_HTTP_SERVER;
1842 // printf("Autodetected HTTP protocol after %u ms...\n\n", GetTimeAgeMS(start));
1843 }
1844 else if ( (thread->autoProtocols & PROTOCOL_MESSAGE)
1846 protocol = PROTOCOL_MESSAGE;
1847 // printf("Autodetected Message protocol after %u ms...\n\n", GetTimeAgeMS(start));
1848 }
1849 else if ( (thread->autoProtocols & PROTOCOL_TELNET)
1851 protocol = PROTOCOL_TELNET;
1852 // printf("Autodetected Telnet protocol after %u ms...\n\n", GetTimeAgeMS(start));
1853 }
1854 }
1855 // else
1856 // printf(".", size);
1857 } while (thread->con && (!protocol) && (GetTimeAgeMS(start) < (int32)thread->autoProtocolTimeout) && thread->shouldContinue);
1858 //printf("\n\n");
1859 }
1860
1861 delete [] buffer;
1862
1863 if (!thread->shouldContinue) {
1864 delete(thread->con);
1865 thread->con = NULL;
1866 thread->isRunning = false;
1867 thread_ret_val(1);
1868 }
1869
1870 if (protocol == 0) {
1871 if (thread->defaultProtocol == 0) {
1872 LogPrint(0,LOG_NETWORK,2,"No valid protocol detected for incoming network connection, disconnecting...\n\n");
1873 // con->disconnect();
1874 delete(thread->con);
1875 thread->con = NULL;
1876 thread->isRunning = false;
1877 thread_ret_val(1);
1878 }
1879 else {
1880 protocol = thread->defaultProtocol;
1881 switch(protocol) {
1883 //printf("Choosing default HTTP SERVER protocol after %u ms...\n\n", GetTimeAgeMS(start));
1884 break;
1886 //printf("Choosing default HTTP CLIENT protocol after %u ms...\n\n", GetTimeAgeMS(start));
1887 break;
1888 case PROTOCOL_MESSAGE:
1889 //printf("Choosing default MESSAGE protocol after %u ms...\n\n", GetTimeAgeMS(start));
1890 break;
1891 case PROTOCOL_TELNET:
1892 //printf("Choosing default TELNET protocol after %u ms...\n\n", GetTimeAgeMS(start));
1893 break;
1894 default:
1895 //printf("Choosing default unknown protocol after %u ms...\n\n", GetTimeAgeMS(start));
1896 break;
1897 }
1898 }
1899 }
1900
1901 thread->parent->enterNetworkEvent(NETWORKEVENT_CONNECT, protocol, thread->id);
1902
1903 thread->defaultProtocol = protocol;
1904
1905 THREAD_FUNCTION func = NULL;
1906 switch(protocol) {
1908 return HTTPServerRun(thread);
1910 return HTTPClientRun(thread);
1911 case PROTOCOL_MESSAGE:
1912 return MessageConnectionRun(thread);
1913 case PROTOCOL_TELNET:
1914 return TelnetServerRun(thread);
1915 default:
1916 delete(thread->con);
1917 thread->con = NULL;
1918 thread->isRunning = false;
1919 thread_ret_val(1);
1920 }
1921
1922
1923}
1924
1925
1927
1928 NetworkThread* thread = (NetworkThread*) arg;
1929 if ((thread == NULL) || (thread->con == NULL))
1930 thread_ret_val(1);
1931 thread->isRunning = true;
1932
1933 bool disconnected = false;
1934
1935 bool upgradedToWebsocket = false;
1936
1937 HTTPReply* reply;
1938 WebsocketData* wsData;
1939
1940 // The main job here is to check for disconnects and to auto-reconnect
1941
1942 while (thread->shouldContinue) {
1943 if (thread->con->isConnected()) {
1944 //utils::Sleep(50);
1945 if (upgradedToWebsocket) {
1946 wsData = HTTPProtocol::ReceiveWebsocketData(thread->con, 500);
1947 if (wsData) {
1948 if (wsData->isTerminationRequest()) {
1949 delete(wsData);
1950 // We can now terminate the connection
1952 { // capture before clearing isRunning: once false, shutdown()/endConnection()
1953 // may free this NetworkThread while we are still on our way out
1954 NetworkChannel* endParent = thread->parent;
1955 uint64 endConid = thread->id;
1956 thread->isRunning = false;
1957 endParent->endConnection(endConid);
1958 }
1959 thread_ret_val(1);
1960 }
1961 else {
1962 //printf("--- HTTP Server thread got new Websocket data...\n");
1963 if (!thread->parent->enterWebsocketData(wsData, thread->id)) {
1965 delete(wsData);
1966 }
1967 }
1968 }
1969 }
1970 else {
1971 // printf("--- HTTP Server thread receiving...\n");
1972 reply = HTTPProtocol::ReceiveHTTPReply(thread->con, 100);
1973 if (reply) {
1974 // Check for Websocket upgrade
1975 if (reply->isWebsocketUpgrade()) {
1976 // Consider the connection upgraded
1977 upgradedToWebsocket = true;
1978 // keep the reply so the main thread knows that the upgrade was successful
1979 }
1980 // printf("--- HTTP Client thread got new reply (%s)...\n", reply);
1981 if (!thread->parent->enterHTTPReply(reply, NULL, thread->id)) {
1983 delete(reply);
1984 }
1985 }
1986 }
1987 }
1988 else if (thread->autoreconnect) {
1989 if (!disconnected) {
1991 disconnected = true;
1992 }
1993 if (!thread->con->reconnect(1000)) {
1994 utils::Sleep(50);
1995 }
1996 else {
1997 // if we have greetingData send it now
1998 if (thread->con->greetingData && thread->con->greetingSize) {
1999 if (!thread->con->send(thread->con->greetingData, thread->con->greetingSize)) {
2001 continue;
2002 }
2003 }
2005 disconnected = false;
2006 }
2007 }
2008 else {
2010 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2011 // may free this NetworkThread while we are still on our way out
2012 NetworkChannel* endParent = thread->parent;
2013 uint64 endConid = thread->id;
2014 thread->isRunning = false;
2015 endParent->endConnection(endConid);
2016 }
2017 thread_ret_val(1);
2018 }
2019 }
2020
2021 // printf("9");
2022 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2023 // may free this NetworkThread while we are still on our way out
2024 NetworkChannel* endParent = thread->parent;
2025 uint64 endConid = thread->id;
2026 thread->isRunning = false;
2027 endParent->endConnection(endConid);
2028 }
2029 thread_ret_val(0);
2030}
2031
2033 // Per-connection HTTP server thread. Loops receiving HTTPRequests (500ms
2034 // poll) and dispatching them to the channel owner; when a request turns
2035 // out to be an RFC 6455 upgrade handshake the thread answers with the 101
2036 // Sec-WebSocket-Accept reply and flips into WebSocket mode for the rest of
2037 // the connection's life, thereafter parsing frames instead of requests and
2038 // answering CLOSE with a termination confirmation. So one listener port
2039 // serves both plain HTTP and long-lived WebSocket sessions.
2040
2041 // printf("--- Starting new HTTP Server thread...\n");
2042 NetworkThread* thread = (NetworkThread*) arg;
2043 if ((thread == NULL) || (thread->con == NULL))
2044 thread_ret_val(1);
2045 thread->isRunning = true;
2046
2047 bool disconnected = false;
2048
2049 bool upgradedToWebsocket = false;
2050 std::string wsOrigin;
2051
2052 HTTPRequest* req = NULL;
2053 HTTPReply* reply = NULL;
2054 WebsocketData* wsData, *wsDataReply;
2055 while (thread->shouldContinue) {
2056 if (thread->con->isConnected()) {
2057
2058 if (upgradedToWebsocket) {
2059 wsData = HTTPProtocol::ReceiveWebsocketData(thread->con, 500);
2060 if (wsData) {
2061 if (wsData->isTerminationRequest()) {
2062 LogPrint(0, LOG_NETWORK, 2, "Client requested termination of Websocket %llu", thread->id);
2064 if (!thread->parent->sendWebsocketData(wsDataReply, thread->id)) {
2065 }
2066 delete(wsDataReply);
2067 delete(wsData);
2068 // For now, leave the connection running until the client terminates
2069 }
2070 else {
2071 //printf("--- HTTP Server thread got new Websocket data...\n");
2072 if (!thread->parent->enterWebsocketData(wsData, thread->id)) {
2074 delete(req);
2075 }
2076 }
2077 }
2078 }
2079 else {
2080 // printf("--- HTTP Server thread receiving...\n");
2081 req = HTTPProtocol::ReceiveHTTPRequest(thread->con, 500);
2082 if (req) {
2083 LogPrint(0, LOG_NETWORK, 5, "Received incoming HTTP request, header size %u, content length: %u", req->headerLength, req->contentLength);
2084 // Check for Websocket upgrade
2085 if (req->isWebsocketUpgrade()) {
2086 const char* origin = req->getHeaderEntry("Origin");
2087 const char* key = req->getHeaderEntry("Sec-WebSocket-Key");
2088 const char* version = req->getHeaderEntry("Sec-WebSocket-Version");
2089 if (key && version) {
2090 if (origin)
2091 wsOrigin = origin;
2092
2093 // reply with confirmation
2094 reply = HTTPReply::CreateWebsocketHTTPReply(key, version);
2095 if (!reply)
2097 if (!thread->parent->sendHTTPReply(reply, thread->id)) {
2098 LogPrint(0, LOG_NETWORK, 1, "Unable to upgrade HTTP Server %llu to Websocket", thread->id);
2100 }
2101 // Consider the connection upgraded
2102 LogPrint(0, LOG_NETWORK, 2, "Upgraded HTTP Server %llu to Websocket", thread->id);
2103 upgradedToWebsocket = true;
2104 }
2105 else {
2106 // reply with error
2108 if (!thread->parent->sendHTTPReply(reply, thread->id)) {
2109 LogPrint(0, LOG_NETWORK, 1, "Unable to upgrade HTTP Server %llu to Websocket, key and/or version not provided", thread->id);
2111 }
2112 }
2113 delete(req);
2114 }
2115 else {
2116 // printf("--- HTTP Server thread got new request (%s)...\n", req->getRequest());
2117 if (!thread->parent->enterHTTPRequest(req, thread->id)) {
2119 delete(req);
2120 }
2121 }
2122 }
2123 }
2124 }
2125 else {
2127 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2128 // may free this NetworkThread while we are still on our way out
2129 NetworkChannel* endParent = thread->parent;
2130 uint64 endConid = thread->id;
2131 thread->isRunning = false;
2132 // printf("--- Disconnect, HTTP Server thread exit...\n");
2133 endParent->endConnection(endConid);
2134 }
2135 thread_ret_val(1);
2136 }
2137 }
2138
2139 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2140 // may free this NetworkThread while we are still on our way out
2141 NetworkChannel* endParent = thread->parent;
2142 uint64 endConid = thread->id;
2143 thread->isRunning = false;
2144 // printf("--- Finish, HTTP Server thread exit...\n");
2145 endParent->endConnection(endConid);
2146 }
2147 thread_ret_val(0);
2148}
2149
2151 // Per-connection binary-message thread: short (30ms) blocking receives so
2152 // the shouldContinue flag is honoured promptly, pushing every complete
2153 // DataMessage into the channel (async callback or sync wait queue). When
2154 // the connection was created with autoreconnect, a drop is surfaced as
2155 // NETWORKEVENT_DISCONNECT_RETRYING and the thread itself keeps calling
2156 // reconnect() until the link is back (then NETWORKEVENT_RECONNECT) — this
2157 // is the transparent-reconnect machinery the Request* classes rely on.
2158
2159 //uint32 tt;
2160 //utils::GetCurrentThreadOSID(tt);
2161
2162 //LogPrint(0, 0, 0, "%u ************** ReceiveMessage starting ******************", tt);
2163 NetworkThread* thread = (NetworkThread*) arg;
2164 if ((thread == NULL) || (thread->con == NULL))
2165 thread_ret_val(1);
2166 thread->isRunning = true;
2167 uint64 remoteAddr;
2168 bool disconnected = false;
2169 bool wasConnected = false;
2170
2171 uint64 t;
2172 DataMessage* msg;
2173 while (thread->shouldContinue) {
2174 if (thread->con->isConnected()) {
2175 //printf("[%llu]", thread->id);
2176 wasConnected = true;
2177 t = GetTimeNow();
2178 msg = MessageProtocol::ReceiveMessage(thread->con, 30);
2179 //if (GetTimeAge(t) > 200) {
2180 //if (msg)
2181 // LogPrint(0,0,0,"%u ************** ReceiveMessage msg (ref %llu) took %s ******************", tt, msg->getReference(), PrintTimeDifString(GetTimeAge(t)).c_str());
2182 //else
2183 // LogPrint(0,0,0,"%u ************** ReceiveMessage NULL took %s ******************", PrintTimeDifString(GetTimeAge(t)).c_str());
2184 //}
2185 if (msg) {
2186 //t = GetTimeNow();
2187 if (!thread->parent->enterMessage(msg, thread->id)) {
2189 delete(msg);
2190 }
2191 }
2192 }
2193 else if (thread->autoreconnect) {
2194 //LogPrint(0,0,0,"************** ReceiveMessage auto reconnect ******************");
2195 if (!disconnected && wasConnected) {
2197 disconnected = true;
2198 }
2199 remoteAddr = thread->con->getRemoteAddress();
2200 //LogPrint(0,0,0,"************** ReceiveMessage %llu auto reconnecting to %u.%u.%u.%u:%u ******************",
2201 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2202 t = GetTimeNow();
2203 if (!thread->con->reconnect(1000)) {
2204 //printf("-%llu*%d-", thread->id, GetTimeAgeMS(t));
2205 //LogPrint(0, 0, 0, "-------------- ReceiveMessage %llu FAILED reconnecting to %u.%u.%u.%u:%u --------------",
2206 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2207 utils::Sleep(50);
2208 }
2209 else {
2210 //printf("<%llu>", thread->id);
2211 utils::Sleep(20);
2212 if (thread->con->isConnected()) {
2213 // ######### if we have greetingData send it now
2214 if (thread->con->greetingData && thread->con->greetingSize) {
2215 if (!thread->con->send(thread->con->greetingData, thread->con->greetingSize)) {
2217 //LogPrint(0, 0, 0, "-------------- ReceiveMessage %llu FAILED sending greeting to %u.%u.%u.%u:%u --------------",
2218 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2219 continue;
2220 }
2221 else {
2222 //LogPrint(0, 0, 0, "!!!!!!!!!!!!!! ReceiveMessage %llu SUCCESS sending greeting to %u.%u.%u.%u:%u !!!!!!!!!!!!!!",
2223 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2224 }
2225 }
2226 else {
2227 // LogPrint(0, 0, 0, "!!!!!!!!!!!!!! ReceiveMessage %llu SUCCESS reconnecting to %u.%u.%u.%u:%u !!!!!!!!!!!!!!",
2228 // thread->id, GETIPADDRESSQUADPORT(remoteAddr));
2229 }
2231 disconnected = false;
2232 }
2233 // otherwise, it didn't work anyway...
2234 }
2235 }
2236 else {
2237 //printf("!%llu!", thread->id);
2238 //LogPrint(0,0,0,"%u ************** ReceiveMessage disconnect ******************", tt);
2240 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2241 // may free this NetworkThread while we are still on our way out
2242 NetworkChannel* endParent = thread->parent;
2243 uint64 endConid = thread->id;
2244 thread->isRunning = false;
2245 endParent->endConnection(endConid);
2246 }
2247 thread_ret_val(1);
2248 }
2249 }
2250
2251 //LogPrint(0,0,0,"%u ************** ReceiveMessage returning ******************", tt);
2252 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2253 // may free this NetworkThread while we are still on our way out
2254 NetworkChannel* endParent = thread->parent;
2255 uint64 endConid = thread->id;
2256 thread->isRunning = false;
2257 endParent->endConnection(endConid);
2258 }
2259 thread_ret_val(0);
2260}
2261
2263
2264 NetworkThread* thread = (NetworkThread*) arg;
2265 if ((thread == NULL) || (thread->con == NULL))
2266 thread_ret_val(1);
2267 thread->isRunning = true;
2268
2269 bool disconnected = false;
2270
2271 TelnetLine* line;
2272 while (thread->shouldContinue) {
2273 if (thread->con->isConnected(thread->isAsync ? 0 : 50)) {
2274 if (thread->isAsync) {
2275 line = TelnetProtocol::ReceiveTelnetLine(thread->con, 50);
2276 if (line) {
2277 if (!thread->parent->enterTelnetLine(line, thread->id)) {
2279 delete(line);
2280 }
2281 }
2282 }
2283 else {
2284 utils::Sleep(50);
2285 }
2286 //else {
2287 // if (thread->con->peekStream() < 0) {
2288 // thread->parent->enterNetworkEvent(NETWORKEVENT_DISCONNECT, thread->defaultProtocol, thread->id);
2289 // thread->isRunning = false;
2290 // thread_ret_val(1);
2291 // }
2292 //}
2293 }
2294 else {
2296 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2297 // may free this NetworkThread while we are still on our way out
2298 NetworkChannel* endParent = thread->parent;
2299 uint64 endConid = thread->id;
2300 thread->isRunning = false;
2301 endParent->endConnection(endConid);
2302 }
2303 thread_ret_val(1);
2304 }
2305 }
2306
2307 { // capture before clearing isRunning: once false, shutdown()/endConnection()
2308 // may free this NetworkThread while we are still on our way out
2309 NetworkChannel* endParent = thread->parent;
2310 uint64 endConid = thread->id;
2311 thread->isRunning = false;
2312 endParent->endConnection(endConid);
2313 }
2314 thread_ret_val(0);
2315}
2316
2318
2319 // Self-contained loopback test: start a local listener, then open several
2320 // delayed (greeting) TCP connections to it on a high localhost port and
2321 // confirm the listener receives the greeting message on each connection.
2322 const uint16 PORT = 38101;
2323 const uint32 CONNECTIONS = 3;
2324
2325 unittest::progress(0, "starting delayed-connect listener");
2326
2327 NetworkManager* manager = new NetworkManager();
2328
2329 NetworkChannel* listen = manager->createListener(PORT, NOENC, PROTOCOL_MESSAGE, true, 0, true, 0, NULL);
2330 if (!listen) {
2331 unittest::fail("NetworkManager delayed-connect test: could not start listening on port %u", PORT);
2332 delete(manager);
2334 return false;
2335 }
2336
2337 DataMessage* msgConnect = new DataMessage();
2338 msgConnect->setString("URI", "ExecutorConnect");
2339
2340 std::vector<uint64> conIDs;
2341 uint64 conid = 0;
2342 uint64 location = 0;
2343 NetworkChannel* channel = NULL;
2344
2345 unittest::progress(25, "opening delayed connections");
2346 channel = manager->addTCPConnection("localhost", PORT, NOENC, PROTOCOL_MESSAGE, true, 0, NULL, conid, location, 1000, (char*)msgConnect->data, msgConnect->getSize());
2347 if (!channel || !conid) {
2348 unittest::fail("NetworkManager delayed-connect test: could not create channel for connection 1");
2349 goto err;
2350 }
2351 conIDs.push_back(conid);
2352 for (uint32 n = 1; n < CONNECTIONS; n++) {
2353 conid = channel->addTCPConnection("localhost", PORT, NOENC, PROTOCOL_MESSAGE, true, location, 1000, (char*)msgConnect->data, msgConnect->getSize());
2354 if (!conid) {
2355 unittest::fail("NetworkManager delayed-connect test: could not add connection %u", n + 1);
2356 goto err;
2357 }
2358 conIDs.push_back(conid);
2359 }
2360
2361 unittest::progress(60, "receiving greeting messages");
2362 for (uint32 n = 0; n < CONNECTIONS; n++) {
2363 uint64 recvid = 0;
2364 DataMessage* in = listen->waitForMessage(recvid, 2000);
2365 if (!in) {
2366 unittest::fail("NetworkManager delayed-connect test: greeting %u not received", n + 1);
2367 goto err;
2368 }
2369 unittest::detail("delayed-connect: received greeting %u", n + 1);
2370 delete(in);
2371 }
2372
2373 unittest::progress(90, "shutting down");
2374 delete msgConnect;
2375 delete(manager);
2377 unittest::progress(100, "done");
2378 return true;
2379err:
2380 delete msgConnect;
2381 delete(manager);
2383 return false;
2384
2385}
2386
2388
2389 uint32 size = 70000;
2390
2391 char* dat;
2392 uint64 conid = 0;
2393 uint64 conid2 = 0;
2394 uint32 address;
2395 uint64 destination;
2396 DataMessage* msg, *msg2;
2397 uint64 start = GetTimeNow();
2398 uint32 count = 100, subcount = 100, subcount2 = 10, n, m, k;
2399 uint64 t, end;
2400 NetworkChannel* con = NULL;
2401 PsyType type = { { 1,10001,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };
2402
2403 uint64 recvid;
2404
2405 unittest::progress(0, "starting TCP listener");
2406
2407 NetworkManager* manager = new NetworkManager();
2408
2409 NetworkChannel* listen;
2410
2411 // Retry the bind briefly: when this test is run repeatedly back-to-back the
2412 // previous run's port can still be releasing, so a single bind attempt can
2413 // transiently fail with "address in use". Only sleeps on failure, so a normal
2414 // run is unaffected.
2415 listen = NULL;
2416 for (int tries = 0; tries < 20 && !listen; tries++) {
2417 listen = manager->createListener(38100, NOENC, PROTOCOL_MESSAGE, true, 0, true, 0, NULL);
2418 if (!listen) utils::Sleep(100);
2419 }
2420 if (!listen) {
2421 unittest::fail("NetworkManager test: could not start listening on port 38100");
2422 goto err;
2423 }
2424
2425 uint64 location;
2426 con = manager->createTCPConnection("localhost", 38100, NOENC, PROTOCOL_MESSAGE, true, false, 0, NULL, conid, location);
2427 if (!con) {
2428 unittest::fail("NetworkManager test: could not connect");
2429 goto err;
2430 }
2431 dat = new char[size];
2432 memset(dat, 0, size);
2433 msg = new DataMessage(CTRL_TEST, 20);
2434 msg->setData("Test", dat, size);
2435 delete [] dat;
2436 if (!con->sendMessage(msg, conid)) {
2437 unittest::fail("NetworkManager test: could not send message");
2438 goto err;
2439 }
2440 msg2 = listen->waitForMessage(recvid, 10000);
2441 if (!msg2) {
2442 unittest::fail("NetworkManager test: no message received");
2443 goto err;
2444 }
2445 else if ( (msg->getType() != msg2->getType()) || (msg->getFrom() != msg2->getFrom()) ) {
2446 unittest::fail("NetworkManager test: message sent and received mismatch");
2447 goto err;
2448 }
2449 delete(msg2);
2450
2451 unittest::progress(10, "TCP single message round-trip");
2452 start = GetTimeNow();
2453 if (!con->sendMessage(msg, conid)) {
2454 unittest::fail("NetworkManager test: could not send message");
2455 goto err;
2456 }
2457 t = GetTimeNow();
2458 msg2 = listen->waitForMessage(recvid, 10000);
2459
2460 if (!msg2) {
2461 unittest::fail("NetworkManager test: no message received");
2462 goto err;
2463 }
2464 else if ( (msg->getType() != msg2->getType()) || (msg->getFrom() != msg2->getFrom()) ) {
2465 unittest::fail("NetworkManager test: message sent and received mismatch");
2466 goto err;
2467 }
2468 delete(msg2);
2469 end = GetTimeNow();
2470 unittest::detail("TCP single message send %s, receive %s, total %s",
2471 PrintTimeDifString(t - start).c_str(),
2472 PrintTimeDifString(end - t).c_str(),
2473 PrintTimeDifString(end - start).c_str() );
2474
2475 unittest::progress(20, "TCP throughput loop");
2476 t = 0;
2477 count = 10, subcount = 10, subcount2 = 15;
2478 for (m = 0; m < count; m++) {
2479 // LogPrint(0,0,0,"[%u] Starting to send %u msgs...\n", (m+1)*subcount, subcount);
2480 start = GetTimeNow();
2481 for (n = 0; n < subcount; n++) {
2482 type.levels[15] = n;
2483 msg->setType(type);
2484 // t = GetTimeNow();
2485 for (k=0; k<subcount2; k++) {
2486 if (!con->sendMessage(msg, conid)) {
2487 unittest::fail("NetworkManager test: could not send TCP message %u", n);
2488 goto err;
2489 }
2490 }
2491 // printf("SendMessage: %lu\n", GetTimeAgeMS(t));
2492// }
2493 // LogPrint(0,0,0,"[%u] Sent %u msgs...\n", (m+1)*subcount, subcount);
2494 // utils::Sleep(1000);
2495 // printf("Msg Queue size: %u\n\n", (uint32)listen->queueMessages.size());
2496// for (n = 0; n < subcount; n++) {
2497 for (k=0; k<subcount2; k++) {
2498 msg2 = listen->waitForMessage(recvid, 10000);
2499 type.levels[15] = n;
2500 if (!msg2) {
2501 unittest::fail("NetworkManager test: [%u/%u/%u] no TCP message received", n, m, k);
2502 goto err;
2503 }
2504 else if ( (msg2->getType() != type) || (msg->getFrom() != msg2->getFrom()) ) {
2505 unittest::fail("NetworkManager test: [%u] TCP message sent and received mismatch", n);
2506 goto err;
2507 }
2508 delete(msg2);
2509 }
2510 }
2511 end = GetTimeNow();
2512 unittest::detail("[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2513 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2514 subcount*subcount2, subcount*subcount2 * size,
2515 ((double)(end-start))/(subcount*subcount2));
2516 t += end - start;
2517 }
2518
2519 {
2520 uint32 tcpMsgs = count*subcount*subcount2;
2521 double tcpUs = (double)t;
2522 unittest::detail("Total: Sent and received %u TCP msgs, %.3fus per msg", tcpMsgs, tcpUs/tcpMsgs);
2523 unittest::metric("tcp_msg_throughput", (double)tcpMsgs / tcpUs * 1e6, "msg/s", true);
2524 unittest::metric("tcp_avg_latency", tcpUs / tcpMsgs, "us", false);
2525 unittest::metric("tcp_throughput", ((double)tcpMsgs * size) / tcpUs, "MB/s", true);
2526 }
2527
2528 con->endConnection(conid);
2529 delete(msg);
2530
2531 unittest::progress(55, "starting UDP listener");
2532 t = 0;
2533
2534 listen = manager->createUDPConnection(38101, PROTOCOL_MESSAGE, true, true, 0, NULL, conid2);
2535 if (!listen) {
2536 unittest::fail("NetworkManager test: could not start UDP listening on port 38101");
2537 goto err;
2538 }
2539
2540 // Target the loopback address (the UDP listener binds INADDR_ANY) so this
2541 // self-contained test works on any machine. Sending to the LAN interface
2542 // address can silently fail to loop back (firewall / routing), which would
2543 // otherwise hang this phase until the per-test timeout.
2544 address = LOCALHOSTIP;
2545 destination = GETIPADDRESSPORT(address, 38101);
2546
2547 size = 1024;
2548 dat = new char[size];
2549 memset(dat, 0, size);
2550 msg = new DataMessage(CTRL_TEST, 20);
2551 msg->setData("Test", dat, size);
2552 delete [] dat;
2553 // UDP is best-effort: a single datagram can be dropped even on loopback, so
2554 // a strict one-shot round-trip is inherently flaky. Retry a few times and
2555 // require that at least one round-trip succeeds - that verifies UDP
2556 // send/receive works without occasionally failing on a lost packet.
2557 unittest::progress(65, "UDP single message round-trip");
2558 start = GetTimeNow();
2559 {
2560 bool udpOk = false;
2561 for (int attempt = 0; attempt < 10 && !udpOk; attempt++) {
2562 if (!manager->sendUDPMessage(msg, destination)) {
2563 unittest::fail("NetworkManager test: could not send UDP message");
2564 goto err;
2565 }
2566 msg2 = listen->waitForMessage(recvid, 500);
2567 if (!msg2)
2568 continue; // datagram lost - retry
2569 if ( (msg->getType() != msg2->getType()) || (msg->getFrom() != msg2->getFrom()) ) {
2570 delete(msg2);
2571 unittest::fail("NetworkManager test: UDP message sent and received mismatch");
2572 goto err;
2573 }
2574 delete(msg2);
2575 udpOk = true;
2576 }
2577 if (!udpOk) {
2578 unittest::fail("NetworkManager test: no UDP round-trip on port 38101 after 10 attempts (loopback UDP may be blocked here)");
2579 goto err;
2580 }
2581 }
2582 end = GetTimeNow();
2583 unittest::detail("UDP single message round-trip ok in %s", PrintTimeDifString(end - start).c_str());
2584
2585 // UDP is best-effort: dropped datagrams are expected and tolerated (not a
2586 // failure). Cap the whole phase with a time budget and use a short per-read
2587 // wait so a burst of packet loss can never stall the test to the per-test
2588 // timeout.
2589 unittest::progress(70, "UDP throughput loop");
2590 t = 0;
2591 count = 10, subcount = 10, subcount2 = 15;
2592 { // scope the budget locals so the earlier goto err's don't cross them
2593 uint64 udpLoopStart = GetTimeNow();
2594 const int32 udpBudgetMs = 5000;
2595 for (m = 0; m < count && GetTimeAgeMS(udpLoopStart) < udpBudgetMs; m++) {
2596 // LogPrint(0,0,0,"[%u] Starting to send %u msgs...\n", (m+1)*subcount, subcount);
2597 start = GetTimeNow();
2598 for (n = 0; n < subcount; n++) {
2599 type.levels[15] = n;
2600 msg->setType(type);
2601 // t = GetTimeNow();
2602 for (k=0; k<subcount2; k++) {
2603 if (!manager->sendUDPMessage(msg, destination)) {
2604 unittest::fail("NetworkManager test: could not send UDP message %u", n);
2605 goto err;
2606 }
2607 }
2608 // printf("SendMessage: %lu\n", GetTimeAgeMS(t));
2609// }
2610 // LogPrint(0,0,0,"[%u] Sent %u msgs...\n", (m+1)*subcount, subcount);
2611 // utils::Sleep(1000);
2612 // printf("Msg Queue size: %u\n\n", (uint32)listen->queueMessages.size());
2613// for (n = 0; n < subcount; n++) {
2614 for (k=0; k<subcount2; k++) {
2615 if (GetTimeAgeMS(udpLoopStart) >= udpBudgetMs)
2616 break; // stay within the phase budget regardless of UDP loss
2617 msg2 = listen->waitForMessage(recvid, 50);
2618 if (!msg2) {
2619 t -= 50000; // discount the wait so the rate reflects delivered msgs
2620 continue;
2621 }
2622 delete(msg2);
2623 }
2624 }
2625 end = GetTimeNow();
2626 unittest::detail("[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2627 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2628 subcount*subcount2, subcount*subcount2 * size,
2629 ((double)(end-start))/(subcount*subcount2));
2630 t += end - start;
2631 }
2632 } // end budget-local scope
2633
2634 delete(msg);
2635 {
2636 uint32 udpMsgs = count*subcount*subcount2;
2637 double udpUs = (double)t;
2638 if (udpUs > 0) {
2639 unittest::detail("Total UDP: Sent and received %u msgs, %.3fus per msg", udpMsgs, udpUs/udpMsgs);
2640 unittest::metric("udp_msg_throughput", (double)udpMsgs / udpUs * 1e6, "msg/s", true);
2641 unittest::metric("udp_avg_latency", udpUs / udpMsgs, "us", false);
2642 }
2643 }
2644
2645 unittest::progress(95, "shutting down");
2646 delete(manager);
2648 unittest::progress(100, "done");
2649 return true;
2650err:
2651 delete(manager);
2653 return false;
2654}
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2668
2671
2673// printf("HTTPTestServer received HTTPRequest...\n\n");
2674
2675 char text[512];
2676 snprintf(text, 512, "Hello World %llu", GetTimeNow());
2677
2678 uint64 localAddr = 0;
2679 utils::GetLocalIPAddress(*(uint32*)&localAddr);
2680
2681 HTTPReply* reply = new HTTPReply(localAddr);
2682 if (!reply->createPage(HTTP_OK, GetTimeNow(), "MyServer", GetTimeNow(), true, false, "text/html", text)) {
2683 printf("Error generating HTML page...\n\n");
2684 }
2685
2686 if (!channel->sendHTTPReply(reply, conid))
2687 printf("Error sending response...\n\n");
2688// else
2689// printf("HTTPTestServer sent response...\n\n");
2690
2691 delete(reply);
2692 return true;
2693}
2694
2695
2696
2697
2698
2699
2700
2701
2704
2707
2709 printf("WebsocketTestServer received HTTPRequest...\n\n");
2710 delete(req);
2711 return true;
2712}
2713
2715 uint64 size = 0;
2716 const char* data = wsData->getContent(size);
2717 if (wsData->dataType == wsData->TEXT) {
2718 std::string str = utils::StringFormat("%s - %s", PrintTimeNowString().c_str(), data);
2719 WebsocketData* wsDataOut = new WebsocketData();
2720 wsDataOut->setData(wsData->TEXT, false, str.c_str(), str.length());
2721 channel->sendWebsocketData(wsDataOut, conid);
2722 delete wsDataOut;
2723 }
2724 else {
2725 WebsocketData* wsDataOut = new WebsocketData();
2726 wsDataOut->setData(wsData->BINARY, false, data, size);
2727 channel->sendWebsocketData(wsDataOut, conid);
2728 delete wsDataOut;
2729 }
2730 //printf("WebsocketTestServer received data...\n\n");
2731 delete wsData;
2732 return true;
2733}
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2747
2748// char buffer1[] =
2749//"GET /path/file.html HTTP/1.0\n\
2750//From: someuser@jmarshall.com\n\
2751//User-Agent: HTTPTool/1.0\n\
2752//if-modified-since: Sat, 29 Oct 1994 19:43:31 GMT\n\n";
2753//
2754// char buffer[] =
2755//"POST /path/script.cgi HTTP/1.0\n\
2756//From: frog@jmarshall.com\n\
2757//User-Agent: HTTPTool/1.0\n\
2758//Content-Type: application/x-www-form-urlencoded\n\
2759//Content-Length: 35\n\n\
2760//home=Cosby&favorite+flavor=fl%26ies";
2761//
2762// HTTPRequest* req = new HTTPRequest(0);
2763// if (!req->processHeader(buffer, strlen(buffer)))
2764// return false;
2765//
2766// if (!req->processContent("home=Cosby&favorite+flavor=fl%26ies", 35))
2767// return false;
2768
2769 uint64 conid;
2770 NetworkChannel* con;
2771 HTTPRequest* req;
2772 HTTPReply* reply;
2773 uint32 n, count;
2774 uint64 httpStart, httpEnd;
2775
2776 unittest::progress(0, "starting HTTP server");
2777
2778 HTTPTestServer* testServer = new HTTPTestServer();
2779 NetworkManager* manager = new NetworkManager();
2780
2781 NetworkChannel* listen = manager->createListener(38102, NOENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
2782 if (!listen) {
2783 unittest::fail("NetworkManager HTTP test: could not start listening on port 38102");
2784 goto err;
2785 }
2786
2787 uint64 location;
2788 con = manager->createTCPConnection("localhost", 38102, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
2789 if (!con) {
2790 unittest::fail("NetworkManager HTTP test: could not connect");
2791 goto err;
2792 }
2793 unittest::progress(15, "HTTP single request round-trip");
2794
2795 req = new HTTPRequest((uint64)0);
2796 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
2797 delete(req);
2798 unittest::fail("NetworkManager HTTP test: could not create request");
2799 goto err;
2800 }
2801 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
2802 delete(req);
2803 if (reply == NULL) {
2804 unittest::fail("NetworkManager HTTP test: did not receive reply");
2805 goto err;
2806 }
2807 delete(reply);
2808
2809 unittest::progress(30, "HTTP request loop");
2810 count = 500;
2811 httpStart = GetTimeNow();
2812 for (n=0; n<count; n++) {
2813 if (n && !(n%10)) {
2814 con->endConnection(conid);
2815 con = manager->createTCPConnection("localhost", 38102, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
2816 if (!con) {
2817 unittest::fail("NetworkManager HTTP test: could not reconnect [%u]", n);
2818 goto err;
2819 }
2820 }
2821
2822 req = new HTTPRequest((uint64)0);
2823 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
2824 delete(req);
2825 unittest::fail("NetworkManager HTTP test: could not create request [%u]", n);
2826 goto err;
2827 }
2828 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
2829 delete(req);
2830 if (reply == NULL) {
2831 unittest::fail("NetworkManager HTTP test: did not receive reply [%u]", n);
2832 goto err;
2833 }
2834 delete(reply);
2835 }
2836 httpEnd = GetTimeNow();
2837 {
2838 double httpUs = (double)(httpEnd - httpStart);
2839 unittest::detail("HTTP: %u requests in %.3fms", count, httpUs / 1000.0);
2840 unittest::metric("http_request_rate", (double)count / httpUs * 1e6, "req/s", true);
2841 unittest::metric("http_avg_latency", httpUs / count, "us", false);
2842 }
2843
2844 con->endConnection(conid);
2845
2846
2847 //NetworkChannel* con2 = manager->createConnection("cmlabs.com", 80, PROTOCOL_HTTP_CLIENT, false, 0, NULL, conid);
2848 //if (!con2) {
2849 // printf("Could not connect to cmlabs.com...\n\n");
2850 // goto err;
2851 //}
2852 //printf("Connected...\n\n");
2853
2854 //req = new HTTPRequest((uint64)0);
2855 //if (!req->createRequest(HTTP_GET, "cmlabs.com", "/", NULL, 0, true, 0)) {
2856 // delete(req);
2857 // printf("Could not create request...\n\n");
2858 // goto err;
2859 //}
2860 //printf("Request:\n%s\n", req->data);
2861 //if (!con2->sendHTTPRequest(req, conid)) {
2862 // delete(req);
2863 // printf("Could not send request...\n\n");
2864 // goto err;
2865 //}
2866 //delete(req);
2867
2868 //reply = con2->waitForHTTPReply(conid2, 10000);
2869 //if (reply == NULL) {
2870 // printf("Did not receive reply...\n\n");
2871 // goto err;
2872 //}
2873 //printf("Reply:\n%s\n", reply->data);
2874 //delete(reply);
2875
2876
2877
2878
2879
2880
2881 unittest::progress(95, "shutting down");
2882 delete(manager);
2883 delete(testServer);
2885 unittest::progress(100, "done");
2886 return true;
2887err:
2888 delete(manager);
2889 delete(testServer);
2891 return false;
2892
2893}
2894
2895
2896#ifdef _USE_SSL_
2897
2898// Generate a throwaway RSA self-signed certificate (CN=localhost) and write the
2899// cert and private key to PEM files. Used only by the HTTPS unit test; the SSL
2900// listener loads cert/key from files (SSL_CTX_use_certificate_chain_file /
2901// SSL_CTX_use_PrivateKey_file). Generated at runtime so no key is committed.
2902// Cross-platform temp path for throwaway test cert/key PEM files (/tmp is
2903// POSIX-only; on Windows use %TEMP%).
2904static std::string sslTestTempPath(const char* filename) {
2905#ifdef WINDOWS
2906 const char* t = getenv("TEMP");
2907 if (!t) t = getenv("TMP");
2908 if (!t) t = ".";
2909 return std::string(t) + "\\" + filename;
2910#else
2911 return std::string("/tmp/") + filename;
2912#endif
2913}
2914
2915static bool generateSelfSignedCert(const char* certPath, const char* keyPath) {
2916 bool ok = false;
2917 EVP_PKEY* pkey = EVP_RSA_gen(2048); // OpenSSL 3 one-shot RSA keygen
2918 if (!pkey)
2919 return false;
2920 X509* x509 = X509_new();
2921 if (x509) {
2922 ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
2923 X509_gmtime_adj(X509_getm_notBefore(x509), 0);
2924 X509_gmtime_adj(X509_getm_notAfter(x509), (long)60 * 60 * 24 * 3650); // ~10 years
2925 X509_set_pubkey(x509, pkey);
2926 X509_NAME* name = X509_get_subject_name(x509);
2927 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
2928 (const unsigned char*)"localhost", -1, -1, 0);
2929 X509_set_issuer_name(x509, name); // self-signed: issuer == subject
2930 if (X509_sign(x509, pkey, EVP_sha256())) {
2931 FILE* kf = fopen(keyPath, "wb");
2932 FILE* cf = fopen(certPath, "wb");
2933 if (kf && cf &&
2934 PEM_write_PrivateKey(kf, pkey, NULL, NULL, 0, NULL, NULL) == 1 &&
2935 PEM_write_X509(cf, x509) == 1)
2936 ok = true;
2937 if (kf) fclose(kf);
2938 if (cf) fclose(cf);
2939 }
2940 X509_free(x509);
2941 }
2942 EVP_PKEY_free(pkey);
2943 return ok;
2944}
2945
2946// HTTPS end-to-end test: a self-signed SSL HTTP server + an SSL HTTP client
2947// exchanging real requests over TLS on loopback. Only built/registered when
2948// compiled with `make ssl` (-D _USE_SSL_); proves the OpenSSL-backed SSLENC
2949// path (handshake + encrypted read/write) actually works.
2950bool NetworkManager::UnitTestHTTPS() {
2951
2952 uint64 conid;
2953 NetworkChannel* con = NULL;
2954 HTTPRequest* req;
2955 HTTPReply* reply;
2956 uint32 n, count;
2957 uint64 location;
2958 uint64 httpsStart, httpsEnd;
2959
2960 std::string certPathS = sslTestTempPath("psytest_https_cert.pem");
2961 std::string keyPathS = sslTestTempPath("psytest_https_key.pem");
2962 const char* certPath = certPathS.c_str();
2963 const char* keyPath = keyPathS.c_str();
2964
2965 unittest::progress(0, "generating self-signed certificate");
2966 if (!generateSelfSignedCert(certPath, keyPath)) {
2967 unittest::fail("NetworkManager HTTPS test: could not generate self-signed certificate");
2968 return false;
2969 }
2970
2971 HTTPTestServer* testServer = new HTTPTestServer();
2972 NetworkManager* manager = new NetworkManager();
2973 manager->setSSLCertificate(certPath, keyPath);
2974 // The test server uses a throwaway self-signed cert, so the client side
2975 // must explicitly opt out of peer verification (verification itself is
2976 // covered by the network_sslverify test).
2977 manager->setSSLAllowSelfSigned(true);
2978
2979 unittest::progress(10, "starting HTTPS (SSL) server");
2980 NetworkChannel* listen = manager->createListener(38112, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
2981 if (!listen) {
2982 unittest::fail("NetworkManager HTTPS test: could not start SSL listener on port 38112");
2983 goto err;
2984 }
2985
2986 unittest::progress(25, "HTTPS client connect + TLS handshake");
2987 con = manager->createTCPConnection("localhost", 38112, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
2988 if (!con) {
2989 unittest::fail("NetworkManager HTTPS test: could not connect / TLS handshake failed");
2990 goto err;
2991 }
2992
2993 unittest::progress(45, "HTTPS single request round-trip");
2994 req = new HTTPRequest((uint64)0);
2995 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
2996 delete(req);
2997 unittest::fail("NetworkManager HTTPS test: could not create request");
2998 goto err;
2999 }
3000 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3001 delete(req);
3002 if (reply == NULL) {
3003 unittest::fail("NetworkManager HTTPS test: did not receive reply over TLS");
3004 goto err;
3005 }
3006 delete(reply);
3007
3008 unittest::progress(60, "HTTPS request loop");
3009 count = 50;
3010 httpsStart = GetTimeNow();
3011 for (n=0; n<count; n++) {
3012 if (n && !(n%10)) {
3013 con->endConnection(conid);
3014 con = manager->createTCPConnection("localhost", 38112, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3015 if (!con) {
3016 unittest::fail("NetworkManager HTTPS test: could not reconnect over TLS [%u]", n);
3017 goto err;
3018 }
3019 }
3020 req = new HTTPRequest((uint64)0);
3021 if (!req->createRequest(HTTP_GET, "", "/", NULL, 0, true, 0)) {
3022 delete(req);
3023 unittest::fail("NetworkManager HTTPS test: could not create request [%u]", n);
3024 goto err;
3025 }
3026 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
3027 delete(req);
3028 if (reply == NULL) {
3029 unittest::fail("NetworkManager HTTPS test: did not receive reply over TLS [%u]", n);
3030 goto err;
3031 }
3032 delete(reply);
3033 }
3034 httpsEnd = GetTimeNow();
3035 {
3036 double httpsUs = (double)(httpsEnd - httpsStart);
3037 unittest::detail("HTTPS: %u TLS requests in %.3fms", count, httpsUs / 1000.0);
3038 unittest::metric("https_request_rate", (double)count / httpsUs * 1e6, "req/s", true);
3039 unittest::metric("https_avg_latency", httpsUs / count, "us", false);
3040 }
3041 con->endConnection(conid);
3042
3043 unittest::progress(95, "shutting down");
3044 delete(manager);
3045 delete(testServer);
3047 unlink(certPath);
3048 unlink(keyPath);
3049 unittest::progress(100, "done");
3050 return true;
3051err:
3052 delete(manager);
3053 delete(testServer);
3055 unlink(certPath);
3056 unlink(keyPath);
3057 return false;
3058}
3059
3060#endif // _USE_SSL_
3061
3062#ifdef _USE_SSL_
3063// SSL client certificate verification test:
3064// 1) default policy (verify peer) must REJECT a server presenting an
3065// untrusted (self-signed) certificate,
3066// 2) with allowselfsigned enabled the same connection must be ACCEPTED,
3067// 3) the SSL_CTX verify mode must reflect the policy in both cases.
3068bool NetworkManager::UnitTestSSLVerify() {
3069
3070 std::string certPathS = sslTestTempPath("psytest_sslverify_cert.pem");
3071 std::string keyPathS = sslTestTempPath("psytest_sslverify_key.pem");
3072 const char* certPath = certPathS.c_str();
3073 const char* keyPath = keyPathS.c_str();
3074 uint64 conid = 0, location = 0;
3075 NetworkChannel* con = NULL;
3076 bool ok = true;
3077
3078 unittest::progress(0, "checking ctx verify modes");
3079 {
3080 SSLConnection* c1 = new SSLConnection();
3081 if (!c1->init() || (c1->getVerifyMode() != SSL_VERIFY_PEER)) {
3082 unittest::fail("default client ctx verify mode is not SSL_VERIFY_PEER (got %d)", c1->getVerifyMode());
3083 delete(c1);
3084 return false;
3085 }
3086 delete(c1);
3087 SSLConnection* c2 = new SSLConnection();
3088 c2->setAllowSelfSigned(true);
3089 if (!c2->init() || (c2->getVerifyMode() != SSL_VERIFY_NONE)) {
3090 unittest::fail("allowselfsigned client ctx verify mode is not SSL_VERIFY_NONE (got %d)", c2->getVerifyMode());
3091 delete(c2);
3092 return false;
3093 }
3094 delete(c2);
3095 unittest::detail("ctx verify modes correct (PEER by default, NONE when allowselfsigned)");
3096 }
3097
3098 unittest::progress(20, "generating self-signed certificate");
3099 if (!generateSelfSignedCert(certPath, keyPath)) {
3100 unittest::fail("could not generate self-signed certificate");
3101 return false;
3102 }
3103
3104 HTTPTestServer* testServer = new HTTPTestServer();
3105 NetworkManager* manager = new NetworkManager();
3106 manager->setSSLCertificate(certPath, keyPath);
3107
3108 unittest::progress(35, "starting SSL server with self-signed cert");
3109 NetworkChannel* listen = manager->createListener(38113, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3110 if (!listen) {
3111 unittest::fail("could not start SSL listener on port 38113");
3112 ok = false;
3113 goto done;
3114 }
3115
3116 unittest::progress(50, "connecting with verification ON (must be rejected)");
3117 // default: sslAllowSelfSigned = -1 -> process default (false) -> verify peer
3118 con = manager->createTCPConnection("localhost", 38113, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3119 if (con) {
3120 unittest::fail("connection to untrusted self-signed server was ACCEPTED with verification on");
3121 con->endConnection(conid);
3122 ok = false;
3123 goto done;
3124 }
3125 unittest::detail("verification on: TLS handshake to self-signed server correctly rejected");
3126
3127 unittest::progress(75, "connecting with allowselfsigned=true (must be accepted)");
3128 manager->setSSLAllowSelfSigned(true);
3129 con = manager->createTCPConnection("localhost", 38113, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3130 if (!con) {
3131 unittest::fail("connection to self-signed server FAILED despite allowselfsigned=true");
3132 ok = false;
3133 goto done;
3134 }
3135 unittest::detail("allowselfsigned: TLS handshake to self-signed server accepted");
3136 con->endConnection(conid);
3137
3138done:
3139 unittest::progress(95, "shutting down");
3140 delete(manager);
3141 delete(testServer);
3143 unlink(certPath);
3144 unlink(keyPath);
3145 unittest::progress(100, "done");
3146 return ok;
3147}
3148#endif // _USE_SSL_
3149
3150#ifdef _USE_SSL_
3151// Helpers for the hostname/CA verification test: generate a throwaway CA and
3152// leaf certificates signed by it (with a SAN), all at runtime in temp files.
3153static EVP_PKEY* sslTestGenKey() {
3154 return EVP_RSA_gen(2048);
3155}
3156
3157static bool sslTestWritePEM(X509* cert, EVP_PKEY* key, const char* certPath, const char* keyPath) {
3158 bool ok = false;
3159 FILE* cf = fopen(certPath, "wb");
3160 FILE* kf = keyPath ? fopen(keyPath, "wb") : NULL;
3161 if (cf && PEM_write_X509(cf, cert) == 1)
3162 ok = true;
3163 if (ok && keyPath)
3164 ok = (kf && PEM_write_PrivateKey(kf, key, NULL, NULL, 0, NULL, NULL) == 1);
3165 if (cf) fclose(cf);
3166 if (kf) fclose(kf);
3167 return ok;
3168}
3169
3170static bool sslTestAddExt(X509* cert, X509* issuer, int nid, const char* value) {
3171 X509V3_CTX ctx;
3172 X509V3_set_ctx_nodb(&ctx);
3173 X509V3_set_ctx(&ctx, issuer, cert, NULL, NULL, 0);
3174 X509_EXTENSION* ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
3175 if (!ext)
3176 return false;
3177 X509_add_ext(cert, ext, -1);
3178 X509_EXTENSION_free(ext);
3179 return true;
3180}
3181
3182// Create a self-signed CA certificate (CA:TRUE) and return the key/cert
3183static bool sslTestGenCA(const char* caCertPath, EVP_PKEY** caKeyOut, X509** caCertOut) {
3184 EVP_PKEY* key = sslTestGenKey();
3185 if (!key)
3186 return false;
3187 X509* x = X509_new();
3188 if (!x) { EVP_PKEY_free(key); return false; }
3189 X509_set_version(x, 2);
3190 ASN1_INTEGER_set(X509_get_serialNumber(x), 1000);
3191 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3192 X509_gmtime_adj(X509_getm_notAfter(x), (long)60 * 60 * 24 * 365);
3193 X509_set_pubkey(x, key);
3194 X509_NAME* name = X509_get_subject_name(x);
3195 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
3196 (const unsigned char*)"Psyclone Test CA", -1, -1, 0);
3197 X509_set_issuer_name(x, name);
3198 bool ok = sslTestAddExt(x, x, NID_basic_constraints, "critical,CA:TRUE") &&
3199 sslTestAddExt(x, x, NID_key_usage, "critical,keyCertSign,cRLSign") &&
3200 (X509_sign(x, key, EVP_sha256()) != 0) &&
3201 sslTestWritePEM(x, NULL, caCertPath, NULL);
3202 if (!ok) { X509_free(x); EVP_PKEY_free(key); return false; }
3203 *caKeyOut = key;
3204 *caCertOut = x;
3205 return true;
3206}
3207
3208// Create a leaf (server) certificate with the given CN and SAN, signed by the CA
3209static bool sslTestGenLeaf(const char* certPath, const char* keyPath, const char* cn,
3210 const char* san, X509* caCert, EVP_PKEY* caKey, long serial) {
3211 EVP_PKEY* key = sslTestGenKey();
3212 if (!key)
3213 return false;
3214 X509* x = X509_new();
3215 if (!x) { EVP_PKEY_free(key); return false; }
3216 X509_set_version(x, 2);
3217 ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
3218 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3219 X509_gmtime_adj(X509_getm_notAfter(x), (long)60 * 60 * 24 * 365);
3220 X509_set_pubkey(x, key);
3221 X509_NAME* name = X509_get_subject_name(x);
3222 X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
3223 (const unsigned char*)cn, -1, -1, 0);
3224 X509_set_issuer_name(x, X509_get_subject_name(caCert));
3225 bool ok = sslTestAddExt(x, caCert, NID_basic_constraints, "critical,CA:FALSE") &&
3226 sslTestAddExt(x, caCert, NID_subject_alt_name, san) &&
3227 (X509_sign(x, caKey, EVP_sha256()) != 0) &&
3228 sslTestWritePEM(x, key, certPath, keyPath);
3229 X509_free(x);
3230 EVP_PKEY_free(key);
3231 return ok;
3232}
3233
3234// SSL hostname + custom CA verification test:
3235// 1) a CA-signed server is REJECTED when the CA is not provided (verify on),
3236// 2) the same server is ACCEPTED when its CA is given via setSSLCALocation (SSL_CTX_load_verify_locations) and the cert matches the hostname,
3237// 3) a trusted (CA-signed) server whose cert is for a DIFFERENT hostname is
3238// REJECTED when connecting by hostname (SSL_set1_host check),
3239// 4) the same mismatching server is ACCEPTED when connecting by IP literal
3240// (no hostname known -> chain-of-trust check only), proving 3) failed on
3241// the hostname check and IP/raw-address paths are not broken.
3242bool NetworkManager::UnitTestSSLHostCA() {
3243
3244 std::string caCertPathS = sslTestTempPath("psytest_sslhostca_ca.pem");
3245 std::string goodCertPathS = sslTestTempPath("psytest_sslhostca_good_cert.pem");
3246 std::string goodKeyPathS = sslTestTempPath("psytest_sslhostca_good_key.pem");
3247 std::string badCertPathS = sslTestTempPath("psytest_sslhostca_bad_cert.pem");
3248 std::string badKeyPathS = sslTestTempPath("psytest_sslhostca_bad_key.pem");
3249 const char* caCertPath = caCertPathS.c_str();
3250 const char* goodCertPath = goodCertPathS.c_str();
3251 const char* goodKeyPath = goodKeyPathS.c_str();
3252 const char* badCertPath = badCertPathS.c_str();
3253 const char* badKeyPath = badKeyPathS.c_str();
3254 EVP_PKEY* caKey = NULL;
3255 X509* caCert = NULL;
3256 uint64 conid = 0, location = 0;
3257 NetworkChannel* con = NULL;
3258 HTTPTestServer* testServer = NULL;
3259 NetworkManager* goodManager = NULL;
3260 NetworkManager* badManager = NULL;
3261 NetworkChannel* listen = NULL;
3262 bool ok = true;
3263
3264 unittest::progress(0, "generating test CA and CA-signed certificates");
3265 if (!sslTestGenCA(caCertPath, &caKey, &caCert)) {
3266 unittest::fail("could not generate test CA");
3267 return false;
3268 }
3269 if (!sslTestGenLeaf(goodCertPath, goodKeyPath, "localhost", "DNS:localhost", caCert, caKey, 1001) ||
3270 !sslTestGenLeaf(badCertPath, badKeyPath, "wronghost.example", "DNS:wronghost.example", caCert, caKey, 1002)) {
3271 unittest::fail("could not generate CA-signed server certificates");
3272 X509_free(caCert); EVP_PKEY_free(caKey);
3273 unlink(caCertPath);
3274 return false;
3275 }
3276 unittest::detail("CA + leaf certs generated (SAN localhost / wronghost.example)");
3277
3278 testServer = new HTTPTestServer();
3279
3280 // --- Server presenting the CORRECT hostname cert (CN/SAN localhost) ---
3281 goodManager = new NetworkManager();
3282 goodManager->setSSLCertificate(goodCertPath, goodKeyPath);
3283 unittest::progress(20, "starting SSL server with CA-signed localhost cert");
3284 listen = goodManager->createListener(38114, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3285 if (!listen) {
3286 unittest::fail("could not start SSL listener on port 38114");
3287 ok = false;
3288 goto done;
3289 }
3290
3291 unittest::progress(30, "connecting with verify on, custom CA NOT provided (must be rejected)");
3292 con = goodManager->createTCPConnection("localhost", 38114, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3293 if (con) {
3294 unittest::fail("CA-signed server was ACCEPTED although its CA was not provided");
3295 con->endConnection(conid);
3296 ok = false;
3297 goto done;
3298 }
3299 unittest::detail("no CA provided: CA-signed server correctly rejected");
3300
3301 unittest::progress(45, "connecting with custom CA provided + matching hostname (must be accepted)");
3302 goodManager->setSSLCALocation(caCertPath, NULL);
3303 con = goodManager->createTCPConnection("localhost", 38114, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3304 if (!con) {
3305 unittest::fail("connection FAILED despite custom CA provided and matching hostname");
3306 ok = false;
3307 goto done;
3308 }
3309 unittest::detail("custom CA + matching hostname: TLS handshake accepted");
3310 con->endConnection(conid);
3311
3312 // --- Server presenting a MISMATCHING hostname cert (CN/SAN wronghost.example) ---
3313 badManager = new NetworkManager();
3314 badManager->setSSLCertificate(badCertPath, badKeyPath);
3315 badManager->setSSLCALocation(caCertPath, NULL);
3316 unittest::progress(60, "starting SSL server with CA-signed wronghost cert");
3317 listen = badManager->createListener(38115, SSLENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3318 if (!listen) {
3319 unittest::fail("could not start SSL listener on port 38115");
3320 ok = false;
3321 goto done;
3322 }
3323
3324 unittest::progress(70, "connecting by hostname to server with mismatching cert (must be rejected)");
3325 con = badManager->createTCPConnection("localhost", 38115, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3326 if (con) {
3327 unittest::fail("server with cert for wronghost.example was ACCEPTED for hostname localhost");
3328 con->endConnection(conid);
3329 ok = false;
3330 goto done;
3331 }
3332 unittest::detail("hostname mismatch: trusted cert for wrong host correctly rejected");
3333
3334 unittest::progress(85, "connecting by IP literal (no hostname check; must be accepted)");
3335 con = badManager->createTCPConnection("127.0.0.1", 38115, SSLENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location, 3000);
3336 if (!con) {
3337 unittest::fail("connection by IP literal FAILED (chain-of-trust only path broken)");
3338 ok = false;
3339 goto done;
3340 }
3341 unittest::detail("IP literal connect: chain-of-trust only, accepted (hostname check skipped)");
3342 con->endConnection(conid);
3343
3344done:
3345 unittest::progress(95, "shutting down");
3346 delete(goodManager);
3347 delete(badManager);
3348 delete(testServer);
3350 X509_free(caCert);
3351 EVP_PKEY_free(caKey);
3352 unlink(caCertPath);
3353 unlink(goodCertPath); unlink(goodKeyPath);
3354 unlink(badCertPath); unlink(badKeyPath);
3355 unittest::progress(100, "done");
3356 return ok;
3357}
3358#endif // _USE_SSL_
3359
3361 const char* host;
3362 uint32 port;
3363 std::vector<std::string>* urls;
3365 uint32 status;
3366};
3367
3369 if (arg == NULL) thread_ret_val(1);
3370
3372 data->status = 1;
3373
3374 uint64 conid;
3375 NetworkChannel* con = NULL;
3376 HTTPRequest* req;
3377 HTTPReply* reply;
3378 uint64 location;
3379 uint32 e;
3380
3382
3383 printf("Connecting...\n");
3384 con = data->manager->createTCPConnection(data->host, data->port, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3385 if (!con) {
3386 printf("Could not connect to host %s:%u...\n\n", data->host, data->port);
3387 data->status = 90;
3388 goto err;
3389 }
3390 data->status = 2;
3391
3392 for (uint32 n=0; n<10; n++) {
3393 // choose a random url...
3394 e = (uint32)utils::RandomValue((double)(data->urls->size()-1));
3395 // printf("Getting %u url: %s...\n\n", e, data->urls->at(e).c_str());
3396
3397 req = new HTTPRequest((uint64)0);
3398 if (!req->createRequest(HTTP_GET, data->host, data->urls->at(e).c_str(), NULL, 0, true, 0)) {
3399 delete(req);
3400 printf("Could not create request [%u]...\n\n", n);
3401 data->status = 98;
3402 goto err;
3403 }
3404
3405 printf("[%u] Sending...\n", n);
3406 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
3407 delete(req);
3408
3409 if (reply == NULL) {
3410 printf("Did not receive reply...\n\n");
3411 data->status = 97;
3412 goto err;
3413 }
3414 else if (reply->type == HTTP_SERVER_UNAVAILABLE) {
3415 printf("Server unavailable...\n\n");
3416 data->status = 96;
3417 goto err;
3418 }
3419 else if (reply->type == HTTP_SERVER_NOREPLY) {
3420 printf("Server no reply...\n\n");
3421 data->status = 95;
3422 goto err;
3423 }
3424 else if (reply->type == HTTP_SERVER_MALFORMED_REPLY) {
3425 printf("Server malformed reply...\n\n");
3426 data->status = 94;
3427 goto err;
3428 }
3429
3430 delete(reply);
3431 }
3432
3433 data->status = 10;
3434 thread_ret_val(0);
3435err:
3436 thread_ret_val(1);
3437}
3438
3440
3441 // Self-contained loopback test: start the websocket-capable HTTP server,
3442 // connect a loopback client, perform a websocket upgrade handshake and
3443 // verify the server replies with a websocket (101) upgrade. This exercises
3444 // the server-side websocket path without waiting for external clients.
3445 uint64 conid;
3446 NetworkChannel* con;
3447 HTTPRequest* req;
3448 HTTPReply* reply;
3449
3450 unittest::progress(0, "starting websocket server");
3451
3452 WebsocketTestServer* testServer = new WebsocketTestServer();
3453 NetworkManager* manager = new NetworkManager();
3454
3455 NetworkChannel* listen = manager->createListener(38103, NOENC, PROTOCOL_HTTP_SERVER, true, 3000, false, 0, testServer);
3456 if (!listen) {
3457 unittest::fail("NetworkManager websocket test: could not start listening on port 38103");
3458 goto err;
3459 }
3460
3461 unittest::progress(30, "connecting websocket client");
3462 uint64 location;
3463 con = manager->createTCPConnection("localhost", 38103, NOENC, PROTOCOL_HTTP_CLIENT, true, false, 0, NULL, conid, location);
3464 if (!con) {
3465 unittest::fail("NetworkManager websocket test: could not connect");
3466 goto err;
3467 }
3468
3469 unittest::progress(55, "sending websocket upgrade handshake");
3470 req = new HTTPRequest((uint64)0);
3471 if (!req->createWebsocketRequest("/", "localhost", NULL, NULL)) {
3472 delete(req);
3473 unittest::fail("NetworkManager websocket test: could not create websocket upgrade request");
3474 goto err;
3475 }
3476 reply = con->sendReceiveHTTPRequest(req, conid, 3000);
3477 delete(req);
3478 if (reply == NULL) {
3479 unittest::fail("NetworkManager websocket test: did not receive handshake reply");
3480 goto err;
3481 }
3482 if (!reply->isWebsocketUpgrade()) {
3483 delete(reply);
3484 unittest::fail("NetworkManager websocket test: reply was not a websocket upgrade");
3485 goto err;
3486 }
3487 unittest::detail("websocket: server confirmed upgrade handshake");
3488 delete(reply);
3489
3490 con->endConnection(conid);
3491
3492 unittest::progress(95, "shutting down");
3493 delete(manager);
3494 delete(testServer);
3496 unittest::progress(100, "done");
3497 return true;
3498err:
3499 delete(manager);
3500 delete(testServer);
3502 return false;
3503
3504}
3505
3506
3507bool NetworkManager::TestHTTP(const char* host, uint32 port, std::vector<std::string> &urls) {
3508
3509 printf("Testing HTTP on %s:%u with random urls...\n\n", host, port);
3510
3511 uint32 loops = 10;
3512 uint32 numThreads = 5;
3513 uint64 conid;
3514 uint64 location;
3515
3516 HTTPServerTestData* data = NULL;
3517
3518 NetworkManager* manager = new NetworkManager();
3519 NetworkChannel* testChannel = manager->createTCPConnection(host, port, NOENC, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
3520 if (!testChannel) {
3521 printf("Could not connect to host %s:%u...\n\n", host, port);
3522 delete(manager);
3523 return false;
3524 }
3525 testChannel->endConnection(conid);
3526 printf("Host is available, starting test...\n\n");
3527
3528 //HTTPRequest* req;
3529 //HTTPReply* reply;
3530
3531 //req = new HTTPRequest((uint64)0);
3532 //if (!req->createRequest(HTTP_GET, host, "/", NULL, 0, true, 0)) {
3533 // delete(req);
3534 // printf("Could not create request...\n\n");
3535 // delete(manager);
3536 // return false;
3537 //}
3538
3539 //reply = testChannel->sendReceiveHTTPRequest(req, conid, 3000);
3540 //delete(req);
3541
3542 //if (reply == NULL) {
3543 // printf("Did not receive reply...\n\n");
3544 // delete(manager);
3545 // return false;
3546 //}
3547 //delete(reply);
3548
3549// for (uint32 n=0; n<100; n++) {
3550// for (uint32 m=0; m<100; m++) {
3551// // choose a random url...
3552// uint32 e = (uint32)utils::RandomValue(urls.size()-1);
3553// printf("Getting url: %s... ", urls.at(e).c_str());
3554//
3555// req = new HTTPRequest((uint64)0);
3556// if (!req->createRequest(HTTP_GET, host, urls.at(e).c_str(), NULL, 0, true, 0)) {
3557// delete(req);
3558// printf("Could not create request [%u]...\n\n", n);
3559// goto err;
3560// }
3561//
3562// reply = testChannel->sendReceiveHTTPRequest(req, conid, 3000);
3563// delete(req);
3564//
3565// if (reply == NULL) {
3566// printf("Did not receive reply...\n\n");
3567// goto err;
3568// }
3569// printf("SUCCESS\n");
3570// delete(reply);
3571// }
3572// testChannel->endConnection(conid);
3573// testChannel = manager->createTCPConnection(host, port, PROTOCOL_HTTP_CLIENT, false, false, 0, NULL, conid, location);
3574// if (!testChannel) {
3575// printf("Could not connect %u to host %s:%u...\n\n", n, host, port);
3576// delete(manager);
3577// return false;
3578// }
3579// }
3580//
3581//
3582//err:
3583// delete(manager);
3584// return false;
3585
3586 //testChannel->endConnection(conid);
3587 //printf("Host is available, starting test...\n\n");
3588
3589 uint32 n, m;
3590 uint32 threadIDs[1000];
3591 HTTPServerTestData testData[1000];
3592 for (n=0; n<loops; n++) {
3593 for (m=0; m<numThreads; m++) {
3594 testData[m].host = host;
3595 testData[m].port = port;
3596 testData[m].urls = &urls;
3597 testData[m].manager = manager;
3598 testData[m].status = 0;
3599 if (!ThreadManager::CreateThread(HTTPServerTest, &testData[m], threadIDs[m])) {
3600 LogPrint(0, LOG_SYSTEM, 0, "Could not start tester thread...");
3601 return false;
3602 }
3603 }
3604 while (true) {
3605 for (m=0; m<numThreads; m++) {
3606 if (ThreadManager::IsThreadRunning(threadIDs[m]))
3607 break;
3608 if (testData[m].status > 10) {
3609 printf("[%u] Failed\n", m);
3610 return false;
3611 }
3612 else {
3613 printf("[%u] Done\n", m);
3614 }
3615 }
3616 if (m >= numThreads)
3617 break;
3618 utils::Sleep(100);
3619 }
3620 }
3621
3622 return true;
3623}
3624
3625
3628 "TCP and UDP message send/receive over loopback", "network");
3630 "Delayed/greeting TCP connections to a loopback listener", "network");
3632 "HTTP server/client request/reply over loopback", "network");
3634 "Websocket upgrade handshake against loopback server", "network");
3635#ifdef _USE_SSL_
3636 // Only present in SSL builds (make ssl). Exercises the OpenSSL-backed
3637 // HTTPS/TLS request-reply path end-to-end over loopback.
3638 UnitTestRunner::instance().registerTest("network_https", NetworkManager::UnitTestHTTPS,
3639 "HTTPS (TLS) server/client request/reply over loopback", "network");
3640 UnitTestRunner::instance().registerTest("network_sslverify", NetworkManager::UnitTestSSLVerify,
3641 "SSL client certificate verification (secure by default, allowselfsigned opt-out)", "network");
3642 UnitTestRunner::instance().registerTest("network_sslhostca", NetworkManager::UnitTestSSLHostCA,
3643 "SSL hostname verification (SSL_set1_host) and custom CA location (SSL_CTX_load_verify_locations)", "network");
3644#endif // _USE_SSL_
3645}
3646
3647
3648}
3649
3650
#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)
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:9113
double RandomValue()
Uniform random double in [0,1).
Definition Utils.cpp:9119
bool GetLocalIPAddress(uint32 &address)
Get the primary local IPv4 address.
Definition Utils.cpp:7048
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:8067
THREAD_RET THREAD_FUNCTION_CALL HTTPServerTest(THREAD_ARG arg)
static struct PsyType CTRL_TEST
Definition ObjectIDs.h:83
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.