upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/net.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/net.cpp')
-rw-r--r--src/net.cpp391
1 files changed, 66 insertions, 325 deletions
diff --git a/src/net.cpp b/src/net.cpp
index bdfb9c3..ab07f94 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -12,7 +12,6 @@
12 12
13void ThreadMessageHandler2(void* parg); 13void ThreadMessageHandler2(void* parg);
14void ThreadSocketHandler2(void* parg); 14void ThreadSocketHandler2(void* parg);
15void ThreadOpenConnections2(void* parg);
16 15
17 16
18 17
@@ -32,8 +31,6 @@ bool fShutdown = false;
32array<bool, 10> vfThreadRunning; 31array<bool, 10> vfThreadRunning;
33vector<CNode*> vNodes; 32vector<CNode*> vNodes;
34CCriticalSection cs_vNodes; 33CCriticalSection cs_vNodes;
35map<vector<unsigned char>, CAddress> mapAddresses;
36CCriticalSection cs_mapAddresses;
37map<CInv, CDataStream> mapRelay; 34map<CInv, CDataStream> mapRelay;
38deque<pair<int64, CInv> > vRelayExpiration; 35deque<pair<int64, CInv> > vRelayExpiration;
39CCriticalSection cs_mapRelay; 36CCriticalSection cs_mapRelay;
@@ -41,61 +38,6 @@ map<CInv, int64> mapAlreadyAskedFor;
41string strBtfConnect; // .btf peer to keep connected to (from /connectbtf) 38string strBtfConnect; // .btf peer to keep connected to (from /connectbtf)
42 39
43 40
44
45CAddress addrProxy;
46
47bool ConnectSocket(const CAddress& addrConnect, SOCKET& hSocketRet)
48{
49 hSocketRet = INVALID_SOCKET;
50
51 SOCKET hSocket = socket(AF_INET, SOCK_STREAM, 0);
52 if (hSocket == INVALID_SOCKET)
53 return false;
54
55 bool fRoutable = !(addrConnect.GetByte(3) == 10 || (addrConnect.GetByte(3) == 192 && addrConnect.GetByte(2) == 168));
56 bool fProxy = (addrProxy.ip && fRoutable);
57 struct sockaddr_in sockaddr = (fProxy ? addrProxy.GetSockAddr() : addrConnect.GetSockAddr());
58
59 if (connect(hSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR)
60 {
61 closesocket(hSocket);
62 return false;
63 }
64
65 if (fProxy)
66 {
67 printf("Proxy connecting to %s\n", addrConnect.ToString().c_str());
68 char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user";
69 memcpy(pszSocks4IP + 2, &addrConnect.port, 2);
70 memcpy(pszSocks4IP + 4, &addrConnect.ip, 4);
71 char* pszSocks4 = pszSocks4IP;
72 int nSize = sizeof(pszSocks4IP);
73
74 int ret = send(hSocket, pszSocks4, nSize, 0);
75 if (ret != nSize)
76 {
77 closesocket(hSocket);
78 return error("Error sending to proxy\n");
79 }
80 char pchRet[8];
81 if (recv(hSocket, pchRet, 8, 0) != 8)
82 {
83 closesocket(hSocket);
84 return error("Error reading proxy response\n");
85 }
86 if (pchRet[1] != 0x5a)
87 {
88 closesocket(hSocket);
89 return error("Proxy returned error %d\n", pchRet[1]);
90 }
91 printf("Proxy connection established %s\n", addrConnect.ToString().c_str());
92 }
93
94 hSocketRet = hSocket;
95 return true;
96}
97
98
99bool GetMyExternalIP(unsigned int& ipRet) 41bool GetMyExternalIP(unsigned int& ipRet)
100{ 42{
101 // Try several plain-text IP echo services in order. 43 // Try several plain-text IP echo services in order.
@@ -178,38 +120,6 @@ bool GetMyExternalIP(unsigned int& ipRet)
178 120
179 121
180 122
181bool AddAddress(CAddrDB& addrdb, const CAddress& addr)
182{
183 if (!addr.IsRoutable())
184 return false;
185 if (addr.ip == addrLocalHost.ip)
186 return false;
187 CRITICAL_BLOCK(cs_mapAddresses)
188 {
189 map<vector<unsigned char>, CAddress>::iterator it = mapAddresses.find(addr.GetKey());
190 if (it == mapAddresses.end())
191 {
192 // New address
193 mapAddresses.insert(make_pair(addr.GetKey(), addr));
194 addrdb.WriteAddress(addr);
195 return true;
196 }
197 else
198 {
199 CAddress& addrFound = (*it).second;
200 if ((addrFound.nServices | addr.nServices) != addrFound.nServices)
201 {
202 // Services have been added
203 addrFound.nServices |= addr.nServices;
204 addrdb.WriteAddress(addrFound);
205 return true;
206 }
207 }
208 }
209 return false;
210}
211
212
213 123
214 124
215 125
@@ -330,64 +240,6 @@ CNode* FindNode(unsigned int ip)
330 return NULL; 240 return NULL;
331} 241}
332 242
333CNode* FindNode(CAddress addr)
334{
335 CRITICAL_BLOCK(cs_vNodes)
336 {
337 foreach(CNode* pnode, vNodes)
338 if (pnode->addr == addr)
339 return (pnode);
340 }
341 return NULL;
342}
343
344CNode* ConnectNode(CAddress addrConnect, int64 nTimeout)
345{
346 if (addrConnect.ip == addrLocalHost.ip)
347 return NULL;
348
349 // Look for an existing connection
350 CNode* pnode = FindNode(addrConnect.ip);
351 if (pnode)
352 {
353 if (nTimeout != 0)
354 pnode->AddRef(nTimeout);
355 else
356 pnode->AddRef();
357 return pnode;
358 }
359
360 if (fDebug)
361 printf("trying %s\n", addrConnect.ToString().c_str());
362
363 // Connect
364 SOCKET hSocket;
365 if (ConnectSocket(addrConnect, hSocket))
366 {
367 if (fDebug)
368 printf("connected %s\n", addrConnect.ToString().c_str());
369
370 // Add node
371 CNode* pnode = new CNode(hSocket, addrConnect, false);
372 if (nTimeout != 0)
373 pnode->AddRef(nTimeout);
374 else
375 pnode->AddRef();
376 CRITICAL_BLOCK(cs_vNodes)
377 vNodes.push_back(pnode);
378
379 CRITICAL_BLOCK(cs_mapAddresses)
380 mapAddresses[addrConnect.GetKey()].nLastFailed = 0;
381 return pnode;
382 }
383 else
384 {
385 CRITICAL_BLOCK(cs_mapAddresses)
386 mapAddresses[addrConnect.GetKey()].nLastFailed = GetTime();
387 return NULL;
388 }
389}
390
391// Synthetic marker address for a tunneled `.btf` peer. The real endpoint is 243// Synthetic marker address for a tunneled `.btf` peer. The real endpoint is
392// unknown by design (the tunnel hides it), so tag the CNode with a non-routable 244// unknown by design (the tunnel hides it), so tag the CNode with a non-routable
393// 10.x.x.x address (fails IsRoutable, so it is never gossiped as a real peer) 245// 10.x.x.x address (fails IsRoutable, so it is never gossiped as a real peer)
@@ -401,20 +253,15 @@ static CAddress BtfMarkerAddr(const unsigned char b5[5])
401 return CAddress(ip, port, nLocalServices); 253 return CAddress(ip, port, nLocalServices);
402} 254}
403 255
404// Connect to a peer by its `.btf` address: resolve the self-certified 256// Shared tail: given a peer's pubkey and already-resolved rendezvous
405// descriptor over Nostr, then tunnel through its meeting node with the 257// coordinates, open the tunnel and register the CNode. Used by both
406// end-to-end channel. Neither side ever learns the other's IP. 258// ConnectNodeBtf (which resolves the descriptor itself) and
407CNode* ConnectNodeBtf(const string& strBtfAddr) 259// ConnectNodeBtfResolved (which takes an already-resolved descriptor, so it
260// makes no Nostr relay calls at all -- see nostr.cpp's BtfResolveMany for why
261// that matters when dialing many candidates in parallel).
262static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char pk[32],
263 const string& strMeeting, const unsigned char enc_pub[32])
408{ 264{
409 unsigned char pk[32];
410 if (!btf::ParseAddress(strBtfAddr, pk))
411 {
412 printf("ConnectNodeBtf: invalid address %s\n", strBtfAddr.c_str());
413 return NULL;
414 }
415 if (BtfLocalAddress() == strBtfAddr)
416 return NULL; // ourselves
417
418 CAddress addr = BtfMarkerAddr(pk); 265 CAddress addr = BtfMarkerAddr(pk);
419 CNode* pnode = FindNode(addr.ip); 266 CNode* pnode = FindNode(addr.ip);
420 if (pnode) 267 if (pnode)
@@ -423,17 +270,6 @@ CNode* ConnectNodeBtf(const string& strBtfAddr)
423 return pnode; 270 return pnode;
424 } 271 }
425 272
426 if (fDebug)
427 printf("trying %s\n", strBtfAddr.c_str());
428
429 string strMeeting;
430 unsigned char enc_pub[32];
431 if (!BtfResolve(strBtfAddr, strMeeting, enc_pub))
432 {
433 if (fDebug)
434 printf("ConnectNodeBtf: could not resolve a descriptor for %s\n", strBtfAddr.c_str());
435 return NULL;
436 }
437 size_t colon = strMeeting.rfind(':'); 273 size_t colon = strMeeting.rfind(':');
438 if (colon == string::npos) 274 if (colon == string::npos)
439 return NULL; 275 return NULL;
@@ -446,12 +282,12 @@ CNode* ConnectNodeBtf(const string& strBtfAddr)
446 if (hSocket == INVALID_SOCKET) 282 if (hSocket == INVALID_SOCKET)
447 { 283 {
448 if (fDebug) 284 if (fDebug)
449 printf("ConnectNodeBtf: tunnel to %s via %s failed\n", strBtfAddr.c_str(), strMeeting.c_str()); 285 LogPrint("net", "ConnectNodeBtf: tunnel to %s via %s failed\n", strBtfAddr.c_str(), strMeeting.c_str());
450 return NULL; 286 return NULL;
451 } 287 }
452 288
453 if (fDebug) 289 if (fDebug)
454 printf("connected %s via rendezvous %s\n", strBtfAddr.c_str(), strMeeting.c_str()); 290 LogPrint("net", "connected %s via rendezvous %s\n", strBtfAddr.c_str(), strMeeting.c_str());
455 291
456 // Add node 292 // Add node
457 pnode = new CNode(hSocket, addr, false); 293 pnode = new CNode(hSocket, addr, false);
@@ -461,6 +297,53 @@ CNode* ConnectNodeBtf(const string& strBtfAddr)
461 return pnode; 297 return pnode;
462} 298}
463 299
300// Connect to a peer by its `.btf` address: resolve the self-certified
301// descriptor over Nostr, then tunnel through its meeting node with the
302// end-to-end channel. Neither side ever learns the other's IP.
303CNode* ConnectNodeBtf(const string& strBtfAddr)
304{
305 unsigned char pk[32];
306 if (!btf::ParseAddress(strBtfAddr, pk))
307 {
308 LogPrint("net", "ConnectNodeBtf: invalid address %s\n", strBtfAddr.c_str());
309 return NULL;
310 }
311 if (BtfLocalAddress() == strBtfAddr)
312 return NULL; // ourselves
313
314 if (fDebug)
315 LogPrint("net", "trying %s\n", strBtfAddr.c_str());
316
317 string strMeeting;
318 unsigned char enc_pub[32];
319 if (!BtfResolve(strBtfAddr, strMeeting, enc_pub))
320 {
321 if (fDebug)
322 LogPrint("net", "ConnectNodeBtf: could not resolve a descriptor for %s\n", strBtfAddr.c_str());
323 return NULL;
324 }
325 return ConnectNodeBtfTail(strBtfAddr, pk, strMeeting, enc_pub);
326}
327
328// Same as ConnectNodeBtf, but skips the Nostr resolve step because the caller
329// already resolved the descriptor (e.g. via BtfResolveMany, batching many
330// addresses into one relay round-trip instead of one relay round-trip per
331// address). Makes zero relay connections -- only talks to the peer's
332// rendezvous meeting node.
333CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting,
334 const unsigned char enc_pub[32])
335{
336 unsigned char pk[32];
337 if (!btf::ParseAddress(strBtfAddr, pk))
338 {
339 LogPrint("net", "ConnectNodeBtf: invalid address %s\n", strBtfAddr.c_str());
340 return NULL;
341 }
342 if (BtfLocalAddress() == strBtfAddr)
343 return NULL; // ourselves
344 return ConnectNodeBtfTail(strBtfAddr, pk, strMeeting, enc_pub);
345}
346
464// Anonymous inbound listener (this node's `.btf` hidden service). Registers at 347// Anonymous inbound listener (this node's `.btf` hidden service). Registers at
465// the meeting relay and blocks until a client dials our address; each pairing 348// the meeting relay and blocks until a client dials our address; each pairing
466// becomes a normal inbound CNode riding the end-to-end channel, then we 349// becomes a normal inbound CNode riding the end-to-end channel, then we
@@ -535,7 +418,7 @@ void ThreadBtfAccept(void* parg)
535 RAND_bytes(rnd, sizeof(rnd)); 418 RAND_bytes(rnd, sizeof(rnd));
536 CAddress addr = BtfMarkerAddr(rnd); 419 CAddress addr = BtfMarkerAddr(rnd);
537 420
538 printf("accepted .btf connection via rendezvous %s\n", strMeeting.c_str()); 421 LogPrint("net", "accepted .btf connection via rendezvous %s\n", strMeeting.c_str());
539 CNode* pnode = new CNode(hSocket, addr, true); 422 CNode* pnode = new CNode(hSocket, addr, true);
540 pnode->AddRef(); 423 pnode->AddRef();
541 CRITICAL_BLOCK(cs_vNodes) 424 CRITICAL_BLOCK(cs_vNodes)
@@ -574,7 +457,7 @@ void ThreadBtfConnect(void* parg)
574 457
575void CNode::Disconnect() 458void CNode::Disconnect()
576{ 459{
577 printf("disconnecting node %s\n", addr.ToString().c_str()); 460 LogPrint("net", "disconnecting node %s\n", addr.ToString().c_str());
578 461
579 closesocket(hSocket); 462 closesocket(hSocket);
580 463
@@ -653,7 +536,7 @@ void ThreadSocketHandler2(void* parg)
653 536
654 if (pnodeExtra->GetRefCount() <= (pnodeExtra->fNetworkNode ? 1 : 0)) 537 if (pnodeExtra->GetRefCount() <= (pnodeExtra->fNetworkNode ? 1 : 0))
655 { 538 {
656 printf("(%d nodes) disconnecting duplicate: %s\n", vNodes.size(), pnodeExtra->addr.ToString().c_str()); 539 LogPrint("net", "(%d nodes) disconnecting duplicate: %s\n", vNodes.size(), pnodeExtra->addr.ToString().c_str());
657 if (pnodeExtra->fNetworkNode && !pnode->fNetworkNode) 540 if (pnodeExtra->fNetworkNode && !pnode->fNetworkNode)
658 { 541 {
659 pnode->AddRef(); 542 pnode->AddRef();
@@ -745,7 +628,7 @@ void ThreadSocketHandler2(void* parg)
745 if (nSelect == SOCKET_ERROR) 628 if (nSelect == SOCKET_ERROR)
746 { 629 {
747 int nErr = WSAGetLastError(); 630 int nErr = WSAGetLastError();
748 printf("select failed: %d\n", nErr); 631 LogPrint("net", "select failed: %d\n", nErr);
749 for (int i = 0; i <= hSocketMax; i++) 632 for (int i = 0; i <= hSocketMax; i++)
750 { 633 {
751 FD_SET(i, &fdsetRecv); 634 FD_SET(i, &fdsetRecv);
@@ -784,7 +667,7 @@ void ThreadSocketHandler2(void* parg)
784 } 667 }
785 else 668 else
786 { 669 {
787 printf("accepted connection from %s\n", addr.ToString().c_str()); 670 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str());
788 CNode* pnode = new CNode(hSocket, addr, true); 671 CNode* pnode = new CNode(hSocket, addr, true);
789 pnode->AddRef(); 672 pnode->AddRef();
790 CRITICAL_BLOCK(cs_vNodes) 673 CRITICAL_BLOCK(cs_vNodes)
@@ -823,7 +706,7 @@ void ThreadSocketHandler2(void* parg)
823 { 706 {
824 // socket closed gracefully 707 // socket closed gracefully
825 if (!pnode->fDisconnect) 708 if (!pnode->fDisconnect)
826 printf("recv: socket closed\n"); 709 LogPrint("net", "recv: socket closed\n");
827 pnode->fDisconnect = true; 710 pnode->fDisconnect = true;
828 } 711 }
829 else if (nBytes < 0) 712 else if (nBytes < 0)
@@ -833,7 +716,7 @@ void ThreadSocketHandler2(void* parg)
833 if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) 716 if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
834 { 717 {
835 if (!pnode->fDisconnect) 718 if (!pnode->fDisconnect)
836 printf("recv failed: %d\n", nErr); 719 LogPrint("net", "recv failed: %d\n", nErr);
837 pnode->fDisconnect = true; 720 pnode->fDisconnect = true;
838 } 721 }
839 } 722 }
@@ -862,7 +745,7 @@ void ThreadSocketHandler2(void* parg)
862 } 745 }
863 else 746 else
864 { 747 {
865 printf("send error %d\n", nBytes); 748 LogPrint("net", "send error %d\n", nBytes);
866 if (pnode->ReadyToDisconnect()) 749 if (pnode->ReadyToDisconnect())
867 pnode->vSend.clear(); 750 pnode->vSend.clear();
868 } 751 }
@@ -885,141 +768,6 @@ void ThreadSocketHandler2(void* parg)
885 768
886 769
887 770
888void ThreadOpenConnections(void* parg)
889{
890 IMPLEMENT_RANDOMIZE_STACK(ThreadOpenConnections(parg));
891
892 loop
893 {
894 vfThreadRunning[1] = true;
895 CheckForShutdown(1);
896 try
897 {
898 ThreadOpenConnections2(parg);
899 }
900 CATCH_PRINT_EXCEPTION("ThreadOpenConnections()")
901 vfThreadRunning[1] = false;
902 Sleep(5000);
903 }
904}
905
906void ThreadOpenConnections2(void* parg)
907{
908 printf("ThreadOpenConnections started\n");
909
910 // Initiate network connections
911 const int nMaxConnections = 15;
912 loop
913 {
914 // Wait
915 vfThreadRunning[1] = false;
916 Sleep(500);
917 while (vNodes.size() >= nMaxConnections || vNodes.size() >= mapAddresses.size())
918 {
919 CheckForShutdown(1);
920 Sleep(2000);
921 }
922 vfThreadRunning[1] = true;
923 CheckForShutdown(1);
924
925
926 // Make a list of unique class C's
927 unsigned char pchIPCMask[4] = { 0xff, 0xff, 0xff, 0x00 };
928 unsigned int nIPCMask = *(unsigned int*)pchIPCMask;
929 vector<unsigned int> vIPC;
930 CRITICAL_BLOCK(cs_mapAddresses)
931 {
932 vIPC.reserve(mapAddresses.size());
933 unsigned int nPrev = 0;
934 foreach(const PAIRTYPE(vector<unsigned char>, CAddress)& item, mapAddresses)
935 {
936 const CAddress& addr = item.second;
937 if (!addr.IsIPv4())
938 continue;
939
940 // Taking advantage of mapAddresses being in sorted order,
941 // with IPs of the same class C grouped together.
942 unsigned int ipC = addr.ip & nIPCMask;
943 if (ipC != nPrev)
944 vIPC.push_back(nPrev = ipC);
945 }
946 }
947
948 //
949 // The IP selection process is designed to limit vulnerability to address flooding.
950 // Any class C (a.b.c.?) has an equal chance of being chosen, then an IP is
951 // chosen within the class C. An attacker may be able to allocate many IPs, but
952 // they would normally be concentrated in blocks of class C's. They can hog the
953 // attention within their class C, but not the whole IP address space overall.
954 // A lone node in a class C will get as much attention as someone holding all 255
955 // IPs in another class C.
956 //
957 bool fSuccess = false;
958 int nLimit = vIPC.size();
959 while (!fSuccess && nLimit-- > 0)
960 {
961 // Choose a random class C
962 unsigned int ipC = vIPC[GetRand(vIPC.size())];
963
964 // Organize all addresses in the class C by IP
965 map<unsigned int, vector<CAddress> > mapIP;
966 CRITICAL_BLOCK(cs_mapAddresses)
967 {
968 unsigned int nDelay = ((30 * 60) << vNodes.size());
969 if (nDelay > 8 * 60 * 60)
970 nDelay = 8 * 60 * 60;
971 for (map<vector<unsigned char>, CAddress>::iterator mi = mapAddresses.lower_bound(CAddress(ipC, 0).GetKey());
972 mi != mapAddresses.upper_bound(CAddress(ipC | ~nIPCMask, 0xffff).GetKey());
973 ++mi)
974 {
975 const CAddress& addr = (*mi).second;
976 unsigned int nRandomizer = (addr.nLastFailed * addr.ip * 7777U) % 20000;
977 if (GetTime() - addr.nLastFailed > nDelay * nRandomizer / 10000)
978 mapIP[addr.ip].push_back(addr);
979 }
980 }
981 if (mapIP.empty())
982 break;
983
984 // Choose a random IP in the class C
985 map<unsigned int, vector<CAddress> >::iterator mi = mapIP.begin();
986 advance(mi, GetRand(mapIP.size()));
987
988 // Once we've chosen an IP, we'll try every given port before moving on
989 foreach(const CAddress& addrConnect, (*mi).second)
990 {
991 if (addrConnect.ip == addrLocalHost.ip || !addrConnect.IsIPv4() || FindNode(addrConnect.ip))
992 continue;
993
994 CNode* pnode = ConnectNode(addrConnect);
995 if (!pnode)
996 continue;
997 pnode->fNetworkNode = true;
998
999 if (addrLocalHost.IsRoutable())
1000 {
1001 // Advertise our address
1002 vector<CAddress> vAddrToSend;
1003 vAddrToSend.push_back(addrLocalHost);
1004 pnode->PushMessage("addr", vAddrToSend);
1005 }
1006
1007 // Get as many addresses as we can
1008 pnode->PushMessage("getaddr");
1009
1010 ////// should the one on the receiving end do this too?
1011 // Subscribe our local subscription list
1012 const unsigned int nHops = 0;
1013 for (unsigned int nChannel = 0; nChannel < pnodeLocalHost->vfSubscribe.size(); nChannel++)
1014 if (pnodeLocalHost->vfSubscribe[nChannel])
1015 pnode->PushMessage("subscribe", nChannel, nHops);
1016
1017 fSuccess = true;
1018 break;
1019 }
1020 }
1021 }
1022}
1023 771
1024 772
1025 773
@@ -1187,7 +935,7 @@ bool StartNode(string& strError)
1187 return false; 935 return false;
1188 } 936 }
1189 937
1190 // Get our external IP in a background thread — up to 4 HTTP requests with 938 // Get our external IP in a background thread -- up to 4 HTTP requests with
1191 // 5s timeouts each could otherwise delay startup by 20s on first run. 939 // 5s timeouts each could otherwise delay startup by 20s on first run.
1192 // addrIncoming from a previous session is used immediately as a fallback. 940 // addrIncoming from a previous session is used immediately as a fallback.
1193 if (addrIncoming.ip) 941 if (addrIncoming.ip)
@@ -1228,13 +976,6 @@ bool StartNode(string& strError)
1228 return false; 976 return false;
1229 } 977 }
1230 978
1231 if (_beginthread(ThreadOpenConnections, 0, NULL) == -1)
1232 {
1233 strError = "Error: _beginthread(ThreadOpenConnections) failed";
1234 printf("%s\n", strError.c_str());
1235 return false;
1236 }
1237
1238 if (_beginthread(ThreadMessageHandler, 0, NULL) == -1) 979 if (_beginthread(ThreadMessageHandler, 0, NULL) == -1)
1239 { 980 {
1240 strError = "Error: _beginthread(ThreadMessageHandler) failed"; 981 strError = "Error: _beginthread(ThreadMessageHandler) failed";