mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
383 lines
11 KiB
C
383 lines
11 KiB
C
#include <stddef.h>
|
|
#include "nninternal.h"
|
|
#include "NATify.h"
|
|
|
|
gsi_bool gotERT1, gotERT2, gotERT3;
|
|
gsi_bool gotADDRESS1a, gotADDRESS1b, gotADDRESS2, gotADDRESS3;
|
|
|
|
// prototypes for compilers that complain
|
|
const char * AddressToString(unsigned int ip, unsigned short port, char string[22]);
|
|
unsigned int NameToIp(const char *name);
|
|
|
|
|
|
const char * AddressToString(unsigned int ip, unsigned short port, char string[22])
|
|
{
|
|
static char strAddressArray[2][22];
|
|
static int nIndex;
|
|
char * strAddress;
|
|
|
|
if(string)
|
|
strAddress = string;
|
|
else
|
|
{
|
|
nIndex ^= 1;
|
|
strAddress = strAddressArray[nIndex];
|
|
}
|
|
|
|
if(ip)
|
|
{
|
|
IN_ADDR inAddr;
|
|
|
|
inAddr.s_addr = ip;
|
|
|
|
if(port)
|
|
sprintf(strAddress, "%s:%d", inet_ntoa(inAddr), port);
|
|
else
|
|
sprintf(strAddress, "%s", inet_ntoa(inAddr));
|
|
}
|
|
else if(port)
|
|
sprintf(strAddress, ":%d", port);
|
|
else
|
|
strAddress[0] = '\0';
|
|
|
|
return strAddress;
|
|
}
|
|
|
|
static unsigned int GetLocalIP()
|
|
{
|
|
int num_local_ips;
|
|
struct hostent *phost;
|
|
struct in_addr *addr;
|
|
unsigned int localip = 0;
|
|
phost = getlocalhost();
|
|
if (phost == NULL)
|
|
return 0;
|
|
for (num_local_ips = 0 ; ; num_local_ips++)
|
|
{
|
|
if (phost->h_addr_list[num_local_ips] == 0)
|
|
break;
|
|
addr = (struct in_addr *)phost->h_addr_list[num_local_ips];
|
|
if (addr->s_addr == htonl(0x7F000001))
|
|
continue;
|
|
localip = addr->s_addr;
|
|
|
|
if(IsPrivateIP(addr))
|
|
return localip;
|
|
}
|
|
return localip; //else a specific private address wasn't found - return what we've got
|
|
}
|
|
|
|
static unsigned short GetLocalPort(SOCKET sock)
|
|
{
|
|
int ret;
|
|
struct sockaddr_in saddr;
|
|
int saddrlen = sizeof(saddr);
|
|
ret = getsockname(sock,(struct sockaddr *)&saddr, &saddrlen);
|
|
if (gsiSocketIsError(ret))
|
|
return 0;
|
|
return saddr.sin_port;
|
|
}
|
|
|
|
unsigned int NameToIp(const char *name)
|
|
{
|
|
unsigned int ret;
|
|
struct hostent *hent;
|
|
|
|
ret = inet_addr(name);
|
|
|
|
if (ret == INADDR_NONE)
|
|
{
|
|
hent = gethostbyname(name);
|
|
if (!hent)
|
|
return 0;
|
|
ret = *(unsigned int *)hent->h_addr_list[0];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int DiscoverReachability(SOCKET sock, unsigned int ip, unsigned short port, int portType)
|
|
{
|
|
NatNegPacket p;
|
|
SOCKADDR_IN addr;
|
|
int len = sizeof(SOCKADDR_IN);
|
|
int success = 1;
|
|
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"(%d) Sending ERT Request to %s\n", portType, AddressToString(ip, port, NULL));
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = ip;
|
|
addr.sin_port = htons(port);
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
p.magic[0] = NN_MAGIC_0;
|
|
p.magic[1] = NN_MAGIC_1;
|
|
p.magic[2] = NN_MAGIC_2;
|
|
p.magic[3] = NN_MAGIC_3;
|
|
p.magic[4] = NN_MAGIC_4;
|
|
p.magic[5] = NN_MAGIC_5;
|
|
p.version = NN_PROTVER;
|
|
p.packettype = NN_NATIFY_REQUEST;
|
|
p.cookie = (int)htonl(NATIFY_COOKIE);
|
|
p.Packet.Init.porttype = (unsigned char)portType;
|
|
|
|
success = sendto(sock, (const char *)&p, sizeof(p), 0, (SOCKADDR *)&addr, sizeof(addr));
|
|
|
|
GSI_UNUSED(len);
|
|
return(success);
|
|
}
|
|
|
|
int DiscoverMapping(SOCKET sock, unsigned int ip, unsigned short port, int portType, int id)
|
|
{
|
|
NatNegPacket p;
|
|
SOCKADDR_IN addr;
|
|
int len = sizeof(SOCKADDR_IN);
|
|
int success;
|
|
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"Sending ADDRESS CHECK %d to %s\n", id, AddressToString(ip, port, NULL));
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = ip;
|
|
addr.sin_port = htons(port);
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
p.magic[0] = NN_MAGIC_0;
|
|
p.magic[1] = NN_MAGIC_1;
|
|
p.magic[2] = NN_MAGIC_2;
|
|
p.magic[3] = NN_MAGIC_3;
|
|
p.magic[4] = NN_MAGIC_4;
|
|
p.magic[5] = NN_MAGIC_5;
|
|
p.version = NN_PROTVER;
|
|
p.packettype = NN_ADDRESS_CHECK;
|
|
p.cookie = (int)htonl(id);
|
|
p.Packet.Init.porttype = (unsigned char)portType;
|
|
|
|
success = sendto(sock, (const char *)&p, sizeof(p), 0, (SOCKADDR *)&addr, sizeof(addr));
|
|
|
|
GSI_UNUSED(len);
|
|
return(success);
|
|
}
|
|
|
|
void OutputMapping(const AddressMapping * theMap)
|
|
{
|
|
if(theMap == NULL)
|
|
{
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"ERROR: Port mapping not available.");
|
|
}
|
|
else
|
|
{
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"Discovered map: [%s] -> [%s]\n", AddressToString(theMap->privateIp, theMap->privatePort, NULL),
|
|
AddressToString(theMap->publicIp, theMap->publicPort, NULL));
|
|
}
|
|
}
|
|
|
|
static int Think(SOCKET sock, NAT * nat)
|
|
{
|
|
static char data[NNINBUF_LEN];
|
|
int length;
|
|
unsigned char ptype;
|
|
NatNegPacket p;
|
|
struct sockaddr_in saddr;
|
|
int saddrlen = sizeof(struct sockaddr_in);
|
|
|
|
// Is natification complete?
|
|
if(gotERT1 && gotERT2 && gotERT3 &&
|
|
gotADDRESS1a && gotADDRESS1b && gotADDRESS2 && gotADDRESS3)
|
|
{
|
|
// Don't need to wait any longer, got all the stuff.
|
|
return(gsi_false);
|
|
}
|
|
|
|
// Process incoming data if there is any.
|
|
if(sock != INVALID_SOCKET)
|
|
{
|
|
// Check if there is data.
|
|
while(CanReceiveOnSocket(sock))
|
|
{
|
|
length = recvfrom(sock, data, NNINBUF_LEN, 0, (struct sockaddr *)&saddr, &saddrlen);
|
|
|
|
if (gsiSocketIsError(length))
|
|
{
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice,
|
|
"RECV SOCKET ERROR: %d\n", GOAGetLastError(sock));
|
|
break;
|
|
}
|
|
|
|
if (memcmp(data, NNMagicData, NATNEG_MAGIC_LEN) != 0)
|
|
return(gsi_true);
|
|
|
|
ptype = *(unsigned char *)(data + offsetof(NatNegPacket, packettype));
|
|
|
|
if (length < INITPACKET_SIZE)
|
|
return(gsi_true);
|
|
|
|
if(ptype == NN_ERTTEST)
|
|
{
|
|
memcpy(&p, data, INITPACKET_SIZE);
|
|
|
|
switch(p.Packet.Init.porttype)
|
|
{
|
|
case NN_PT_NN1:
|
|
// Got the solicited ERT reply.
|
|
gotERT1 = gsi_true;
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"(1) Got the solicited ERT from: %s\n", AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL));
|
|
break;
|
|
case NN_PT_NN2:
|
|
// Got the unsolicited IP ERT reply.
|
|
nat->ipRestricted = gsi_false;
|
|
gotERT2 = gsi_true;
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"(2) Got the unsolicited (address) ERT from: %s\n", AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL));
|
|
break;
|
|
case NN_PT_NN3:
|
|
// Got the unsolicited IP&Port ERT reply.
|
|
nat->portRestricted = gsi_false;
|
|
gotERT3 = gsi_true;
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"(3) Got the unsolicited (port) ERT from: %s\n", AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL));
|
|
break;
|
|
}
|
|
|
|
}
|
|
else if(ptype == NN_ADDRESS_REPLY)
|
|
{
|
|
memcpy(&p, data, INITPACKET_SIZE);
|
|
|
|
p.cookie = (int)ntohl(p.cookie);
|
|
|
|
switch(p.cookie)
|
|
{
|
|
case packet_map1a:
|
|
gotADDRESS1a = gsi_true; break;
|
|
case packet_map1b:
|
|
gotADDRESS1b = gsi_true; break;
|
|
case packet_map2:
|
|
gotADDRESS2 = gsi_true; break;
|
|
case packet_map3:
|
|
gotADDRESS3 = gsi_true; break;
|
|
}
|
|
|
|
nat->mappings[p.cookie].privateIp = GetLocalIP();
|
|
nat->mappings[p.cookie].privatePort = ntohs(GetLocalPort(sock));
|
|
nat->mappings[p.cookie].publicIp = p.Packet.Init.localip;
|
|
nat->mappings[p.cookie].publicPort = ntohs(p.Packet.Init.localport);
|
|
|
|
gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment,
|
|
"Got ADDRESS REPLY %d from: %s\n", p.cookie, AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL));
|
|
OutputMapping(&nat->mappings[p.cookie]);
|
|
}
|
|
|
|
if (sock == INVALID_SOCKET)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(gsi_true);
|
|
}
|
|
|
|
int NatifyThink(SOCKET sock, NAT * nat)
|
|
{
|
|
return(Think(sock, nat));
|
|
}
|
|
|
|
gsi_bool DetermineNatType(NAT * nat)
|
|
{
|
|
// Initialize.
|
|
nat->natType = unknown;
|
|
nat->promiscuity = promiscuity_not_applicable;
|
|
nat->qr2Compatible = gsi_true;
|
|
|
|
// Some of the address mappings are crucial in determining the NAT type.
|
|
// If we don't have them, then we should not proceed.
|
|
if(nat->mappings[packet_map1a].publicIp == 0 ||
|
|
nat->mappings[packet_map2].publicIp == 0 ||
|
|
nat->mappings[packet_map3].publicIp == 0)
|
|
return(gsi_false);
|
|
|
|
// Is there a NAT?
|
|
if(!nat->portRestricted &&
|
|
!nat->ipRestricted &&
|
|
(nat->mappings[packet_map1a].publicIp == nat->mappings[packet_map1a].privateIp))
|
|
{
|
|
nat->natType = no_nat;
|
|
}
|
|
else if(nat->mappings[packet_map1a].publicIp == nat->mappings[packet_map1a].privateIp)
|
|
{
|
|
nat->natType = firewall_only;
|
|
}
|
|
else
|
|
{
|
|
// What type of NAT is it?
|
|
if(!nat->ipRestricted &&
|
|
!nat->portRestricted &&
|
|
(abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1))
|
|
{
|
|
nat->natType = symmetric;
|
|
nat->promiscuity = promiscuous;
|
|
}
|
|
else if(nat->ipRestricted &&
|
|
!nat->portRestricted &&
|
|
(abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1))
|
|
{
|
|
nat->natType = symmetric;
|
|
nat->promiscuity = port_promiscuous;
|
|
}
|
|
else if(!nat->ipRestricted &&
|
|
nat->portRestricted &&
|
|
(abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1))
|
|
{
|
|
nat->natType = symmetric;
|
|
nat->promiscuity = ip_promiscuous;
|
|
}
|
|
else if(nat->ipRestricted &&
|
|
nat->portRestricted &&
|
|
(abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1))
|
|
{
|
|
nat->natType = symmetric;
|
|
nat->promiscuity = not_promiscuous;
|
|
}
|
|
else if(nat->portRestricted)
|
|
nat->natType = port_restricted_cone;
|
|
else if(nat->ipRestricted && !nat->portRestricted)
|
|
nat->natType = restricted_cone;
|
|
else if(!nat->ipRestricted && !nat->portRestricted)
|
|
nat->natType = full_cone;
|
|
else
|
|
nat->natType = unknown;
|
|
}
|
|
|
|
// What is the port mapping behavior?
|
|
if(nat->mappings[packet_map1a].publicPort == nat->mappings[packet_map1a].privatePort &&
|
|
nat->mappings[packet_map2].publicPort == nat->mappings[packet_map2].privatePort &&
|
|
nat->mappings[packet_map3].publicPort == nat->mappings[packet_map3].privatePort)
|
|
// Using private port as the public port.
|
|
nat->mappingScheme = private_as_public;
|
|
else if(nat->mappings[packet_map1a].publicPort == nat->mappings[packet_map2].publicPort &&
|
|
nat->mappings[packet_map2].publicPort == nat->mappings[packet_map3].publicPort)
|
|
// Using the same public port for all requests from the same private port.
|
|
nat->mappingScheme = consistent_port;
|
|
else if(nat->mappings[packet_map1a].publicPort == nat->mappings[packet_map1a].privatePort &&
|
|
nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort == 1)
|
|
// Using private port as the public port for the first mapping.
|
|
// Using an incremental (+1) port mapping scheme there after.
|
|
nat->mappingScheme = mixed;
|
|
else if(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort == 1)
|
|
// Using an incremental (+1) port mapping scheme.
|
|
nat->mappingScheme = incremental;
|
|
else
|
|
// Unrecognized port mapping scheme.
|
|
nat->mappingScheme = unrecognized;
|
|
|
|
if(nat->mappings[packet_map1b].publicPort != 0 && nat->mappings[packet_map1a].publicPort != nat->mappings[packet_map1b].publicPort)
|
|
{
|
|
// NOTE: The NAT maps different ports for the same destination.
|
|
// Game servers may not be properly reported to the GameSpy backend.
|
|
nat->qr2Compatible = gsi_false;
|
|
}
|
|
|
|
return(gsi_true);
|
|
}
|