/* * Copyright (c) 2014, Oculus VR, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #include "NativeFeatureIncludes.h" #if _RAKNET_SUPPORT_NatPunchthroughClient==1 #include "NatPunchthroughClient.h" #include "BitStream.h" #include "MessageIdentifiers.h" #include "RakPeerInterface.h" #include "GetTime.h" #include "PacketLogger.h" #include "Itoa.h" using namespace RakNet; void NatPunchthroughDebugInterface_Printf::OnClientMessage(const char *msg) { printf("%s\n", msg); } #if _RAKNET_SUPPORT_PacketLogger==1 void NatPunchthroughDebugInterface_PacketLogger::OnClientMessage(const char *msg) { if (pl) { pl->WriteMiscellaneous("Nat", msg); } } #endif STATIC_FACTORY_DEFINITIONS(NatPunchthroughClient,NatPunchthroughClient); NatPunchthroughClient::NatPunchthroughClient() { natPunchthroughDebugInterface=0; mostRecentExternalPort=0; sp.nextActionTime=0; portStride=0; hasPortStride=UNKNOWN_PORT_STRIDE; } NatPunchthroughClient::~NatPunchthroughClient() { rakPeerInterface=0; Clear(); } void NatPunchthroughClient::FindRouterPortStride(const SystemAddress &facilitator) { ConnectionState cs = rakPeerInterface->GetConnectionState(facilitator); if (cs!=IS_CONNECTED) return; if (hasPortStride!=UNKNOWN_PORT_STRIDE) return; hasPortStride=CALCULATING_PORT_STRIDE; portStrideCalTimeout = RakNet::GetTime()+5000; if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakString("Calculating port stride from %s", facilitator.ToString(true))); } RakNet::BitStream outgoingBs; outgoingBs.Write((MessageID)ID_NAT_REQUEST_BOUND_ADDRESSES); rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,facilitator,false); } bool NatPunchthroughClient::OpenNAT(RakNetGUID destination, const SystemAddress &facilitator) { ConnectionState cs = rakPeerInterface->GetConnectionState(facilitator); if (cs!=IS_CONNECTED) return false; if (hasPortStride==UNKNOWN_PORT_STRIDE) { FindRouterPortStride(facilitator); QueueOpenNAT(destination, facilitator); } else if (hasPortStride==CALCULATING_PORT_STRIDE) { QueueOpenNAT(destination, facilitator); } else { SendPunchthrough(destination, facilitator); } return true; } /* bool NatPunchthroughClient::OpenNATGroup(DataStructures::List destinationSystems, const SystemAddress &facilitator) { ConnectionState cs = rakPeerInterface->GetConnectionState(facilitator); if (cs!=IS_CONNECTED) return false; unsigned long i; for (i=0; i < destinationSystems.Size(); i++) { SendPunchthrough(destinationSystems[i], facilitator); } GroupPunchRequest *gpr = RakNet::OP_NEW(_FILE_AND_LINE_); gpr->facilitator=facilitator; gpr->pendingList=destinationSystems; groupPunchRequests.Push(gpr, _FILE_AND_LINE_); return true; } */ void NatPunchthroughClient::SetDebugInterface(NatPunchthroughDebugInterface *i) { natPunchthroughDebugInterface=i; } void NatPunchthroughClient::Update(void) { RakNet::Time time = RakNet::GetTime(); if (hasPortStride==CALCULATING_PORT_STRIDE && time > portStrideCalTimeout) { if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage("CALCULATING_PORT_STRIDE timeout"); } SendQueuedOpenNAT(); hasPortStride=UNKNOWN_PORT_STRIDE; } if (sp.nextActionTime && sp.nextActionTime < time) { RakNet::Time delta = time - sp.nextActionTime; if (sp.testMode==SendPing::TESTING_INTERNAL_IPS) { SendOutOfBand(sp.internalIds[sp.attemptCount],ID_NAT_ESTABLISH_UNIDIRECTIONAL); if (++sp.retryCount>=pc.UDP_SENDS_PER_PORT_INTERNAL) { ++sp.attemptCount; sp.retryCount=0; } if (sp.attemptCount>=pc.MAXIMUM_NUMBER_OF_INTERNAL_IDS_TO_CHECK) { sp.testMode=SendPing::WAITING_FOR_INTERNAL_IPS_RESPONSE; if (pc.INTERNAL_IP_WAIT_AFTER_ATTEMPTS>0) { sp.nextActionTime=time+pc.INTERNAL_IP_WAIT_AFTER_ATTEMPTS-delta; } else { sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT; sp.attemptCount=0; sp.sentTTL=false; } } else { sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_INTERNAL-delta; } } else if (sp.testMode==SendPing::WAITING_FOR_INTERNAL_IPS_RESPONSE) { sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT; sp.attemptCount=0; sp.sentTTL=false; } /* else if (sp.testMode==SendPing::SEND_WITH_TTL) { // Send to unused port. We do not want the message to arrive, just to open our router's table SystemAddress sa=sp.targetAddress; int ttlSendIndex; for (ttlSendIndex=0; ttlSendIndex <= pc.MAX_PREDICTIVE_PORT_RANGE; ttlSendIndex++) { sa.SetPortHostOrder((unsigned short) (sp.targetAddress.GetPort()+ttlSendIndex)); SendTTL(sa); } // Only do this stage once // Wait 250 milliseconds for next stage. The delay is so that even with timing errors both systems send out the // datagram with TTL before either sends a real one sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT; sp.nextActionTime=time-delta+250; } */ else if (sp.testMode==SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT) { if (sp.sentTTL==false) { SystemAddress sa=sp.targetAddress; sa.SetPortHostOrder((unsigned short) (sa.GetPort()+sp.attemptCount)); SendTTL(sa); if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakString("Send with TTL 2 to %s", sa.ToString(true))); } sp.nextActionTime = time+pc.EXTERNAL_IP_WAIT_AFTER_FIRST_TTL-delta; sp.sentTTL=true; } else { SystemAddress sa=sp.targetAddress; sa.SetPortHostOrder((unsigned short) (sa.GetPort()+sp.attemptCount)); SendOutOfBand(sa,ID_NAT_ESTABLISH_UNIDIRECTIONAL); IncrementExternalAttemptCount(time, delta); if (sp.attemptCount>pc.MAX_PREDICTIVE_PORT_RANGE) { sp.testMode=SendPing::WAITING_AFTER_ALL_ATTEMPTS; sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS-delta; // Skip TESTING_EXTERNAL_IPS_1024_TO_FACILITATOR_PORT, etc. /* sp.testMode=SendPing::TESTING_EXTERNAL_IPS_1024_TO_FACILITATOR_PORT; sp.attemptCount=0; */ } } } else if (sp.testMode==SendPing::TESTING_EXTERNAL_IPS_1024_TO_FACILITATOR_PORT) { SystemAddress sa=sp.targetAddress; if ( sp.targetGuid < rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS) ) sa.SetPortHostOrder((unsigned short) (1024+sp.attemptCount)); else sa.SetPortHostOrder((unsigned short) (sa.GetPort()+sp.attemptCount)); SendOutOfBand(sa,ID_NAT_ESTABLISH_UNIDIRECTIONAL); IncrementExternalAttemptCount(time, delta); if (sp.attemptCount>pc.MAX_PREDICTIVE_PORT_RANGE) { // From 1024 disabled, never helps as I've seen, but slows down the process by half sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_1024; sp.attemptCount=0; } } else if (sp.testMode==SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_1024) { SystemAddress sa=sp.targetAddress; if ( sp.targetGuid > rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS) ) sa.SetPortHostOrder((unsigned short) (1024+sp.attemptCount)); else sa.SetPortHostOrder((unsigned short) (sa.GetPort()+sp.attemptCount)); SendOutOfBand(sa,ID_NAT_ESTABLISH_UNIDIRECTIONAL); IncrementExternalAttemptCount(time, delta); if (sp.attemptCount>pc.MAX_PREDICTIVE_PORT_RANGE) { // From 1024 disabled, never helps as I've seen, but slows down the process by half sp.testMode=SendPing::TESTING_EXTERNAL_IPS_1024_TO_1024; sp.attemptCount=0; } } else if (sp.testMode==SendPing::TESTING_EXTERNAL_IPS_1024_TO_1024) { SystemAddress sa=sp.targetAddress; sa.SetPortHostOrder((unsigned short) (1024+sp.attemptCount)); SendOutOfBand(sa,ID_NAT_ESTABLISH_UNIDIRECTIONAL); IncrementExternalAttemptCount(time, delta); if (sp.attemptCount>pc.MAX_PREDICTIVE_PORT_RANGE) { if (natPunchthroughDebugInterface) { char ipAddressString[32]; sp.targetAddress.ToString(true, ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Likely bidirectional punchthrough failure to guid %s, system address %s.", guidString, ipAddressString)); } sp.testMode=SendPing::WAITING_AFTER_ALL_ATTEMPTS; sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS-delta; } } else if (sp.testMode==SendPing::WAITING_AFTER_ALL_ATTEMPTS) { // Failed. Tell the user OnPunchthroughFailure(); // UpdateGroupPunchOnNatResult(sp.facilitator, sp.targetGuid, sp.targetAddress, 1); } if (sp.testMode==SendPing::PUNCHING_FIXED_PORT) { SendOutOfBand(sp.targetAddress,ID_NAT_ESTABLISH_BIDIRECTIONAL); if (++sp.retryCount>=sp.punchingFixedPortAttempts) { if (natPunchthroughDebugInterface) { char ipAddressString[32]; sp.targetAddress.ToString(true, ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Likely unidirectional punchthrough failure to guid %s, system address %s.", guidString, ipAddressString)); } sp.testMode=SendPing::WAITING_AFTER_ALL_ATTEMPTS; sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS-delta; } else { if ((sp.retryCount%pc.UDP_SENDS_PER_PORT_EXTERNAL)==0) sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_BETWEEN_PORTS-delta; else sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL-delta; } } } /* // Remove elapsed groupRequestsInProgress unsigned int i; i=0; while (i < groupRequestsInProgress.Size()) { if (time > groupRequestsInProgress[i].time) groupRequestsInProgress.RemoveAtIndexFast(i); else i++; } */ } void NatPunchthroughClient::PushFailure(void) { Packet *p = AllocatePacketUnified(sizeof(MessageID)+sizeof(unsigned char)); p->data[0]=ID_NAT_PUNCHTHROUGH_FAILED; p->systemAddress=sp.targetAddress; p->systemAddress.systemIndex=(SystemIndex)-1; p->guid=sp.targetGuid; if (sp.weAreSender) p->data[1]=1; else p->data[1]=0; p->wasGeneratedLocally=true; rakPeerInterface->PushBackPacket(p, true); } void NatPunchthroughClient::OnPunchthroughFailure(void) { if (pc.retryOnFailure==false) { if (natPunchthroughDebugInterface) { char ipAddressString[32]; sp.targetAddress.ToString(true, ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Failed punchthrough once. Returning failure to guid %s, system address %s to user.", guidString, ipAddressString)); } PushFailure(); OnReadyForNextPunchthrough(); return; } unsigned int i; for (i=0; i < failedAttemptList.Size(); i++) { if (failedAttemptList[i].guid==sp.targetGuid) { if (natPunchthroughDebugInterface) { char ipAddressString[32]; sp.targetAddress.ToString(true, ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Failed punchthrough twice. Returning failure to guid %s, system address %s to user.", guidString, ipAddressString)); } // Failed a second time, so return failed to user PushFailure(); OnReadyForNextPunchthrough(); failedAttemptList.RemoveAtIndexFast(i); return; } } if (rakPeerInterface->GetConnectionState(sp.facilitator)!=IS_CONNECTED) { if (natPunchthroughDebugInterface) { char ipAddressString[32]; sp.targetAddress.ToString(true, ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Not connected to facilitator, so cannot retry punchthrough after first failure. Returning failure onj guid %s, system address %s to user.", guidString, ipAddressString)); } // Failed, and can't try again because no facilitator PushFailure(); return; } if (natPunchthroughDebugInterface) { char ipAddressString[32]; sp.targetAddress.ToString(true, ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("First punchthrough failure on guid %s, system address %s. Reattempting.", guidString, ipAddressString)); } // Failed the first time. Add to the failure queue and try again AddrAndGuid aag; aag.addr=sp.targetAddress; aag.guid=sp.targetGuid; failedAttemptList.Push(aag, _FILE_AND_LINE_); // Tell the server we are ready OnReadyForNextPunchthrough(); // If we are the sender, try again, immediately if possible, else added to the queue on the faciltiator if (sp.weAreSender) SendPunchthrough(sp.targetGuid, sp.facilitator); } PluginReceiveResult NatPunchthroughClient::OnReceive(Packet *packet) { switch (packet->data[0]) { case ID_NAT_GET_MOST_RECENT_PORT: { OnGetMostRecentPort(packet); return RR_STOP_PROCESSING_AND_DEALLOCATE; } case ID_NAT_PUNCHTHROUGH_FAILED: case ID_NAT_PUNCHTHROUGH_SUCCEEDED: if (packet->wasGeneratedLocally==false) return RR_STOP_PROCESSING_AND_DEALLOCATE; break; case ID_NAT_RESPOND_BOUND_ADDRESSES: { RakNet::BitStream bs(packet->data,packet->length,false); bs.IgnoreBytes(sizeof(MessageID)); unsigned char boundAddressCount; bs.Read(boundAddressCount); if (boundAddressCount<2) { if (natPunchthroughDebugInterface) natPunchthroughDebugInterface->OnClientMessage(RakString("INCAPABLE_PORT_STRIDE. My external ID is %s", rakPeerInterface->GetExternalID(packet->systemAddress).ToString())); hasPortStride=INCAPABLE_PORT_STRIDE; SendQueuedOpenNAT(); } SystemAddress boundAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS]; for (int i=0; i < boundAddressCount && i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; i++) { bs.Read(boundAddresses[i]); if (boundAddresses[i]!=packet->systemAddress) { RakNet::BitStream outgoingBs; outgoingBs.Write((MessageID)ID_NAT_PING); uint16_t externalPort = rakPeerInterface->GetExternalID(packet->systemAddress).GetPort(); outgoingBs.Write( externalPort ); rakPeerInterface->SendOutOfBand((const char*) boundAddresses[i].ToString(false),boundAddresses[i].GetPort(),(const char*) outgoingBs.GetData(),outgoingBs.GetNumberOfBytesUsed()); break; } } } return RR_STOP_PROCESSING_AND_DEALLOCATE; case ID_OUT_OF_BAND_INTERNAL: if (packet->length>=2 && packet->data[1]==ID_NAT_PONG) { RakNet::BitStream bs(packet->data,packet->length,false); bs.IgnoreBytes(sizeof(MessageID)*2); uint16_t externalPort; bs.Read(externalPort); uint16_t externalPort2; bs.Read(externalPort2); portStride = externalPort2 - externalPort; mostRecentExternalPort = externalPort2; hasPortStride=HAS_PORT_STRIDE; if (natPunchthroughDebugInterface) natPunchthroughDebugInterface->OnClientMessage(RakString("HAS_PORT_STRIDE %i. First external port %i. Second external port %i.", portStride, externalPort, externalPort2)); SendQueuedOpenNAT(); return RR_STOP_PROCESSING_AND_DEALLOCATE; } else if (packet->length>=2 && (packet->data[1]==ID_NAT_ESTABLISH_UNIDIRECTIONAL || packet->data[1]==ID_NAT_ESTABLISH_BIDIRECTIONAL) && sp.nextActionTime!=0) { RakNet::BitStream bs(packet->data,packet->length,false); bs.IgnoreBytes(2); uint16_t sessionId; bs.Read(sessionId); // RakAssert(sessionId<100); if (sessionId!=sp.sessionId) break; char ipAddressString[32]; packet->systemAddress.ToString(true,ipAddressString); // sp.targetGuid==packet->guid is because the internal IP addresses reported may include loopbacks not reported by RakPeer::IsLocalIP() if (packet->data[1]==ID_NAT_ESTABLISH_UNIDIRECTIONAL && sp.targetGuid==packet->guid) { if (sp.testMode!=SendPing::PUNCHING_FIXED_PORT) { sp.testMode=SendPing::PUNCHING_FIXED_PORT; sp.retryCount+=sp.attemptCount*pc.UDP_SENDS_PER_PORT_EXTERNAL; sp.targetAddress=packet->systemAddress; // Keeps trying until the other side gives up too, in case it is unidirectional sp.punchingFixedPortAttempts=pc.UDP_SENDS_PER_PORT_EXTERNAL*(pc.MAX_PREDICTIVE_PORT_RANGE+1); if (natPunchthroughDebugInterface) { char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("PUNCHING_FIXED_PORT: Received ID_NAT_ESTABLISH_UNIDIRECTIONAL from guid %s, system address %s.", guidString, ipAddressString)); } } else { if (natPunchthroughDebugInterface) { char guidString[128]; sp.targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Received ID_NAT_ESTABLISH_UNIDIRECTIONAL from guid %s, system address %s.", guidString, ipAddressString)); } } SendOutOfBand(sp.targetAddress,ID_NAT_ESTABLISH_BIDIRECTIONAL); } else if (packet->data[1]==ID_NAT_ESTABLISH_BIDIRECTIONAL && sp.targetGuid==packet->guid) { // They send back our port unsigned short ourExternalPort; bs.Read(ourExternalPort); if (mostRecentExternalPort==0) { mostRecentExternalPort=ourExternalPort; if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("ID_NAT_ESTABLISH_BIDIRECTIONAL mostRecentExternalPort first time set to %i", mostRecentExternalPort)); } } else { if (sp.testMode!=SendPing::TESTING_INTERNAL_IPS && sp.testMode!=SendPing::WAITING_FOR_INTERNAL_IPS_RESPONSE) { if (hasPortStride!=HAS_PORT_STRIDE) { portStride = ourExternalPort - mostRecentExternalPort; hasPortStride=HAS_PORT_STRIDE; if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakString("ID_NAT_ESTABLISH_BIDIRECTIONAL: Estimated port stride from incoming connection at %i. ourExternalPort=%i mostRecentExternalPort=%i", portStride, ourExternalPort, mostRecentExternalPort)); } SendQueuedOpenNAT(); } //nextExternalPort += portStride * (pc.MAX_PREDICTIVE_PORT_RANGE+1); mostRecentExternalPort = ourExternalPort; if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakString("ID_NAT_ESTABLISH_BIDIRECTIONAL: New mostRecentExternalPort %i", mostRecentExternalPort)); } } } SendOutOfBand(packet->systemAddress,ID_NAT_ESTABLISH_BIDIRECTIONAL); // Tell the user about the success sp.targetAddress=packet->systemAddress; PushSuccess(); //UpdateGroupPunchOnNatResult(sp.facilitator, sp.targetGuid, sp.targetAddress, 1); OnReadyForNextPunchthrough(); bool removedFromFailureQueue=RemoveFromFailureQueue(); if (natPunchthroughDebugInterface) { char guidString[128]; sp.targetGuid.ToString(guidString); if (removedFromFailureQueue) natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough to guid %s, system address %s succeeded on 2nd attempt.", guidString, ipAddressString)); else natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough to guid %s, system address %s succeeded on 1st attempt.", guidString, ipAddressString)); } } // mostRecentNewExternalPort=packet->systemAddress.GetPort(); } return RR_STOP_PROCESSING_AND_DEALLOCATE; case ID_NAT_ALREADY_IN_PROGRESS: { RakNet::BitStream incomingBs(packet->data, packet->length, false); incomingBs.IgnoreBytes(sizeof(MessageID)); RakNetGUID targetGuid; incomingBs.Read(targetGuid); // Don't update group, just use later message // UpdateGroupPunchOnNatResult(packet->systemAddress, targetGuid, UNASSIGNED_SYSTEM_ADDRESS, 2); if (natPunchthroughDebugInterface) { char guidString[128]; targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough retry to guid %s failed due to ID_NAT_ALREADY_IN_PROGRESS. Returning failure.", guidString)); } } break; case ID_NAT_TARGET_NOT_CONNECTED: case ID_NAT_CONNECTION_TO_TARGET_LOST: case ID_NAT_TARGET_UNRESPONSIVE: { const char *reason; if (packet->data[0]==ID_NAT_TARGET_NOT_CONNECTED) reason=(char *)"ID_NAT_TARGET_NOT_CONNECTED"; else if (packet->data[0]==ID_NAT_CONNECTION_TO_TARGET_LOST) reason=(char *)"ID_NAT_CONNECTION_TO_TARGET_LOST"; else reason=(char *)"ID_NAT_TARGET_UNRESPONSIVE"; RakNet::BitStream incomingBs(packet->data, packet->length, false); incomingBs.IgnoreBytes(sizeof(MessageID)); RakNetGUID targetGuid; incomingBs.Read(targetGuid); //UpdateGroupPunchOnNatResult(packet->systemAddress, targetGuid, UNASSIGNED_SYSTEM_ADDRESS, 2); if (packet->data[0]==ID_NAT_CONNECTION_TO_TARGET_LOST || packet->data[0]==ID_NAT_TARGET_UNRESPONSIVE) { uint16_t sessionId; incomingBs.Read(sessionId); if (sessionId!=sp.sessionId) break; } unsigned int i; for (i=0; i < failedAttemptList.Size(); i++) { if (failedAttemptList[i].guid==targetGuid) { if (natPunchthroughDebugInterface) { char guidString[128]; targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough retry to guid %s failed due to %s.", guidString, reason)); } // If the retry target is not connected, or loses connection, or is not responsive, then previous failures cannot be retried. // Don't need to return failed, the other messages indicate failure anyway /* Packet *p = AllocatePacketUnified(sizeof(MessageID)); p->data[0]=ID_NAT_PUNCHTHROUGH_FAILED; p->systemAddress=failedAttemptList[i].addr; p->systemAddress.systemIndex=(SystemIndex)-1; p->guid=failedAttemptList[i].guid; rakPeerInterface->PushBackPacket(p, false); */ failedAttemptList.RemoveAtIndexFast(i); break; } } if (natPunchthroughDebugInterface) { char guidString[128]; targetGuid.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough attempt to guid %s failed due to %s.", guidString, reason)); } // Stop trying punchthrough sp.nextActionTime=0; /* RakNet::BitStream bs(packet->data, packet->length, false); bs.IgnoreBytes(sizeof(MessageID)); RakNetGUID failedSystem; bs.Read(failedSystem); bool deletedFirst=false; unsigned int i=0; while (i < pendingOpenNAT.Size()) { if (pendingOpenNAT[i].destination==failedSystem) { if (i==0) deletedFirst=true; pendingOpenNAT.RemoveAtIndex(i); } else i++; } // Failed while in progress. Go to next in attempt queue if (deletedFirst && pendingOpenNAT.Size()) { SendPunchthrough(pendingOpenNAT[0].destination, pendingOpenNAT[0].facilitator); sp.nextActionTime=0; } */ } break; case ID_TIMESTAMP: if (packet->data[sizeof(MessageID)+sizeof(RakNet::Time)]==ID_NAT_CONNECT_AT_TIME) { OnConnectAtTime(packet); return RR_STOP_PROCESSING_AND_DEALLOCATE; } break; } return RR_CONTINUE_PROCESSING; } /* void NatPunchthroughClient::ProcessNextPunchthroughQueue(void) { // Go to the next attempt if (pendingOpenNAT.Size()) pendingOpenNAT.RemoveAtIndex(0); // Do next punchthrough attempt if (pendingOpenNAT.Size()) SendPunchthrough(pendingOpenNAT[0].destination, pendingOpenNAT[0].facilitator); sp.nextActionTime=0; } */ void NatPunchthroughClient::OnConnectAtTime(Packet *packet) { // RakAssert(sp.nextActionTime==0); RakNet::BitStream bs(packet->data, packet->length, false); bs.IgnoreBytes(sizeof(MessageID)); bs.Read(sp.nextActionTime); bs.IgnoreBytes(sizeof(MessageID)); bs.Read(sp.sessionId); bs.Read(sp.targetAddress); int j; // int k; // k=0; for (j=0; j < MAXIMUM_NUMBER_OF_INTERNAL_IDS; j++) bs.Read(sp.internalIds[j]); // Prevents local testing /* for (j=0; j < MAXIMUM_NUMBER_OF_INTERNAL_IDS; j++) { SystemAddress id; bs.Read(id); char str[32]; id.ToString(false,str); if (rakPeerInterface->IsLocalIP(str)==false) sp.internalIds[k++]=id; } */ sp.attemptCount=0; sp.retryCount=0; if (pc.MAXIMUM_NUMBER_OF_INTERNAL_IDS_TO_CHECK>0) { sp.testMode=SendPing::TESTING_INTERNAL_IPS; } else { // TESTING: Try sending to unused ports on the remote system to reserve our own ports while not getting banned //sp.testMode=SendPing::SEND_WITH_TTL; sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT; sp.attemptCount=0; sp.sentTTL=false; } bs.Read(sp.targetGuid); bs.Read(sp.weAreSender); } void NatPunchthroughClient::SendTTL(const SystemAddress &sa) { if (sa==UNASSIGNED_SYSTEM_ADDRESS) return; if (sa.GetPort()==0) return; char ipAddressString[32]; sa.ToString(false, ipAddressString); // TTL of 1 doesn't get past the router, 2 might hit the other system on a LAN rakPeerInterface->SendTTL(ipAddressString,sa.GetPort(), 2); } char *TestModeToString(NatPunchthroughClient::SendPing::TestMode tm) { switch (tm) { case NatPunchthroughClient::SendPing::TESTING_INTERNAL_IPS: return "TESTING_INTERNAL_IPS"; break; case NatPunchthroughClient::SendPing::WAITING_FOR_INTERNAL_IPS_RESPONSE: return "WAITING_FOR_INTERNAL_IPS_RESPONSE"; break; // case NatPunchthroughClient::SendPing::SEND_WITH_TTL: // return "SEND_WITH_TTL"; // break; case NatPunchthroughClient::SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT: return "TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_FACILITATOR_PORT"; break; case NatPunchthroughClient::SendPing::TESTING_EXTERNAL_IPS_1024_TO_FACILITATOR_PORT: return "TESTING_EXTERNAL_IPS_1024_TO_FACILITATOR_PORT"; break; case NatPunchthroughClient::SendPing::TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_1024: return "TESTING_EXTERNAL_IPS_FACILITATOR_PORT_TO_1024"; break; case NatPunchthroughClient::SendPing::TESTING_EXTERNAL_IPS_1024_TO_1024: return "TESTING_EXTERNAL_IPS_1024_TO_1024"; break; case NatPunchthroughClient::SendPing::WAITING_AFTER_ALL_ATTEMPTS: return "WAITING_AFTER_ALL_ATTEMPTS"; break; case NatPunchthroughClient::SendPing::PUNCHING_FIXED_PORT: return "PUNCHING_FIXED_PORT"; break; } return ""; } void NatPunchthroughClient::SendOutOfBand(SystemAddress sa, MessageID oobId) { if (sa==UNASSIGNED_SYSTEM_ADDRESS) return; if (sa.GetPort()==0) return; RakNet::BitStream oob; oob.Write(oobId); oob.Write(sp.sessionId); // RakAssert(sp.sessionId<100); if (oobId==ID_NAT_ESTABLISH_BIDIRECTIONAL) oob.Write(sa.GetPort()); char ipAddressString[32]; sa.ToString(false, ipAddressString); rakPeerInterface->SendOutOfBand((const char*) ipAddressString,sa.GetPort(),(const char*) oob.GetData(),oob.GetNumberOfBytesUsed()); if (natPunchthroughDebugInterface) { sa.ToString(true,ipAddressString); char guidString[128]; sp.targetGuid.ToString(guidString); // server - diff = my time // server = myTime + diff RakNet::Time clockDifferential = rakPeerInterface->GetClockDifferential(sp.facilitator); RakNet::Time serverTime = RakNet::GetTime() + clockDifferential; if (oobId==ID_NAT_ESTABLISH_UNIDIRECTIONAL) natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("%I64d: %s: OOB ID_NAT_ESTABLISH_UNIDIRECTIONAL to guid %s, system address %s.\n", serverTime, TestModeToString(sp.testMode), guidString, ipAddressString)); else natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("%I64d: %s: OOB ID_NAT_ESTABLISH_BIDIRECTIONAL to guid %s, system address %s.\n", serverTime, TestModeToString(sp.testMode), guidString, ipAddressString)); } } void NatPunchthroughClient::OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming) { (void) rakNetGUID; (void) isIncoming; // Try to track new port mappings on the router. Not reliable, but better than nothing. SystemAddress ourExternalId = rakPeerInterface->GetExternalID(systemAddress); if (ourExternalId!=UNASSIGNED_SYSTEM_ADDRESS && mostRecentExternalPort==0) { mostRecentExternalPort=ourExternalId.GetPort(); if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("OnNewConnection mostRecentExternalPort first time set to %i", mostRecentExternalPort)); } } /* unsigned int i; i=0; while (i < groupRequestsInProgress.Size()) { if (groupRequestsInProgress[i].guid==rakNetGUID) { groupRequestsInProgress.RemoveAtIndexFast(i); } else { i++; } } */ } void NatPunchthroughClient::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) { (void) systemAddress; (void) rakNetGUID; (void) lostConnectionReason; if (sp.facilitator==systemAddress) { // If we lose the connection to the facilitator, all previous failures not currently in progress are returned as such unsigned int i=0; while (i < failedAttemptList.Size()) { if (sp.nextActionTime!=0 && sp.targetGuid==failedAttemptList[i].guid) { i++; continue; } PushFailure(); failedAttemptList.RemoveAtIndexFast(i); } } /* unsigned int i; i=0; while (i < groupPunchRequests.Size()) { if (groupPunchRequests[i]->facilitator==systemAddress) { RakNet::OP_DELETE(groupPunchRequests[i],_FILE_AND_LINE_); groupPunchRequests.RemoveAtIndexFast(i); } else { i++; } } */ } void NatPunchthroughClient::GetUPNPPortMappings(char *externalPort, char *internalPort, const SystemAddress &natPunchthroughServerAddress) { DataStructures::List< RakNet::RakNetSocket2* > sockets; rakPeerInterface->GetSockets(sockets); Itoa(sockets[0]->GetBoundAddress().GetPort(),internalPort,10); if (mostRecentExternalPort==0) mostRecentExternalPort=rakPeerInterface->GetExternalID(natPunchthroughServerAddress).GetPort(); Itoa(mostRecentExternalPort,externalPort,10); } void NatPunchthroughClient::OnFailureNotification(Packet *packet) { RakNet::BitStream incomingBs(packet->data,packet->length,false); incomingBs.IgnoreBytes(sizeof(MessageID)); RakNetGUID senderGuid; incomingBs.Read(senderGuid); /* unsigned int i; i=0; while (i < groupRequestsInProgress.Size()) { if (groupRequestsInProgress[i].guid==senderGuid) { groupRequestsInProgress.RemoveAtIndexFast(i); break; } else { i++; } } */ } void NatPunchthroughClient::OnGetMostRecentPort(Packet *packet) { RakNet::BitStream incomingBs(packet->data,packet->length,false); incomingBs.IgnoreBytes(sizeof(MessageID)); uint16_t sessionId; incomingBs.Read(sessionId); RakNet::BitStream outgoingBs; outgoingBs.Write((MessageID)ID_NAT_GET_MOST_RECENT_PORT); outgoingBs.Write(sessionId); if (mostRecentExternalPort==0) { mostRecentExternalPort=rakPeerInterface->GetExternalID(packet->systemAddress).GetPort(); RakAssert(mostRecentExternalPort!=0); if (natPunchthroughDebugInterface) { natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("OnGetMostRecentPort mostRecentExternalPort first time set to %i", mostRecentExternalPort)); } } unsigned short portWithStride; if (hasPortStride==HAS_PORT_STRIDE) portWithStride = mostRecentExternalPort + portStride; else portWithStride = mostRecentExternalPort; outgoingBs.Write(portWithStride); rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->systemAddress,false); sp.facilitator=packet->systemAddress; } /* unsigned int NatPunchthroughClient::GetPendingOpenNATIndex(RakNetGUID destination, const SystemAddress &facilitator) { unsigned int i; for (i=0; i < pendingOpenNAT.Size(); i++) { if (pendingOpenNAT[i].destination==destination && pendingOpenNAT[i].facilitator==facilitator) return i; } return (unsigned int) -1; } */ void NatPunchthroughClient::QueueOpenNAT(RakNetGUID destination, const SystemAddress &facilitator) { DSTAndFac daf; daf.destination=destination; daf.facilitator=facilitator; queuedOpenNat.Push(daf, _FILE_AND_LINE_); } void NatPunchthroughClient::SendQueuedOpenNAT(void) { while (queuedOpenNat.IsEmpty()==false) { DSTAndFac daf = queuedOpenNat.Pop(); SendPunchthrough(daf.destination, daf.facilitator); } } void NatPunchthroughClient::SendPunchthrough(RakNetGUID destination, const SystemAddress &facilitator) { RakNet::BitStream outgoingBs; outgoingBs.Write((MessageID)ID_NAT_PUNCHTHROUGH_REQUEST); outgoingBs.Write(destination); rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,facilitator,false); // RakAssert(rakPeerInterface->GetSystemAddressFromGuid(destination)==UNASSIGNED_SYSTEM_ADDRESS); if (natPunchthroughDebugInterface) { char guidString[128]; destination.ToString(guidString); natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Starting ID_NAT_PUNCHTHROUGH_REQUEST to guid %s.", guidString)); } } void NatPunchthroughClient::OnAttach(void) { Clear(); } void NatPunchthroughClient::OnDetach(void) { Clear(); } void NatPunchthroughClient::OnRakPeerShutdown(void) { Clear(); } void NatPunchthroughClient::Clear(void) { OnReadyForNextPunchthrough(); failedAttemptList.Clear(false, _FILE_AND_LINE_); queuedOpenNat.Clear(_FILE_AND_LINE_); /* groupRequestsInProgress.Clear(false, _FILE_AND_LINE_); unsigned int i; for (i=0; i < groupPunchRequests.Size(); i++) { RakNet::OP_DELETE(groupPunchRequests[i],_FILE_AND_LINE_); } groupPunchRequests.Clear(true, _FILE_AND_LINE_); */ } PunchthroughConfiguration* NatPunchthroughClient::GetPunchthroughConfiguration(void) { return &pc; } void NatPunchthroughClient::OnReadyForNextPunchthrough(void) { if (rakPeerInterface==0) return; sp.nextActionTime=0; RakNet::BitStream outgoingBs; outgoingBs.Write((MessageID)ID_NAT_CLIENT_READY); rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,sp.facilitator,false); } void NatPunchthroughClient::PushSuccess(void) { Packet *p = AllocatePacketUnified(sizeof(MessageID)+sizeof(unsigned char)); p->data[0]=ID_NAT_PUNCHTHROUGH_SUCCEEDED; p->systemAddress=sp.targetAddress; p->systemAddress.systemIndex=(SystemIndex)-1; p->guid=sp.targetGuid; if (sp.weAreSender) p->data[1]=1; else p->data[1]=0; p->wasGeneratedLocally=true; rakPeerInterface->PushBackPacket(p, true); } bool NatPunchthroughClient::RemoveFromFailureQueue(void) { unsigned int i; for (i=0; i < failedAttemptList.Size(); i++) { if (failedAttemptList[i].guid==sp.targetGuid) { // Remove from failure queue failedAttemptList.RemoveAtIndexFast(i); return true; } } return false; } void NatPunchthroughClient::IncrementExternalAttemptCount(RakNet::Time time, RakNet::Time delta) { if (++sp.retryCount>=pc.UDP_SENDS_PER_PORT_EXTERNAL) { ++sp.attemptCount; sp.retryCount=0; sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_BETWEEN_PORTS-delta; sp.sentTTL=false; } else { sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL-delta; } } /* // 0=failed, 1=success, 2=ignore void NatPunchthroughClient::UpdateGroupPunchOnNatResult(SystemAddress facilitator, RakNetGUID targetSystem, SystemAddress targetSystemAddress, int result) { GroupPunchRequest *gpr; unsigned long i,j,k; i=0; while (i < groupPunchRequests.Size()) { gpr = groupPunchRequests[i]; if (gpr->facilitator==facilitator) { j=0; while (j < gpr->pendingList.Size()) { if (gpr->pendingList[j]==targetSystem) { if (result==0) { gpr->failedList.Push(targetSystem, _FILE_AND_LINE_); } else if (result==1) { gpr->passedListGuid.Push(targetSystem, _FILE_AND_LINE_); gpr->passedListAddress.Push(targetSystemAddress, _FILE_AND_LINE_); } else { gpr->ignoredList.Push(targetSystem, _FILE_AND_LINE_); } gpr->pendingList.RemoveAtIndex(j); } else j++; } } if (gpr->pendingList.Size()==0) { RakNet::BitStream output; if (gpr->failedList.Size()==0) { output.Write(ID_NAT_GROUP_PUNCH_SUCCEEDED); } else { output.Write(ID_NAT_GROUP_PUNCH_FAILED); } output.WriteCasted(gpr->passedListGuid.Size()); for (k=0; k < gpr->passedListGuid.Size(); k++) { output.Write(gpr->passedListGuid[k]); output.Write(gpr->passedListAddress[k]); } output.WriteCasted(gpr->ignoredList.Size()); for (k=0; k < gpr->ignoredList.Size(); k++) { output.Write(gpr->ignoredList[k]); } output.WriteCasted(gpr->failedList.Size()); for (k=0; k < gpr->failedList.Size(); k++) { output.Write(gpr->failedList[k]); } Packet *p = AllocatePacketUnified(output.GetNumberOfBytesUsed()); p->systemAddress=gpr->facilitator; p->systemAddress.systemIndex=(SystemIndex)-1; p->guid=rakPeerInterface->GetGuidFromSystemAddress(gpr->facilitator); p->wasGeneratedLocally=true; memcpy(p->data, output.GetData(), output.GetNumberOfBytesUsed()); rakPeerInterface->PushBackPacket(p, true); groupPunchRequests.RemoveAtIndex(i); RakNet::OP_DELETE(gpr, _FILE_AND_LINE_); } else i++; } } */ #endif // _RAKNET_SUPPORT_*