/* * Original work: Copyright (c) 2014, Oculus VR, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * RakNet License.txt file in the licenses directory of this source tree. An additional grant * of patent rights can be found in the RakNet Patents.txt file in the same directory. * * * Modified work: Copyright (c) 2016-2017, SLikeSoft UG (haftungsbeschränkt) * * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style * license found in the license.txt file in the root directory of this source tree. */ // How to patch // 1. Create a new server instance using the most recent image (optional) // 2. Connect to the new (or any existing) instance through remote desktop. Immediately set max concurrent users to 0. Wait for users to finish patching, if any started. // 3. Update the patch // 4. Image the server // 5. Wait for the image to complete (press l to check, or use Rackspace control panel. Takes like half an hour). // 6. When the image completes, set the image spawn from the 'l' option to the image that was created in step 4 // 6. Send terminate command. Servers running the old patch will shutdown automatically. // 9. Set max concurrent users back above 0. // 10. Optional: Manually call SpawnServers() if load is immediately anticipated // Common includes #include #include #include "slikenet/Kbhit.h" #include "slikenet/GetTime.h" #include "slikenet/peerinterface.h" #include "slikenet/MessageIdentifiers.h" #include "slikenet/BitStream.h" #include "slikenet/StringCompressor.h" #include "slikenet/FileListTransfer.h" #include "slikenet/FileList.h" // FLP_Printf #include "slikenet/PacketizedTCP.h" #include "slikenet/Gets.h" #include "CloudServerHelper.h" #include "slikenet/FullyConnectedMesh2.h" #include "slikenet/TwoWayAuthentication.h" #include "slikenet/CloudClient.h" #include "slikenet/DynDNS.h" #include "slikenet/peerinterface.h" #include "slikenet/sleep.h" #include "slikenet/ConnectionGraph2.h" #include "CloudServerHelper.h" #include "slikenet/HTTPConnection2.h" #include "Rackspace2.h" #include "slikenet/GetTime.h" #include "slikenet/linux_adapter.h" #include "slikenet/osx_adapter.h" // See http://www.digip.org/jansson/doc/2.4/ // This is used to make it easier to parse the JSON returned from the master server #include "jansson.h" // Server only includes #include "AutopatcherServer.h" // Replace this repository with your own implementation if you don't want to use PostgreSQL #include "AutopatcherPostgreRepository.h" #ifdef _WIN32 #include "slikenet/WindowsIncludes.h" // Sleep #else #include // usleep #endif #define LISTEN_PORT_TCP_PATCHER 60000 using namespace SLNet; static const SLNet::Time LOAD_CHECK_INTERVAL=1000*60*5; static const SLNet::Time LOAD_CHECK_INTERVAL_AFTER_SPAWN=1000*60*60; char databasePassword[128]; int workerThreadCount; int sqlConnectionObjectCount; int allowDownloadingUnmodifiedFiles; unsigned short autopatcherLoad=0; SLNet::RakPeerInterface *rakPeer; SLNet::AutopatcherServer *autopatcherServer; enum AppState { AP_RUNNING, AP_TERMINATE_WHEN_ZERO_USERS_REACHED, AP_TERMINATE_IF_NOT_DNS_HOST, AP_TERMINATING, AP_TERMINATED, } appState; SLNet::Time timeSinceZeroUsers; struct CloudServerHelper_RackspaceCloudDNS : public CloudServerHelper, public Rackspace2EventCallback { public: enum CSHState { AUTHENTICATING, AUTHENTICATED, CANNOT_FIND_PATCHER_DOMAIN, CANNOT_FIND_PATCHER_RECORD, GETTING_SERVERS, GOT_MY_SERVER, CANNOT_FIND_MY_SERVER, GETTING_DOMAINS, GOT_DOMAIN, GETTING_RECORDS, GOT_RECORDS, } cshState; CloudServerHelper_RackspaceCloudDNS() { rackspace2= SLNet::OP_NEW(_FILE_AND_LINE_); memset(&thisServerDetail, 0, sizeof(ServerDetail)); } ~CloudServerHelper_RackspaceCloudDNS() { SLNet::OP_DELETE(rackspace2,_FILE_AND_LINE_); } virtual bool Update(void) { UpdateTCP(); return true; } void CopyServerDetails(json_t *arrayElement, const char *publicIPV4) { strcpy_s(thisServerDetail.publicIPV4, publicIPV4); strcpy_s(thisServerDetail.id, json_string_value(json_object_get(arrayElement, "id"))); json_t *privateAddressesArray = json_object_get(json_object_get(arrayElement, "addresses"),"private"); size_t privateAddressesArraySize = json_array_size(privateAddressesArray); for (size_t l=0; l < privateAddressesArraySize; l++) { json_t *privateAddressElement = json_array_get(privateAddressesArray, l); if (json_integer_value(json_object_get(privateAddressElement, "version"))==4) { strcpy_s(thisServerDetail.privateIPV4,json_string_value(json_object_get(privateAddressElement, "addr"))); break; } } strcpy_s(thisServerDetail.flavorId,json_string_value(json_object_get(json_object_get(arrayElement, "flavor"),"id"))); strcpy_s(thisServerDetail.hostId,json_string_value(json_object_get(arrayElement, "hostId"))); strcpy_s(thisServerDetail.id,json_string_value(json_object_get(arrayElement, "id"))); strcpy_s(thisServerDetail.imageId,json_string_value(json_object_get(json_object_get(arrayElement, "image"),"id"))); strcpy_s(thisServerDetail.name,json_string_value(json_object_get(arrayElement, "name"))); strcpy_s(spawningImageId, thisServerDetail.imageId); strcpy_s(spawningFlavorId, thisServerDetail.flavorId); printf("Using image id %s for new servers\n", spawningImageId); } virtual void OnTCPFailure(void){ printf("--ERROR--: OnTCPFailure()\n"); } virtual void OnTransmissionFailed(HTTPConnection2 *httpConnection2, RakString postStr, RakString authURLDomain) { printf("--ERROR--: OnTransmissionFailed().\npostStr=%s\nauthURLDomain=%s\n", postStr.C_String(), authURLDomain.C_String()); RakSleep(30000); printf("Retrying...\n"); httpConnection2->TransmitRequest(postStr,authURLDomain, 443, true); } virtual void OnEmptyResponse(RakString stringTransmitted) { printf("--ERROR--: OnEmptyResponse(). stringTransmitted=%s", stringTransmitted.C_String()); } virtual void OnMessage(const char *message, RakString responseReceived, RakString stringTransmitted, ptrdiff_t contentOffset) { printf("--WARNING--: OnMessage(). message=%s\nstringTransmitted=%s", message, stringTransmitted.C_String()); } virtual void OnResponse(Rackspace2ResponseCode r2rc, RakString responseReceived, ptrdiff_t contentOffset){ if (r2rc==R2RC_AUTHENTICATED) { printf("Authenticated with Rackspace\nX-Auth-Token: %s\n", rackspace2->GetAuthToken()); cshState=AUTHENTICATED; json_error_t error; json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); json_t *accessJson = json_object_get(root, "access"); json_t *userJson = json_object_get(accessJson, "user"); json_t *regionJson = json_object_get(userJson, "RAX-AUTH:defaultRegion"); const char *userRegionStr = json_string_value(regionJson); json_t *serviceCatalogArray = json_object_get(accessJson, "serviceCatalog"); size_t arraySize = json_array_size(serviceCatalogArray); size_t i; printf("Services:\n"); for (i=0; i < arraySize; i++) { json_t *arrayElement = json_array_get(serviceCatalogArray, i); json_t *elementNameJson = json_object_get(arrayElement, "name"); const char *elementNameStr = json_string_value(elementNameJson); json_t *elementTypeJson = json_object_get(arrayElement, "type"); const char *elementTypeStr = json_string_value(elementTypeJson); printf("%i. name=%s type=%s ", i+1, elementNameStr, elementTypeStr); json_t *endpointArray = json_object_get(arrayElement, "endpoints"); size_t endpointArraySize = json_array_size(endpointArray); size_t j; char *myPublicURLStr=0; for (j=0; j < endpointArraySize; j++) { json_t *endpointArrayElement = json_array_get(endpointArray, j); json_t *publicURLJson = json_object_get(endpointArrayElement, "publicURL"); const char *publicURLStr = json_string_value(publicURLJson); json_t *regionJson = json_object_get(endpointArrayElement, "region"); if (regionJson) { const char *regionStr = json_string_value(regionJson); if (strcmp(regionStr, userRegionStr)==0) { printf("My URL=%s", publicURLStr); myPublicURLStr=(char*) publicURLStr; break; } } else { printf("My URL=%s", publicURLStr); myPublicURLStr=(char*) publicURLStr; break; } } if (j==endpointArraySize) printf("My URL="); if (myPublicURLStr) { if (strcmp(elementNameStr, "cloudDNS")==0) strcpy_s(rackspaceDNSURL, myPublicURLStr); else if (strcmp(elementNameStr, "cloudServersOpenStack")==0) strcpy_s(rackspaceServersURL, myPublicURLStr); } printf("\n"); } if (appState==AP_TERMINATING || appState==AP_TERMINATED || appState==AP_TERMINATE_WHEN_ZERO_USERS_REACHED || appState==AP_TERMINATE_IF_NOT_DNS_HOST) Terminate(); json_decref(root); } else if (r2rc==R2RC_GOT_SERVERS) { json_error_t error; json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); json_t *serversArray = json_object_get(root, "servers"); size_t arraySize = json_array_size(serversArray); size_t i; for (i=0; i < arraySize && cshState==GETTING_SERVERS; i++) { json_t *arrayElement = json_array_get(serversArray, i); //json_t *publicAddressesArray = json_object_get(json_object_get(arrayElement, "addresses"),"public"); //size_t publicAddressesArraySize = json_array_size(publicAddressesArray); //for (size_t j=0; j < publicAddressesArraySize; j++) //{ for (unsigned k=0; k < rakPeer->GetNumberOfAddresses(); k++) { if (strcmp(rakPeer->GetLocalIP(k), json_string_value(json_object_get(arrayElement, "accessIPv4")))==0) { CopyServerDetails(arrayElement, rakPeer->GetLocalIP(k)); cshState=GOT_MY_SERVER; break; } } //} } if (cshState==GETTING_SERVERS) { // printf("Could not find a server with this IP address\n"); // printf("Continue anyway (y/n)?\n"); // if (_getch()=='y') // { for (unsigned k=0; k < rakPeer->GetNumberOfAddresses(); k++) { SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,k); if (sa.IsLANAddress()==false) { json_t *arrayElement = json_array_get(serversArray, 0); CopyServerDetails(arrayElement, rakPeer->GetLocalIP(0)); cshState=GOT_MY_SERVER;; break; } } // } // else // cshState=CANNOT_FIND_MY_SERVER; } json_decref(root); } else if (r2rc==R2RC_GOT_IMAGES) { json_error_t error; json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); json_t *domainsArray = json_object_get(root, "images"); size_t arraySize = json_array_size(domainsArray); size_t i; printf("Got %i images:\n", arraySize); for (i=0; i < arraySize; i++) { json_t *arrayElement = json_array_get(domainsArray, i); printf("%i. name=%s ", i+1, json_string_value(json_object_get(arrayElement, "name"))); printf("progress=%" JSON_INTEGER_FORMAT " ", json_integer_value(json_object_get(arrayElement, "progress"))); printf("status=%s ", json_string_value(json_object_get(arrayElement, "status"))); printf("created=%s ", json_string_value(json_object_get(arrayElement, "created"))); printf("id=%s\n", json_string_value(json_object_get(arrayElement, "id"))); } printf("\n"); if (arraySize>0) { printf("Select image to spawn for new servers?: "); char str[32]; Gets(str, sizeof(str)); if (str[0]) { int idx = atoi(str); if (idx>=1 && static_cast(idx) <= arraySize) { json_t *arrayElement = json_array_get(domainsArray, idx-1); strcpy_s(spawningImageId, json_string_value(json_object_get(arrayElement, "id"))); // Use spawningFlavorId of whatever it was when the server was started, I don't care about flavor printf("Using image id %s for new servers\n", spawningImageId); } } else printf("Aborted.\n"); } json_decref(root); } else if (r2rc==R2RC_GOT_RECORDS) { json_error_t error; json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); json_t *domainsArray = json_object_get(root, "records"); size_t arraySize = json_array_size(domainsArray); size_t i; for (i=0; i < arraySize; i++) { json_t *arrayElement = json_array_get(domainsArray, i); json_t *elementNameJson = json_object_get(arrayElement, "name"); const char *elementNameStr = json_string_value(elementNameJson); if (strcmp(elementNameStr, patcherHostSubdomainURL)==0) { json_t *arrayElement = json_array_get(domainsArray, i); json_t *elementIPJson = json_object_get(arrayElement, "data"); strcpy_s(dnsHostIP, json_string_value(elementIPJson)); json_t *elementIDJson = json_object_get(arrayElement, "id"); strcpy_s(patchHostRecordID,json_string_value(elementIDJson)); timeOfLastDNSHostCheck= SLNet::GetTime(); if (appState==AP_TERMINATE_IF_NOT_DNS_HOST) { if (strcmp(dnsHostIP, rakPeer->GetLocalIP(0))==0) { // We are dns host. Just keep running appState=AP_RUNNING; } else { if (timeSinceZeroUsers!=0) Terminate(); else appState=AP_TERMINATE_WHEN_ZERO_USERS_REACHED; } } cshState=GOT_RECORDS; break; } } if (i==arraySize) { printf("Can't find %s in record list\n", patcherHostSubdomainURL); cshState=CANNOT_FIND_PATCHER_RECORD; } json_decref(root); } else if (r2rc==R2RC_GOT_DOMAINS) { json_error_t error; json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); json_t *domainsArray = json_object_get(root, "domains"); size_t arraySize = json_array_size(domainsArray); size_t i; for (i=0; i < arraySize; i++) { json_t *arrayElement = json_array_get(domainsArray, i); json_t *elementNameJson = json_object_get(arrayElement, "name"); const char *elementNameStr = json_string_value(elementNameJson); if (strcmp(elementNameStr, "raknetpatcher.com")==0) { // Primary domain json_t *idNameJson = json_object_get(arrayElement, "id"); raknetPatcherDomainId = (int) json_integer_value(idNameJson); RakString url("%s/domains/%i/records", rackspaceDNSURL, raknetPatcherDomainId ); rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true); cshState=GETTING_RECORDS; break; } } json_decref(root); if (i==arraySize) { printf("Can't find raknetpatcher.com in domain list\n"); cshState=CANNOT_FIND_PATCHER_DOMAIN; } } else { if (r2rc==R2RC_UNAUTHORIZED) { printf("Unauthorized\n"); // Try to reauthenticate rackspace2->Authenticate(rackspaceAuthenticationURL, rackspaceCloudUsername, apiAccessKey); } else { printf("General response failure: %s\n", responseReceived.C_String()); } } } virtual bool UpdateTCP() { rackspace2->Update(); return false; } void Terminate(void) { appState=AP_TERMINATING; // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/Delete_Server-d1e2883.html RakString url("%s/servers/%s", rackspaceServersURL, thisServerDetail.id); rackspace2->AddOperation(url,Rackspace2::OT_DELETE,0, true); } void GetCustomImages(void) { // curl -k https://ord.servers.api.rackspacecloud.com/v2/570016/images?type=SNAPSHOT --header "X-Auth-Token: 97d8335b-7b9e-4a74-96a1-2a3a043c1000" --header "Content-Type: application/json" // List images // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Images-d1e4435.html RakString url("%s/images/detail?type=SNAPSHOT", rackspaceServersURL); rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true); } void ImageThisServer(void) { // Create image // date // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/Create_Image-d1e4655.html time_t aclock; time( &aclock ); // Get time in seconds tm newtime; localtime_s( &newtime, &aclock ); // Convert time to struct tm form char text[1024]; sprintf_s(text, "%s_%s", patcherHostSubdomainURL, asctime( &newtime )); RakString url("%s/servers/%s/action", rackspaceServersURL, thisServerDetail.id); json_t *jsonObjectRoot = json_object(); json_t *jsonObjectCreateImage = json_object(); json_t *jsonMetaData = json_object(); json_object_set(jsonObjectCreateImage, "name", json_string(text)); json_object_set(jsonObjectRoot, "createImage", jsonObjectCreateImage); json_object_set(jsonObjectCreateImage, "metadata", jsonMetaData); json_object_set(jsonMetaData, "ImageType", json_string("snapshot")); printf("Imaging server with name %s\n", text); rackspace2->AddOperation(url,Rackspace2::OT_POST,jsonObjectRoot, true); } void SpawnServers(unsigned count) { if (count==0) return; RakAssert(count <= 4); // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/CreateServers.html RakString url("%s/servers", rackspaceServersURL); time_t aclock; time( &aclock ); // Get time in seconds tm newtime; localtime_s( &newtime, &aclock ); // Convert time to struct tm form char text[1024]; for (unsigned int i=0; i < count; i++) { json_t *jsonObjectRoot = json_object(); json_t *jsonObjectServer = json_object(); json_object_set(jsonObjectRoot, "server", jsonObjectServer); sprintf_s(text, "%s_%s_%u", patcherHostSubdomainURL, asctime( &newtime ), i); json_object_set(jsonObjectServer, "name", json_string(text)); json_object_set(jsonObjectServer, "imageRef", json_string(spawningImageId)); json_object_set(jsonObjectServer, "flavorRef", json_string(spawningFlavorId)); rackspace2->AddOperation(url,Rackspace2::OT_POST,jsonObjectRoot, true); } } void GetThisServer(void) { // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Servers-d1e2078.html cshState=GETTING_SERVERS; RakString url("%s/servers/detail?status=ACTIVE", rackspaceServersURL); //RakString url("%s/servers/detail", rackspaceServersURL); rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true); } void GetDomainRecords(void) { cshState=GETTING_DOMAINS; RakString url("%s/domains/?name=%s", rackspaceDNSURL, "raknetpatcher.com"); rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true); } bool AuthenticateWithRackspaceBlocking(void) { rackspace2->SetEventCallback(this); rackspace2->Authenticate(rackspaceAuthenticationURL, rackspaceCloudUsername, apiAccessKey); cshState=AUTHENTICATING; while (cshState==AUTHENTICATING) { UpdateTCP(); RakSleep(30); } if (cshState!=AUTHENTICATED) return false; GetThisServer(); while (cshState==GETTING_SERVERS) { UpdateTCP(); RakSleep(30); } if (cshState!=GOT_MY_SERVER) { return false; } // http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/list_domains.html GetDomainRecords(); while (cshState==GETTING_DOMAINS || cshState==GETTING_RECORDS) { UpdateTCP(); RakSleep(30); } if (cshState!=GOT_RECORDS) return false; return true; } void UpdateHostIPAsynch(SystemAddress sa) { if (appState!=AP_RUNNING) return; // RakString url("%s/domains/domainId/records/recordId"); // rackspace2->AddOperation() // http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/Modify_Records-d1e5033.html //rackspace2; RakString url("%s/domains/%i/records/%s", rackspaceDNSURL, raknetPatcherDomainId, patchHostRecordID); json_t *jsonObject = json_object(); json_object_set(jsonObject, "data", json_string(sa.ToString(false))); printf("Setting DNS to %s\n", sa.ToString(false)); // TESTING HACK // json_object_set(jsonObject, "data", json_string("198.61.202.20")); json_object_set(jsonObject, "name", json_string(patcherHostSubdomainURL)); rackspace2->AddOperation(url,Rackspace2::OT_PUT,jsonObject, true); } virtual void PrintHelp(void) { printf("Distributed authenticated CloudServer using DNS based host migration.\n"); printf("Parameter1: serverToServerPassword\n"); printf("Parameter2: serverPort\n"); printf("Parameter3: allowedIncomingConnections\n"); printf("Parameter4: allowedOutgoingConnections\n"); printf("Parameter5: patcherHostRecordURL\n"); printf("Parameter6: patcherHostDomainURL\n"); printf("Parameter7: rackspaceAuthenticationURL\n"); printf("Parameter8: rackspaceCloudUsername\n"); printf("Parameter9: apiAccessKey\n"); printf("Parameter10: databasePassword\n"); printf("Parameter11: workerThreadCount\n"); printf("Parameter12: sqlConnectionObjectCount\n"); printf("Parameter13: allowDownloadingUnmodifiedFiles\n"); printf("Example:\n"); printf("AutopatcherServer_SelfScaling s2sPassword 60000 1024 128 game1.mycompanypatcher.com mycompanypatcher.com https://identity.api.rackspacecloud.com/v2.0 rackspaceUsername rackspacePw dbPassword 3 6 1"); } virtual bool ParseCommandLineParameters(int argc, char **argv) { if (argc!=14) PrintHelp(); serverToServerPassword=argv[1]; rakPeerPort=atoi(argv[2]); allowedIncomingConnections=atoi(argv[3]); allowedOutgoingConnections=atoi(argv[4]); strcpy_s(patcherHostSubdomainURL, argv[5]); strcpy_s(patcherHostDomainURL, argv[6]); strcpy_s(rackspaceAuthenticationURL, argv[7]); strcpy_s(rackspaceCloudUsername, argv[8]); strcpy_s(apiAccessKey, argv[9]); strcpy_s(databasePassword, argv[10]); workerThreadCount=atoi(argv[11]); sqlConnectionObjectCount=atoi(argv[12]); allowDownloadingUnmodifiedFiles=atoi(argv[13]); /* 2 worker threads, 4 mb read: 5 minutes, 55 seconds 4 worker threads, 4 mb read: 5 minutes, 7 seconds 8 worker threads, 4 mb read: 4 minutes, 48 seconds 2 worker threads, 2 mb read: 8 minutes, 31 seconds 8 worker threads, 2 mb read: 4 minutes, 30 seconds, but one failed to start 4 worker threads, 8 mb read: 4 minutes, 55 seconds 4 worker threads, 16 mb read: 4 minutes, 48 seconds 3 worker threads, 16 mb read: 4 minutes, 19 seconds 2 worker threads, 16 mb read: 5 minutes, 18 seconds 3 worker threads, 32 mb read: 4 minutes, 18 seconds */ return true; } // Call when you get ID_FCM2_NEW_HOST virtual void OnFCMNewHost(Packet *packet, RakPeerInterface *rakPeer) { RakAssert(packet->data[0]==ID_FCM2_NEW_HOST); SLNet::BitStream bsIn(packet->data, packet->length, false); bsIn.IgnoreBytes(sizeof(MessageID)); RakNetGUID oldHost; bsIn.Read(oldHost); RakNetGUID newHost = packet->guid; if (newHost==rakPeer->GetMyGUID() && oldHost!=newHost) { for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++) { SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i); if (sa.IsLANAddress()==false) { printf("Assuming host. Updating DNS\n"); UpdateHostIPAsynch(sa); break; } } } } const char *GetCloudHostAddress(void) const {return dnsHostIP;} SLNet::Time GetTimeOfLastDNSHostCheck(void) {return timeOfLastDNSHostCheck;} void SetTimeOfLastDNSHostCheck(SLNet::Time t) {timeOfLastDNSHostCheck=t;} virtual int OnJoinCloudResult( Packet *packet, SLNet::RakPeerInterface *rakPeer, SLNet::CloudServer *cloudServer, SLNet::CloudClient *cloudClient, SLNet::FullyConnectedMesh2 *fullyConnectedMesh2, SLNet::TwoWayAuthentication *twoWayAuthentication, SLNet::ConnectionGraph2 *connectionGraph2, const char *rakPeerIpOrDomain, char myPublicIP[32] ) { if (packet->data[0]==ID_CONNECTION_ATTEMPT_FAILED) { for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++) { SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i); if (sa.IsLANAddress()==false) { printf("Failed connection. Changing DNS to point to this system.\n"); UpdateHostIPAsynch(sa); break; } } } else if (packet->data[0]==ID_ALREADY_CONNECTED && packet->systemAddress.IsLANAddress()) { for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++) { SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i); if (sa.IsLANAddress()==false) { printf("Connected to self-LAN address. Changing DNS to point to this system.\n"); UpdateHostIPAsynch(sa); return CloudServerHelper::OnJoinCloudResult(packet, rakPeer, cloudServer, cloudClient, fullyConnectedMesh2, twoWayAuthentication, connectionGraph2, sa.ToString(false), myPublicIP); } } } return CloudServerHelper::OnJoinCloudResult(packet, rakPeer, cloudServer, cloudClient, fullyConnectedMesh2, twoWayAuthentication, connectionGraph2, rakPeerIpOrDomain, myPublicIP); } virtual void OnConnectionCountChange(RakPeerInterface *rakPeer, CloudClient *cloudClient) { SLNet::BitStream bs; CloudKey cloudKey("CloudConnCount",0); bs.Write(autopatcherLoad); cloudClient->Post(&cloudKey, bs.GetData(), bs.GetNumberOfBytesUsed(), rakPeer->GetMyGUID()); } char patcherHostSubdomainURL[128]; char patcherHostDomainURL[128]; char dnsHostIP[128]; char rackspaceAuthenticationURL[128]; char rackspaceDNSURL[128]; char rackspaceServersURL[128]; char rackspaceCloudUsername[128]; char apiAccessKey[128]; Rackspace2 *rackspace2; char patchHostRecordID[128]; protected: int raknetPatcherDomainId; SLNet::Time timeOfLastDNSHostCheck; struct ServerDetail { char publicIPV4[32]; char privateIPV4[32]; char flavorId[128]; char hostId[128]; char id[128]; char imageId[128]; char name[128]; } thisServerDetail; char spawningImageId[128]; char spawningFlavorId[128]; } *cloudServerHelper; class AutopatcherLoadNotifier : public SLNet::AutopatcherServerLoadNotifier { void AutopatcherLoadNotifier::OnQueueUpdate(SystemAddress remoteSystem, AutopatcherServerLoadNotifier::RequestType requestType, AutopatcherServerLoadNotifier::QueueOperation queueOperation, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState) { autopatcherLoad=autopatcherState->requestsQueued+autopatcherState->requestsWorking; //AutopatcherServerLoadNotifier_Printf::OnQueueUpdate(remoteSystem, requestType, queueOperation, autopatcherState); } void AutopatcherLoadNotifier::OnGetChangelistCompleted( SystemAddress remoteSystem, AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState) { autopatcherLoad=autopatcherState->requestsQueued+autopatcherState->requestsWorking; //AutopatcherServerLoadNotifier_Printf::OnGetChangelistCompleted(remoteSystem, getChangelistResult, autopatcherState); } void AutopatcherLoadNotifier::OnGetPatchCompleted(SystemAddress remoteSystem, AutopatcherServerLoadNotifier::PatchResult patchResult, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState) { autopatcherLoad=autopatcherState->requestsQueued+autopatcherState->requestsWorking; //AutopatcherServerLoadNotifier_Printf::OnGetPatchCompleted(remoteSystem, patchResult, autopatcherState); } }; void SetMaxConcurrentUsers(int i) { autopatcherServer->SetMaxConurrentUsers(i); } //char WORKING_DIRECTORY[MAX_PATH]; //char PATH_TO_XDELTA_EXE[MAX_PATH]; // The default AutopatcherPostgreRepository2 uses bsdiff which takes too much memory for large files. // I override MakePatch to use XDelta in this case class AutopatcherPostgreRepository2_WithXDelta : public SLNet::AutopatcherPostgreRepository2 { int MakePatch(const char *oldFile, const char *newFile, char **patch, unsigned int *patchLength, int *patchAlgorithm) { FILE *fpOld; fopen_s(&fpOld, oldFile, "rb"); fseek(fpOld, 0, SEEK_END); int contentLengthOld = ftell(fpOld); FILE *fpNew; fopen_s(&fpNew, newFile, "rb"); fseek(fpNew, 0, SEEK_END); int contentLengthNew = ftell(fpNew); char WORKING_DIRECTORY[MAX_PATH]; GetTempPathA(MAX_PATH, WORKING_DIRECTORY); if (WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=='\\' || WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=='/') WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=0; const char *PATH_TO_XDELTA_EXE = "c:/xdelta3-3.0.6-win32.exe"; bool shortContent = contentLengthOld < 33554432 && contentLengthNew < 33554432; if (shortContent || PATH_TO_XDELTA_EXE[0]==0) { if (shortContent==false) { printf("Warning: PATH_TO_XDELTA_EXE needed but not set.\ncontentLengthOld=%i\ncontentLengthNew=%i\n", contentLengthOld, contentLengthNew ); } // Use bsdiff, which does a good job but takes a lot of memory based on the size of the file *patchAlgorithm=0; bool b = MakePatchBSDiff(fpOld, contentLengthOld, fpNew, contentLengthNew, patch, patchLength); fclose(fpOld); fclose(fpNew); return b == false ? -1 : 0; } else { *patchAlgorithm=1; fclose(fpOld); fclose(fpNew); char buff[128]; SLNet::TimeUS time = SLNet::GetTimeUS(); #if defined(_WIN32) sprintf_s(buff, "%I64u", time); #else sprintf_s(buff, "%llu", (long long unsigned int) time); #endif // Invoke xdelta // See https://code.google.com/p/xdelta/wiki/CommandLineSyntax char commandLine[512]; _snprintf(commandLine, sizeof(commandLine)-1, "-f -s %s %s patchServer_%s.tmp", oldFile, newFile, buff); commandLine[511]=0; SHELLEXECUTEINFOA shellExecuteInfo; shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFOA); shellExecuteInfo.fMask = SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE; shellExecuteInfo.hwnd = NULL; shellExecuteInfo.lpVerb = "open"; shellExecuteInfo.lpFile = PATH_TO_XDELTA_EXE; shellExecuteInfo.lpParameters = commandLine; shellExecuteInfo.lpDirectory = WORKING_DIRECTORY; shellExecuteInfo.nShow = SW_SHOWNORMAL; shellExecuteInfo.hInstApp = NULL; ShellExecuteExA(&shellExecuteInfo); // ShellExecute is blocking, but if it writes a file to disk that file is not always immediately accessible after it returns. And this only happens in release, and only when not running in the debugger //ShellExecute(NULL, "open", PATH_TO_XDELTA_EXE, commandLine, WORKING_DIRECTORY, SW_SHOWNORMAL); char pathToPatch[MAX_PATH]; sprintf_s(pathToPatch, "%s/patchServer_%s.tmp", WORKING_DIRECTORY, buff); FILE *fpPatch; errno_t error = fopen_s(&fpPatch, pathToPatch, "r+b"); SLNet::TimeUS stopWaiting = time + 60000000 * 5; while (error!=0 && SLNet::GetTimeUS() < stopWaiting) { RakSleep(1000); error = fopen_s(&fpPatch, pathToPatch, "r+b"); } if (error!=0) { char buff[1024]; strerror_s(buff, errno); printf("\nERROR: Could not open %s.\nerr=%i (%s)\narguments=%s\n", pathToPatch, errno, buff, commandLine); return 1; } fseek(fpPatch, 0, SEEK_END); *patchLength = ftell(fpPatch); fseek(fpPatch, 0, SEEK_SET); *patch = (char*) rakMalloc_Ex(*patchLength, _FILE_AND_LINE_); fread(*patch, 1, *patchLength, fpPatch); fclose(fpPatch); int unlinkRes = _unlink(pathToPatch); while (unlinkRes!=0 && SLNet::GetTimeUS() < stopWaiting) { RakSleep(1000); unlinkRes = _unlink(pathToPatch); } if (unlinkRes!=0) { char buff[1024]; strerror_s(buff, errno); printf("\nWARNING: unlink %s failed.\nerr=%i (%s)\n", pathToPatch, errno, buff); } return 0; } } }; int main(int argc, char **argv) { #if OPEN_SSL_CLIENT_SUPPORT==0 #error RakNet must be compiled with OPEN_SSL_CLIENT_SUPPORT=1 to connect to Rackspace return 0; #endif cloudServerHelper = SLNet::OP_NEW(_FILE_AND_LINE_); if (!cloudServerHelper->ParseCommandLineParameters(argc, argv)) { return 1; } rakPeer = SLNet::RakPeerInterface::GetInstance(); SLNet::Time timeForNextLoadCheck= SLNet::GetTime()+LOAD_CHECK_INTERVAL; if (!cloudServerHelper->AuthenticateWithRackspaceBlocking()) { printf("Exiting due to failed startup with Rackspace\n"); return 1; } // This does not work unless it comes after AuthenticateWithRackspaceBlocking SLNet::PacketizedTCP packetizedTCP; if (packetizedTCP.Start(LISTEN_PORT_TCP_PATCHER,cloudServerHelper->allowedIncomingConnections)==false) { printf("Failed to start TCP. Is the port already in use?"); return 1; } appState=AP_RUNNING; timeSinceZeroUsers= SLNet::GetTime(); printf("Server starting... "); autopatcherServer = SLNet::OP_NEW(_FILE_AND_LINE_); // SLNet::FLP_Printf progressIndicator; SLNet::FileListTransfer fileListTransfer; AutopatcherPostgreRepository2_WithXDelta *connectionObject; connectionObject = SLNet::OP_NEW_ARRAY(sqlConnectionObjectCount, _FILE_AND_LINE_); // SLNet::AutopatcherRepositoryInterface **connectionObjectAddresses[sqlConnectionObjectCount]; AutopatcherRepositoryInterface **connectionObjectAddresses = SLNet::OP_NEW_ARRAY(sqlConnectionObjectCount, _FILE_AND_LINE_); for (int i=0; i < sqlConnectionObjectCount; i++) connectionObjectAddresses[i]=&connectionObject[i]; autopatcherServer->SetFileListTransferPlugin(&fileListTransfer); // PostgreSQL is fast, so this may not be necessary, or could use fewer threads // This is used to read increments of large files concurrently, thereby serving users downloads as other users read from the DB fileListTransfer.StartIncrementalReadThreads(sqlConnectionObjectCount); autopatcherServer->SetMaxConurrentUsers(workerThreadCount); // More users than this get queued up AutopatcherLoadNotifier autopatcherLoadNotifier; autopatcherServer->SetLoadManagementCallback(&autopatcherLoadNotifier); packetizedTCP.AttachPlugin(autopatcherServer); packetizedTCP.AttachPlugin(&fileListTransfer); autopatcherServer->SetAllowDownloadOfOriginalUnmodifiedFiles(allowDownloadingUnmodifiedFiles!=0); // ---- PLUGINS ----- // Used to load balance clients, allow for client to client discovery SLNet::CloudServer cloudServer; // Used to update the local cloudServer SLNet::CloudClient cloudClient; // Used to determine the host of the server fully connected mesh, as well as to connect servers automatically SLNet::FullyConnectedMesh2 fullyConnectedMesh2; // Used for servers to verify each other - otherwise any system could pose as a server // Could also be used to verify and restrict clients if paired with the MessageFilter plugin SLNet::TwoWayAuthentication twoWayAuthentication; // Used to tell servers about each other SLNet::ConnectionGraph2 connectionGraph2; rakPeer->AttachPlugin(&cloudServer); rakPeer->AttachPlugin(&cloudClient); rakPeer->AttachPlugin(&fullyConnectedMesh2); rakPeer->AttachPlugin(&twoWayAuthentication); rakPeer->AttachPlugin(&connectionGraph2); if (!cloudServerHelper->StartRakPeer(rakPeer)) return 1; SLNet::CloudServerHelperFilter sampleFilter; // Keeps clients from updating stuff to the server they are not supposed to sampleFilter.serverGuid=rakPeer->GetMyGUID(); cloudServerHelper->SetupPlugins(&cloudServer, &sampleFilter, &cloudClient, &fullyConnectedMesh2, &twoWayAuthentication,&connectionGraph2, cloudServerHelper->serverToServerPassword); int ret; do { ret = cloudServerHelper->JoinCloud(rakPeer, &cloudServer, &cloudClient, &fullyConnectedMesh2, &twoWayAuthentication, &connectionGraph2, cloudServerHelper->GetCloudHostAddress()); } while (ret==2); if (ret==1) return 1; printf("started.\n"); printf("Enter database password:\n"); char connectionString[256]; char username[256]; bool connectionSuccess=false; while (connectionSuccess==false) { strcpy_s(username, "postgres"); strcpy_s(connectionString, "user="); strcat_s(connectionString, username); strcat_s(connectionString, " password="); strcat_s(connectionString, databasePassword); int conIdx; for (conIdx=0; conIdx < sqlConnectionObjectCount; conIdx++) { if (connectionObject[conIdx].Connect(connectionString)==false) { printf("Database connection failed.\n"); printf("Try a different password? (y/n)\n"); if (_getch()!='y') { return 1; } else { connectionSuccess=false; printf("Enter password: "); Gets(databasePassword, sizeof(databasePassword)); break; } } } if (conIdx==sqlConnectionObjectCount) connectionSuccess=true; } printf("Database connection suceeded.\n"); printf("Starting threads\n"); // 4 Worker threads, which is CPU intensive // A greater number of SQL connections, which read files incrementally for large downloads autopatcherServer->StartThreads(workerThreadCount,sqlConnectionObjectCount, connectionObjectAddresses); autopatcherServer->CacheMostRecentPatch(0); printf("System ready for connections\n"); printf("(D)rop database\n(C)reate database.\n(U)pdate revision.\n(S)pawn a clone of this server\n(M)ax concurrent users (this server only)\n(I)mage the current state of this server\n(L)ist images\n(T)erminate cloud\n(Q)uit\n"); char ch; SLNet::Packet *p; while (appState!=AP_TERMINATED) { SLNet::SystemAddress notificationAddress; notificationAddress=packetizedTCP.HasCompletedConnectionAttempt(); // if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS) // printf("ID_CONNECTION_REQUEST_ACCEPTED\n"); notificationAddress=packetizedTCP.HasNewIncomingConnection(); // if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS) // printf("ID_NEW_INCOMING_CONNECTION\n"); notificationAddress=packetizedTCP.HasLostConnection(); // if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS) // printf("ID_CONNECTION_LOST\n"); p=packetizedTCP.Receive(); while (p) { packetizedTCP.DeallocatePacket(p); p=packetizedTCP.Receive(); } if (autopatcherLoad==0) { if (appState==AP_TERMINATE_WHEN_ZERO_USERS_REACHED) { cloudServerHelper->Terminate(); } if (timeSinceZeroUsers==0) timeSinceZeroUsers= SLNet::GetTime(); } else { timeSinceZeroUsers=0; } p=rakPeer->Receive(); while (p) { /* if (p->data[0]==ID_NEW_INCOMING_CONNECTION) printf("ID_NEW_INCOMING_CONNECTION (TCP) from %s\n", p->systemAddress.ToString(true)); else if (p->data[0]==ID_DISCONNECTION_NOTIFICATION) printf("ID_DISCONNECTION_NOTIFICATION (TCP) from %s\n", p->systemAddress.ToString(true)); else if (p->data[0]==ID_CONNECTION_LOST) printf("ID_CONNECTION_LOST (TCP) from %s\n", p->systemAddress.ToString(true)); else */ if (p->data[0]==ID_FCM2_NEW_HOST) { if (appState==AP_RUNNING) { SLNet::BitStream bs(p->data,p->length,false); bs.IgnoreBytes(1); RakNetGUID oldHost; bs.Read(oldHost); if (p->guid==rakPeer->GetMyGUID()) { if (oldHost!=UNASSIGNED_RAKNET_GUID) { printf("ID_FCM2_NEW_HOST: Taking over as host from the old host.\n"); } else { // Room not hosted if we become host the first time since this was done in CreateRoom() already printf("ID_FCM2_NEW_HOST: We have become host for the first time.\n"); } } timeForNextLoadCheck= SLNet::GetTime()+LOAD_CHECK_INTERVAL; } } else if (p->data[0]==ID_USER_PACKET_ENUM) { SLNet::BitStream bsIn(p->data, p->length, false); bsIn.IgnoreBytes((sizeof(MessageID))); RakString incomingPw; bsIn.Read(incomingPw); if (incomingPw==cloudServerHelper->serverToServerPassword) { SetMaxConcurrentUsers(0); if (appState==AP_RUNNING) { if (timeSinceZeroUsers!=0) cloudServerHelper->Terminate(); else appState=AP_TERMINATE_WHEN_ZERO_USERS_REACHED; } // Disconnect from the cloud, and do not accept new connections from users // The autopatcher will still run since it is on TCP rakPeer->Shutdown(1000); } } else if (p->data[0]==ID_CLOUD_GET_RESPONSE) { SLNet::CloudQueryResult cloudQueryResult; cloudClient.OnGetReponse(&cloudQueryResult, p); unsigned int rowIndex; const bool wasCallToGetServers=cloudQueryResult.cloudQuery.keys[0].primaryKey=="CloudConnCount"; RakAssert(wasCallToGetServers); /* if (wasCallToGetServers) printf("Downloaded server list. %i servers.\n", cloudQueryResult.rowsReturned.Size()); */ unsigned short connectionsOnOurServer=65535; unsigned short lowestConnectionsServer=65535; SLNet::SystemAddress lowestConnectionAddress; int totalConnections=0; for (rowIndex=0; rowIndex < cloudQueryResult.rowsReturned.Size(); rowIndex++) { SLNet::CloudQueryRow *row = cloudQueryResult.rowsReturned[rowIndex]; unsigned short connCount; SLNet::BitStream bsIn(row->data, row->length, false); bsIn.Read(connCount); printf("%i. Server found at %s with %i connections\n", rowIndex+1, row->serverSystemAddress.ToString(true), connCount); totalConnections+=connCount; } const int MAX_SERVERS_EVER=32; int available = cloudQueryResult.rowsReturned.Size() * sqlConnectionObjectCount - totalConnections; if (available < 0 && cloudQueryResult.rowsReturned.Size() < MAX_SERVERS_EVER // no more than MAX_SERVERS_EVER servers ever, to control costs ) { int newServersNeeded = -available / sqlConnectionObjectCount; if (newServersNeeded > 4) newServersNeeded = 4; // Do not start more than 4 servers at a time if (cloudQueryResult.rowsReturned.Size() + newServersNeeded > MAX_SERVERS_EVER) { newServersNeeded = MAX_SERVERS_EVER - cloudQueryResult.rowsReturned.Size(); } if (newServersNeeded>0) cloudServerHelper->SpawnServers(static_cast(newServersNeeded)); } timeForNextLoadCheck = SLNet::GetTime() + LOAD_CHECK_INTERVAL_AFTER_SPAWN; cloudClient.DeallocateWithDefaultAllocator(&cloudQueryResult); } cloudServerHelper->OnPacket(p, rakPeer, &cloudClient, &cloudServer, &fullyConnectedMesh2, &twoWayAuthentication, &connectionGraph2); rakPeer->DeallocatePacket(p); p=rakPeer->Receive(); } if (timeSinceZeroUsers!=0 && appState==AP_RUNNING && autopatcherServer->GetMaxConurrentUsers()>0) { // No users currently connected SLNet::Time curTime = SLNet::GetTime(); SLNet::Time diff = curTime - timeSinceZeroUsers; if (diff > 0) { if (fullyConnectedMesh2.IsHostSystem()==false) { // 2. Shutdown automatically if no users for 12 hours, except if we are the host if (diff > 1000 * 60 * 60 * 12) cloudServerHelper->Terminate(); } else { // We are host according to RakPeer. But no users for 24 hours // Verify we are the host according to Rackspace DNS records. If not, shutdown if (diff > 1000 * 60 * 60 * 24) { // No users for 24 hours, verify if we are the host according to Rackspace DNS records. If not, shutdown SLNet::Time diff2; diff2 = curTime - cloudServerHelper->GetTimeOfLastDNSHostCheck(); if (diff2 > 1000 * 60 * 60 * 24) { appState=AP_TERMINATE_IF_NOT_DNS_HOST; cloudServerHelper->SetTimeOfLastDNSHostCheck(curTime); cloudServerHelper->GetDomainRecords(); } } } } } cloudServerHelper->Update(); // 4. If we are the host, every 1 hour check load among all servers (including self). It takes like 30 minutes to build a new server. if (fullyConnectedMesh2.IsHostSystem() && autopatcherServer->GetMaxConurrentUsers()>0) { SLNet::Time curTime = SLNet::GetTime(); if (curTime > timeForNextLoadCheck) { timeForNextLoadCheck = curTime + LOAD_CHECK_INTERVAL; // If the load exceeds such that >=1 new fully loaded server is needed, add that many servers // See ID_CLOUD_GET_RESPONSE SLNet::CloudQuery cloudQuery; cloudQuery.keys.Push(SLNet::CloudKey("CloudConnCount",0),_FILE_AND_LINE_); // CloudConnCount is defined at the top of CloudServerHelper.cpp cloudQuery.subscribeToResults=false; cloudClient.Get(&cloudQuery, rakPeer->GetMyGUID()); } } if (_kbhit()) { ch=_getch(); if (ch=='q') break; else if (ch=='c') { if (connectionObject[0].CreateAutopatcherTables()==false) printf("%s", connectionObject[0].GetLastError()); else printf("Created tables.\n"); if (connectionObject[0].AddApplication(cloudServerHelper->patcherHostSubdomainURL, username)==false) printf("%s", connectionObject[0].GetLastError()); else printf("Added application\n"); } else if (ch=='d') { if (connectionObject[0].DestroyAutopatcherTables()==false) printf("%s", connectionObject[0].GetLastError()); } else if (ch=='i') { printf("Image this server?\nWill clone this server so that future servers made from this image will use its current state.\nPress 'y' to image.\n"); if (_getch()=='y') { cloudServerHelper->ImageThisServer(); } } else if (ch=='s') { printf("How many servers to spawn? (0-4) "); char str[32]; Gets(str, sizeof(str)); int num = atoi(str); if (num>0) cloudServerHelper->SpawnServers(static_cast(num)); } else if (ch=='m') { char maxConcurrent[64]; printf("Enter new allowed max concurrent users: "); Gets(maxConcurrent, sizeof(maxConcurrent)); if (maxConcurrent[0]) SetMaxConcurrentUsers(atoi(maxConcurrent)); } else if (ch=='t') { printf("Setting max concurrent users to 0 on all servers\n"); SetMaxConcurrentUsers(0); BitStream bsOut; bsOut.Write((MessageID)ID_USER_PACKET_ENUM); bsOut.Write(cloudServerHelper->serverToServerPassword); DataStructures::List remoteServersOut; cloudServer.GetRemoteServers(remoteServersOut); for (unsigned int i=0; i < remoteServersOut.Size(); i++) { rakPeer->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, remoteServersOut[i], false); rakPeer->CloseConnection(remoteServersOut[i], true); } printf("Remote servers will shutdown when all users are done\n"); printf("Disconnected from remote servers\n"); for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++) { SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i); if (sa.IsLANAddress()==false) { printf("Updating host DNS entry to this server\n"); cloudServerHelper->UpdateHostIPAsynch(sa); break; } } } else if (ch=='l') { cloudServerHelper->GetCustomImages(); } else if (ch=='u') { // printf("Enter application name: "); char appName[512]; strcpy_s(appName, cloudServerHelper->patcherHostSubdomainURL); // Gets(appName,sizeof(appName)); // if (appName[0]==0) // strcpy_s(appName, "TestApp"); printf("Enter application directory: "); char appDir[512]; Gets(appDir,sizeof(appDir)); if (appDir[0]==0) strcpy_s(appDir, "D:/temp"); if (connectionObject[0].UpdateApplicationFiles(appName, appDir, username, 0)==false) { printf("%s", connectionObject[0].GetLastError()); } else { printf("Update success.\n"); autopatcherServer->CacheMostRecentPatch(appName); } } } RakSleep(30); } SLNet::OP_DELETE_ARRAY(connectionObject, _FILE_AND_LINE_); SLNet::OP_DELETE_ARRAY(connectionObjectAddresses, _FILE_AND_LINE_); packetizedTCP.Stop(); SLNet::RakPeerInterface::DestroyInstance(rakPeer); SLNet::OP_DELETE(cloudServerHelper, _FILE_AND_LINE_); return 0; }