/*H*************************************************************************************************/ /*! \File dirtynet.c \Description Platform-independent network related routines. \Copyright Copyright (c) Electronic Arts 2002-2018 \Version 1.0 01/02/2002 (gschaefer) First Version \Version 1.1 01/27/2003 (jbrookes) Split from dirtynetwin.c */ /*************************************************************************************************H*/ /*** Include files *********************************************************************/ #include #include #include #include "DirtySDK/dirtysock.h" #include "DirtySDK/dirtysock/dirtymem.h" #include "DirtySDK/dirtysock/dirtynet.h" #include "dirtynetpriv.h" /*** Defines ***************************************************************************/ //! 30s hostname cache timeout #define DIRTYNET_HOSTNAMECACHELIFETIME (30*1000) //! verbose logging of dirtynet packet queue operations #define DIRTYNET_PACKETQUEUEDEBUG (DIRTYCODE_LOGGING && FALSE) //! verbose logging of dirtynet rate estimation #define DIRTYNET_RATEDEBUG (DIRTYCODE_LOGGING && FALSE) //! maximum allowable packet queue size #define DIRTYNET_PACKETQUEUEMAX (1024) //! minimum throttle rate supported #define DIRTYNET_MIN_THROTTLE_RATE (1460) /*** Macros ****************************************************************************/ /*** Type Definitions ******************************************************************/ //! socket hostname cache entry typedef struct SocketHostnameCacheEntryT { char strDnsName[256]; uint32_t uAddress; uint32_t uTimer; } SocketHostnameCacheEntryT; //! socket hostname cache struct SocketHostnameCacheT { int32_t iMaxEntries; int32_t iMemGroup; void *pMemGroupUserData; NetCritT Crit; SocketHostnameCacheEntryT CacheEntries[1]; //!< variable-length cache entry list }; //! socket packet queue struct SocketPacketQueueT { int32_t iMemGroup; void *pMemGroupUserData; int16_t iNumPackets; //!< number of packets in the queue int16_t iMaxPackets; //!< max queue size int16_t iPacketHead; //!< current packet queue head int16_t iPacketTail; //!< current packet queue tail uint32_t uLatency; //!< simulated packet latency target, in milliseconds uint32_t uDeviation; //!< simulated packet deviation target, in milliseconds uint32_t uPacketLoss; //!< packet loss percentage, 16.16 fractional integer uint32_t uPacketDrop; //!< number of packets overwritten due to queue overflow uint32_t uPacketMax; //!< maximum number of packets in the queue (high water mark) uint32_t uLatencyTime; //!< current amount of latency in the packet queue int32_t iDeviationTime; //!< current deviation SocketPacketQueueEntryT aPacketQueue[1]; //!< variable-length queue entry list }; #ifndef DIRTYCODE_NX //! socket address map entry struct SocketAddrMapEntryT { int32_t iRefCount; int32_t iVirtualAddress; struct sockaddr_in6 SockAddr6; }; #endif /*** Function Prototypes ***************************************************************/ /*** Variables *************************************************************************/ // Private variables // Public variables /*** Private Functions *****************************************************************/ #ifndef DIRTYCODE_NX /*F********************************************************************************/ /*! \Function _SockaddrIn6Identify \Description Identify IPv6 sockaddr based on specified matching sequence (must come at the start of address bytes). \Input *pAddr6 - address to identify \Input *pCheckVal - bytes to check \Input iValLen - length of byte sequence to check \Output uint8_t - TRUE if the address matches, else FALSE \Version 04/12/2016 (jbrookes) */ /********************************************************************************F*/ static uint8_t _SockaddrIn6Identify(const struct sockaddr_in6 *pAddr6, const uint8_t *pCheckVal, int32_t iValLen) { uint8_t bResult = !memcmp(pCheckVal, pAddr6->sin6_addr.s6_addr, iValLen) ? TRUE : FALSE; return(bResult); } /*F********************************************************************************/ /*! \Function _SockaddrIn6IsIPv4 \Description Identify if specified sockaddr_in6 is an IPv4-mapped IPv6 address \Input *pAddr6 - address to identify \Output uint8_t - TRUE if the address is IPv4-mapped, else FALSE \Version 04/12/2016 (jbrookes) */ /********************************************************************************F*/ static uint8_t _SockaddrIn6IsIPv4(const struct sockaddr_in6 *pAddr6) { const uint8_t aIpv4Prefix[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; return(_SockaddrIn6Identify(pAddr6, aIpv4Prefix, sizeof(aIpv4Prefix))); } /*F********************************************************************************/ /*! \Function _SockaddrIn6IsNAT64 \Description Identify if specified sockaddr_in6 is a NAT64 IPv6 address \Input *pAddr6 - address to identify \Output uint8_t - TRUE if the address is NAT64, else FALSE \Version 04/12/2016 (jbrookes) */ /********************************************************************************F*/ static uint8_t _SockaddrIn6IsNAT64(const struct sockaddr_in6 *pAddr6) { const uint8_t aNat64Prefix[] = { 0x00, 0x64, 0xff, 0x9b }; return(_SockaddrIn6Identify(pAddr6, aNat64Prefix, sizeof(aNat64Prefix))); } /*F********************************************************************************/ /*! \Function _SockaddrIn6IsZero \Description Identify if specified sockaddr_in6 is zero (unspecified) \Input *pAddr6 - address to identify \Output uint8_t - TRUE if the address is zero, else FALSE \Version 04/22/2016 (jbrookes) */ /********************************************************************************F*/ static uint8_t _SockaddrIn6IsZero(const struct sockaddr_in6 *pAddr6) { const uint8_t aIpv6Zero[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; return(_SockaddrIn6Identify(pAddr6, aIpv6Zero, sizeof(aIpv6Zero))); } /*F*************************************************************************************************/ /*! \Function _SockaddrIn6SetAddrText \Description Set Internet address component of sockaddr_in6 struct from textural address. \Input *pAddr6 - pointer to ipv6 address \Input *pStr - pointer to source ipv6 address \Output int32_t - zero=no error, negative=error \Notes See _SockaddrIn6GetAddrText for format considerations and references \Version 08/28/2017 (jbrookes) */ /*************************************************************************************************F*/ static int32_t _SockaddrIn6SetAddrText(struct sockaddr_in6 *pAddr6, const char *pStr) { uint16_t aAddrWords[8], *pWords, uWordVal; int32_t iWordIdx, iWordCt, iSkipIdx, iWriteIdx; const char *pEnd, *pDot; uint32_t uAddr32 = 0; // see if we have dot notation (e.g. nat64, ipv4-mapped) if ((pDot = strchr(pStr, '.')) != NULL) { // find start of dot notation number for (pDot -= 1; isalnum(*pDot) && (pDot > pStr); pDot -= 1) ; // get 32 bit address encoded in dot notation uAddr32 = SocketInTextGetAddr(pDot + 1); } // init word array ds_memclr(aAddrWords, sizeof(aAddrWords)); // convert address words, remember if we had a skip for (iWordIdx = 0, iSkipIdx = -1; (*pStr != '\0') && (iWordIdx < 8); pStr += 1) { uWordVal = SocketHtons(strtol(pStr, (char **)&pEnd, 16)); pStr = pEnd; if ((*pStr == ':') || (*pStr == '\0')) { aAddrWords[iWordIdx++] = uWordVal; } if ((*pStr == ':') && (pStr[1] == ':')) { iSkipIdx = iWordIdx; pStr += 1; } if ((pStr == pDot) && (uAddr32 != 0)) { aAddrWords[iWordIdx++] = SocketHtons((uint16_t)(uAddr32 >> 16)); aAddrWords[iWordIdx++] = SocketHtons((uint16_t)(uAddr32 >> 0)); break; } if (*pStr == '\0') { break; } } // copy to sockaddr, with skip for (iWordCt = iWordIdx, iWordIdx = iWriteIdx = 0, pWords = (uint16_t *)pAddr6->sin6_addr.s6_addr; iWriteIdx < 8; ) { if (iWriteIdx == iSkipIdx) { for (iWordCt = 8 - iWordCt; iWordCt > 0; iWordCt -= 1) { pWords[iWriteIdx++] = 0; } } else { pWords[iWriteIdx++] = aAddrWords[iWordIdx++]; } } return(0); } /*F*************************************************************************************************/ /*! \Function _SockaddrIn6GetAddrText \Description Return Internet address component of sockaddr_in6 struct in textual form (see below). \Input *pAddr6 - pointer to ipv6 address \Input *pStr - pointer to storage for text address \Input iLen - length of buffer \Output char * - returns pStr on success, NULL on failure \Notes IPv6 textual format guidelines are outlined in https://tools.ietf.org/html/rfc4291#section-2.2 and later refined in https://tools.ietf.org/html/rfc5952, which narrows the range of supported options to standardize are more rigid specification. This implementation follows the more limited set of specifications outlined in rfc5952. The short version is: - Preferred form is x:x:x:x:x:x:x:x, where the 'x's are one of four hexadecimal digits of the eight 16-bit pieces of the address - Leading zeros within a field MUST be suppressed - The use of :: indicates one or more groups of 16 bits of zeros; it can appear only once in an address - The :: symbole MUST be used to its maximum capability. It MUST NOT be used to shorten just one 16-bit field - If there are two or more sets of 16-bit zeros of equal length, the left-most MUST be shortened - Alphabetic characters in the address MUST be represented in lowercase - It is RECOMMENDED to use mixed notation if the address can be distinguished as having an IPv4 address embedded in the lower 32 bits solely from the address field through the use of a well-known prefix. \Version 08/28/2017 (jbrookes) */ /*************************************************************************************************F*/ static char *_SockaddrIn6GetAddrText(const struct sockaddr_in6 *pAddr6, char *pStr, int32_t iLen) { uint16_t aAddrWords[8], *pWords; int32_t iWord, iOffset; int32_t iZeroStart, iZeroCount; int32_t iZeroStartTmp, iZeroCountTmp; char strAddr32[16] = ""; // convert to words in host format for (iWord = 0, pWords = (uint16_t *)pAddr6->sin6_addr.s6_addr; iWord < 8; iWord += 1) { aAddrWords[iWord] = SocketNtohs(pWords[iWord]); } // if this is a mixed-notation address convert the final 32bits to a dot-notation address string if (_SockaddrIn6IsIPv4(pAddr6) || _SockaddrIn6IsNAT64(pAddr6)) { uint32_t uAddr32 = (uint32_t)aAddrWords[6] << 16 | (uint32_t)aAddrWords[7]; SocketInAddrGetText(uAddr32, strAddr32, sizeof(strAddr32)); } // find longest stretch of two or more zeros (if there is one) for (iWord = iZeroStart = iZeroCount = iZeroCountTmp = iZeroStartTmp = 0; iWord < 8; iWord += 1) { if (aAddrWords[iWord] == 0) { if (iZeroCountTmp == 0) { iZeroStartTmp = iWord; } iZeroCountTmp += 1; } else { iZeroCountTmp = 0; } if (iZeroCountTmp > iZeroCount) { iZeroStart = iZeroStartTmp; iZeroCount = iZeroCountTmp; } } // format address string for (iWord = 0, iOffset = 0; iWord < 8; iWord += 1) { if ((iWord != iZeroStart) || (iZeroCount < 2)) { iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, "%x", aAddrWords[iWord]); if (iWord < 7) { iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, ":"); } } else { iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, (iWord == 0) ? "::" : ":"); iWord += iZeroCount - 1; } // output dot portion of mixed-notation address, if present if ((strAddr32[0] != '\0') && (iWord == 5)) { iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, "%s", strAddr32); iWord += 2; } } // return to caller return(pStr); } #endif /*F*************************************************************************************************/ /*! \Function _SockaddrIn4SetAddrText \Description Set Internet address component of sockaddr struct from textual address (a.b.c.d). \Input *pAddr - sockaddr structure \Input *pStr - textual address \Output int32_t - zero=no error, negative=error \Version 10/04/1999 (gschaefer) */ /*************************************************************************************************F*/ static int32_t _SockaddrIn4SetAddrText(struct sockaddr *pAddr, const char *pStr) { uint8_t *pIpAddr = (uint8_t *)(pAddr->sa_data+2); int32_t iOctet; for (iOctet = 0; iOctet < 4; iOctet += 1, pStr += 1) { pIpAddr[iOctet] = '\0'; while ((*pStr >= '0') && (*pStr <= '9')) { pIpAddr[iOctet] = (pIpAddr[iOctet]*10) + (*pStr++ & 15); } if ((iOctet < 3) && (*pStr != '.')) { pIpAddr[0] = pIpAddr[1] = pIpAddr[2] = pIpAddr[3] = '\0'; return(-1); } } return(0); } /*** Public Functions ******************************************************************/ /*F*************************************************************************************************/ /*! \Function SockaddrCompare \Description Compare two sockaddr structs and see if address is the same. This is different from simple binary compare because only relevent fields are checked. \Input *pAddr1 - address #1 \Input *pAddr2 - address to compare with address #1 \Output int32_t - zero=no error, negative=error \Version 08/30/1999 (gschaefer) */ /*************************************************************************************************F*/ int32_t SockaddrCompare(const struct sockaddr *pAddr1, const struct sockaddr *pAddr2) { int32_t len = sizeof(*pAddr1)-sizeof(pAddr1->sa_family); // make sure address family matches if (pAddr1->sa_family != pAddr2->sa_family) { return(pAddr1->sa_family- pAddr2->sa_family); } // do type specific comparison if (pAddr1->sa_family == AF_INET) { // compare port and address len = 2 + 4; } else if (pAddr1->sa_family == AF_INET6) { // compare port, flow info, and address len = 2 + 4 + 8; } // binary compare of address data return(memcmp(pAddr1->sa_data, pAddr2->sa_data, len)); } /*F*************************************************************************************************/ /*! \Function SockaddrInSetAddrText \Description Set Internet address component of sockaddr struct from textual address. Important: Only works with AF_INET addresses for nx \Input *pAddr - sockaddr structure \Input *pStr - textual address \Output int32_t - zero=no error, negative=error \Version 10/04/1999 (gschaefer) */ /*************************************************************************************************F*/ int32_t SockaddrInSetAddrText(struct sockaddr *pAddr, const char *pStr) { int32_t iResult = -1; if (pAddr->sa_family == AF_INET) { iResult = _SockaddrIn4SetAddrText(pAddr, pStr); } #ifndef DIRTYCODE_NX else if (pAddr->sa_family == AF_INET6) { iResult = _SockaddrIn6SetAddrText((struct sockaddr_in6 *)pAddr, pStr); } #endif return(iResult); } /*F*************************************************************************************************/ /*! \Function SockaddrInGetAddrText \Description Convert a sockaddr into textual form based on address family \Input *pAddr - sockaddr struct \Input *pStr - address buffer \Input iLen - address length \Output char * - returns str on success, NULL on failure \Version 10/04/1999 (gschaefer) */ /*************************************************************************************************F*/ char *SockaddrInGetAddrText(const struct sockaddr *pAddr, char *pStr, int32_t iLen) { char *pResult = NULL; if (pAddr->sa_family == AF_INET) { pResult = SocketInAddrGetText(SockaddrInGetAddr(pAddr), pStr, iLen); } #ifndef DIRTYCODE_NX else if (pAddr->sa_family == AF_INET6) { pResult = _SockaddrIn6GetAddrText((const struct sockaddr_in6 *)pAddr, pStr, iLen); } #endif return(pResult); } /*F*************************************************************************************************/ /*! \Function SockaddrInParse \Description Convert textual internet address:port into sockaddr structure \Input *pAddr - sockaddr to fill in \Input *pParse - textual address \Output int32_t - flags: 0=parsed nothing 1=parsed addr 2=parsed port 3=parsed addr+port \Version 11/23/2002 (gschaefer) */ /*************************************************************************************************F*/ int32_t SockaddrInParse(struct sockaddr *pAddr, const char *pParse) { int32_t iReturn = 0, iPort = 0; uint32_t uAddr = 0; // init the address SockaddrInit(pAddr, AF_INET); // parse addr:port iReturn = SockaddrInParse2(&uAddr, &iPort, NULL, pParse); // set addr:port in sockaddr SockaddrInSetAddr(pAddr, uAddr); SockaddrInSetPort(pAddr, iPort); // return parse info return(iReturn); } /*F*************************************************************************************************/ /*! \Function SockaddrInParse2 \Description Convert textual internet address:port into sockaddr structure If the textual internet address:port is followed by a second :port, the second port is optionally parsed into pPort2, if not NULL. \Input *pAddr - address to fill in \Input *pPort - port to fill in \Input *pPort2 - second port to fill in \Input *pParse - textual address \Output int32_t - flags: 0=parsed nothing 1=parsed addr 2=parsed port 3=parsed addr+port 4=parsed port2 \Version 11/23/02 (GWS) First Version */ /*************************************************************************************************F*/ int32_t SockaddrInParse2(uint32_t *pAddr, int32_t *pPort, int32_t *pPort2, const char *pParse) { int32_t iReturn = 0; uint32_t uVal; // skip embedded white-space while ((*pParse > 0) && (*pParse <= ' ')) { ++pParse; } // parse the address (no dns for listen) for (uVal = 0; ((*pParse >= '0') && (*pParse <= '9')) || (*pParse == '.'); ++pParse) { // either add or shift if (*pParse != '.') { uVal = (uVal - (uVal & 255)) + ((uVal & 255) * 10) + (*pParse & 15); } else { uVal <<= 8; } } if ((*pAddr = uVal) != 0) { iReturn |= 1; } // skip non-port info while ((*pParse != ':') && (*pParse != 0)) { ++pParse; } // parse the port uVal = 0; if (*pParse == ':') { for (++pParse; (*pParse >= '0') && (*pParse <= '9'); ++pParse) { uVal = (uVal * 10) + (*pParse & 15); } iReturn |= 2; } *pPort = (int32_t)uVal; // parse port2 (optional) if (pPort2 != NULL) { uVal = 0; if (*pParse == ':') { for (++pParse; (*pParse >= '0') && (*pParse <= '9'); ++pParse) { uVal = (uVal * 10) + (*pParse & 15); } iReturn |= 4; } *pPort2 = (int32_t)uVal; } // return the address return(iReturn); } /*F*************************************************************************************************/ /*! \Function SocketInAddrGetText \Description Convert 32-bit internet address into textual form. \Input uAddr - address \Input *pStr - [out] address buffer \Input iLen - address length \Output char * - returns str on success, NULL on failure \Version 06/17/2009 (jbrookes) */ /*************************************************************************************************F*/ char *SocketInAddrGetText(uint32_t uAddr, char *pStr, int32_t iLen) { uint8_t uAddrByte[4]; int32_t iIndex; char *pStrStart = pStr; uAddrByte[0] = (uint8_t)(uAddr>>24); uAddrByte[1] = (uint8_t)(uAddr>>16); uAddrByte[2] = (uint8_t)(uAddr>>8); uAddrByte[3] = (uint8_t)(uAddr>>0); for (iIndex = 0; iIndex < 4; iIndex += 1) { uint32_t uNumber = uAddrByte[iIndex]; if (uNumber > 99) { *pStr++ = (char)('0' + (uNumber / 100)); uNumber %= 100; *pStr++ = (char)('0' + (uNumber / 10)); uNumber %= 10; } if (uNumber > 9) { *pStr++ = (char)('0' + (uNumber / 10)); uNumber %= 10; } *pStr++ = (char)('0' + uNumber); if (iIndex < 3) { *pStr++ = '.'; } } *pStr = '\0'; return(pStrStart); } /*F*************************************************************************************************/ /*! \Function SocketInTextGetAddr \Description Convert textual internet address into 32-bit integer form \Input *pAddrText - textual address \Output int32_t - integer form \Version 11/23/02 (JLB) First Version */ /*************************************************************************************************F*/ int32_t SocketInTextGetAddr(const char *pAddrText) { struct sockaddr SockAddr; int32_t iAddr = 0; SockaddrInit(&SockAddr, AF_INET); if (SockaddrInSetAddrText(&SockAddr, pAddrText) == 0) { iAddr = SockaddrInGetAddr(&SockAddr); } return(iAddr); } /*F*************************************************************************************************/ /*! \Function SocketHtons \Description Convert uint16_t from host to network byte order \Input uAddr - value to convert \Output uint16_t - converted value \Version 10/04/1999 (gschaefer) */ /*************************************************************************************************F*/ uint16_t SocketHtons(uint16_t uAddr) { uint8_t uNetw[2]; ds_memcpy_s(uNetw, sizeof(uNetw), &uAddr, sizeof(uAddr)); return((uNetw[0]<<8)|(uNetw[1]<<0)); } /*F*************************************************************************************************/ /*! \Function SocketHtonl \Description Convert uint32_t from host to network byte order. \Input uAddr - value to convert \Output uint32_t - converted value \Version 10/04/1999 (gschaefer) */ /*************************************************************************************************F*/ uint32_t SocketHtonl(uint32_t uAddr) { uint8_t uNetw[4]; ds_memcpy_s(uNetw, sizeof(uNetw), &uAddr, sizeof(uAddr)); return((((((uNetw[0]<<8)|uNetw[1])<<8)|uNetw[2])<<8)|uNetw[3]); } /*F*************************************************************************************************/ /*! \Function SocketNtohs \Description Convert uint16_t from network to host byte order. \Input uAddr - value to convert \Output uint16_t - converted value \Version 10/0/99 (GWS) First Version */ /*************************************************************************************************F*/ uint16_t SocketNtohs(uint16_t uAddr) { uint8_t uNetw[2]; uNetw[1] = (uint8_t)uAddr; uAddr >>= 8; uNetw[0] = (uint8_t)uAddr; ds_memcpy_s(&uAddr, sizeof(uAddr), uNetw, sizeof(uNetw)); return(uAddr); } /*F*************************************************************************************************/ /*! \Function SocketNtohl \Description Convert uint32_t from network to host byte order. \Input uAddr - value to convert \Output uint32_t - converted value \Version 10/04/1999 (gschaefer) */ /*************************************************************************************************F*/ uint32_t SocketNtohl(uint32_t uAddr) { uint8_t uNetw[4]; uNetw[3] = (uint8_t)uAddr; uAddr >>= 8; uNetw[2] = (uint8_t)uAddr; uAddr >>= 8; uNetw[1] = (uint8_t)uAddr; uAddr >>= 8; uNetw[0] = (uint8_t)uAddr; ds_memcpy_s(&uAddr, sizeof(uAddr), uNetw, sizeof(uNetw)); return(uAddr); } /* HostName Cache functions */ /*F********************************************************************************/ /*! \Function SocketHostnameCacheCreate \Description Create short-term hostname (DNS) cache \Input iMemGroup - memgroup to alloc/free with \Input *pMemGroupUserData - memgroup user data to alloc/free with \Output SocketHostnameCacheT * - hostname cache or NULL on failure \Version 10/09/2013 (jbrookes) */ /********************************************************************************F*/ SocketHostnameCacheT *SocketHostnameCacheCreate(int32_t iMemGroup, void *pMemGroupUserData) { const int32_t iMaxEntries = 16; int32_t iCacheSize = sizeof(SocketHostnameCacheT) + (iMaxEntries * sizeof(SocketHostnameCacheEntryT)); SocketHostnameCacheT *pCache; // alloc and init cache if ((pCache = DirtyMemAlloc(iCacheSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) { NetPrintf(("dirtynet: could not alloc %d bytes for hostname cache\n", iCacheSize)); return(NULL); } ds_memclr(pCache, iCacheSize); // set base info pCache->iMaxEntries = iMaxEntries; pCache->iMemGroup = iMemGroup; pCache->pMemGroupUserData = pMemGroupUserData; // initialize crit NetCritInit2(&pCache->Crit, "HostnameCache", NETCRIT_OPTION_SINGLETHREADENABLE); // return to caller return(pCache); } /*F********************************************************************************/ /*! \Function SocketHostnameCacheDestroy \Description Destroy short-term hostname (DNS) cache \Input *pCache - hostname cache \Version 10/09/2013 (jbrookes) */ /********************************************************************************F*/ void SocketHostnameCacheDestroy(SocketHostnameCacheT *pCache) { NetCritKill(&pCache->Crit); DirtyMemFree(pCache, SOCKET_MEMID, pCache->iMemGroup, pCache->pMemGroupUserData); } /*F********************************************************************************/ /*! \Function SocketHostnameCacheAdd \Description Add hostname and address to hostname cache \Input *pCache - hostname cache \Input *pStrHost - hostname to add \Input uAddress - address of hostname \Input iVerbose - debug level \Version 10/09/2013 (jbrookes) */ /********************************************************************************F*/ void SocketHostnameCacheAdd(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose) { SocketHostnameCacheEntryT *pCacheEntry; int32_t iCacheIdx; // if we're already in the cache, bail if (SocketHostnameCacheGet(pCache, pStrHost, 0) != 0) { return; } // scan cache for an open entry NetCritEnter(&pCache->Crit); for (iCacheIdx = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) { pCacheEntry = &pCache->CacheEntries[iCacheIdx]; if (pCacheEntry->uAddress == 0) { NetPrintfVerbose((iVerbose, 1, "dirtynet: adding hostname cache entry %s/%a\n", pStrHost, uAddress)); ds_strnzcpy(pCacheEntry->strDnsName, pStrHost, sizeof(pCacheEntry->strDnsName)); pCacheEntry->uAddress = uAddress; pCacheEntry->uTimer = NetTick(); break; } } NetCritLeave(&pCache->Crit); } /*F********************************************************************************/ /*! \Function SocketHostnameCacheGet \Description Get address for hostname from cache, if available. \Input *pCache - hostname cache \Input *pStrHost - hostname to add \Input iVerbose - debug level \Output uint32_t - address for hostname, or zero if not in cache \Version 10/09/2013 (jbrookes) */ /********************************************************************************F*/ uint32_t SocketHostnameCacheGet(SocketHostnameCacheT *pCache, const char *pStrHost, int32_t iVerbose) { SocketHostnameCacheEntryT *pCacheEntry; uint32_t uAddress; int32_t iCacheIdx; // scan cache for dns entry NetCritEnter(&pCache->Crit); for (iCacheIdx = 0, uAddress = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) { pCacheEntry = &pCache->CacheEntries[iCacheIdx]; // skip empty entries if (pCacheEntry->strDnsName[0] == '\0') { continue; } // check for entry we want if (!strcmp(pCacheEntry->strDnsName, pStrHost)) { NetPrintfVerbose((iVerbose, 0, "dirtynet: %s=%a [cache]\n", pCacheEntry->strDnsName, pCacheEntry->uAddress)); uAddress = pCache->CacheEntries[iCacheIdx].uAddress; break; } } NetCritLeave(&pCache->Crit); return(uAddress); } /*F********************************************************************************/ /*! \Function SocketHostnameCacheDel \Description Remove hostname cache entry from the cache, if it exists. pStrHost is checked if non-NULL and uAddress is checked if non-zero (note that both can be checked if desired). \Input *pCache - hostname cache \Input *pStrHost - hostname of cache entry to delete, or NULL \Input uAddress - address of cache entry to delete, or zero \Input iVerbose - debug level \Version 09/23/2016 (jbrookes) */ /********************************************************************************F*/ void SocketHostnameCacheDel(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose) { SocketHostnameCacheEntryT *pCacheEntry; int32_t iCacheIdx; // scan cache for dns entry NetCritEnter(&pCache->Crit); for (iCacheIdx = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) { pCacheEntry = &pCache->CacheEntries[iCacheIdx]; // skip empty entries if (pCacheEntry->strDnsName[0] == '\0') { continue; } // check for hostname match if ((pStrHost != NULL) && strcmp(pCacheEntry->strDnsName, pStrHost)) { continue; } // check for address match if ((uAddress != 0) && (pCacheEntry->uAddress != uAddress)) { continue; } // found a match; delete the entry NetPrintfVerbose((iVerbose, 1, "dirtynet: deleting hostname cache entry %s/%a\n", pCacheEntry->strDnsName, pCacheEntry->uAddress)); ds_memclr(pCacheEntry, sizeof(*pCacheEntry)); break; } NetCritLeave(&pCache->Crit); } /*F********************************************************************************/ /*! \Function SocketHostnameCacheProcess \Description Process the hostname cache, clear expired cache entries \Input *pCache - hostname cache \Input iVerbose - debug level \Version 04/23/2019 (eesponda) */ /********************************************************************************F*/ void SocketHostnameCacheProcess(SocketHostnameCacheT *pCache, int32_t iVerbose) { SocketHostnameCacheEntryT *pCacheEntry; uint32_t uCurTick; int32_t iCacheIdx; if (!NetCritTry(&pCache->Crit)) { return; } // scan cache for dns entry for (iCacheIdx = 0, uCurTick = NetTick(); iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1) { pCacheEntry = &pCache->CacheEntries[iCacheIdx]; // skip empty entries if (pCacheEntry->strDnsName[0] == '\0') { continue; } // check for expiration if (NetTickDiff(uCurTick, pCacheEntry->uTimer) > DIRTYNET_HOSTNAMECACHELIFETIME) { NetPrintfVerbose((iVerbose, 1, "dirtynet: expiring hostname cache entry %s/%a\n", pCacheEntry->strDnsName, pCacheEntry->uAddress)); ds_memclr(pCacheEntry, sizeof(*pCacheEntry)); } } NetCritLeave(&pCache->Crit); } /*F********************************************************************************/ /*! \Function SocketHostnameAddRef \Description Check for in-progress DNS requests we can piggyback on, instead of issuing a new request. \Input **ppHostList - list of active lookups \Input *pHost - current lookup \Input bUseRef - force using a new entry if bUseRef=FALSE (should normally be TRUE) \Output HostentT * - Pre-existing DNS request we have refcounted, or NULL \Version 01/16/2014 (jbrookes) */ /********************************************************************************F*/ HostentT *SocketHostnameAddRef(HostentT **ppHostList, HostentT *pHost, uint8_t bUseRef) { HostentT *pHost2 = NULL; // look for an in-progress refcounted lookup NetCritEnter(NULL); if (bUseRef == TRUE) { for (pHost2 = *ppHostList; pHost2 != NULL; pHost2 = pHost2->pNext) { if (!strcmp(pHost2->name, pHost->name) && !pHost2->done) { break; } } } // new lookup, so add to list if (pHost2 == NULL) { pHost->refcount = 1; pHost->pNext = *ppHostList; *ppHostList = pHost; pHost = NULL; } else // found an in-progress lookup, so piggyback on it { pHost = pHost2; pHost->refcount += 1; NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 0, "dirtynet: %s lookup refcounted (%d refs)\n", pHost->name, pHost->refcount)); } NetCritLeave(NULL); return(pHost); } /*F********************************************************************************/ /*! \Function SocketHostnameListProcess \Description Process list of in-progress DNS requests, disposing of those that are completed and no longer referenced. \Input **ppHostList - list of active lookups \Input iMemGroup - memgroup hostname lookup records are allocated with \Input *pMemGroupUserData - memgroup userdata hostname lookup records are allocated with \Notes This function is called from the SocketIdle thread, which is already guarded by the global critical section. It is therefore assumed that it does not need to be explicitly guarded here. \Version 01/16/2014 (jbrookes) */ /********************************************************************************F*/ void SocketHostnameListProcess(HostentT **ppHostList, int32_t iMemGroup, void *pMemGroupUserData) { HostentT **ppHost; for (ppHost = ppHostList; *ppHost != NULL;) { if ((*ppHost)->refcount == 0) { HostentT *pHost = *ppHost; *ppHost = (*ppHost)->pNext; DirtyMemFree(pHost, SOCKET_MEMID, iMemGroup, pMemGroupUserData); } else { ppHost = &(*ppHost)->pNext; } } } /* Packet Queue functions */ /*F********************************************************************************/ /*! \Function SocketPacketQueueCreate \Description Create a packet queue \Input iMaxPackets - size of queue, in packets (max 127) \Input iMemGroup - memgroup to alloc/free with \Input *pMemGroupUserData - memgroup user data to alloc/free with \Output SocketPacketQueueT * - packet queue, or NULL on failure \Version 02/21/2014 (jbrookes) */ /********************************************************************************F*/ SocketPacketQueueT *SocketPacketQueueCreate(int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData) { SocketPacketQueueT *pPacketQueue; int32_t iQueueSize; // enforce min/max queue sizes iMaxPackets = DS_CLAMP(iMaxPackets, 1, DIRTYNET_PACKETQUEUEMAX); // calculate memory required for queue iQueueSize = sizeof(*pPacketQueue) + ((iMaxPackets-1) * sizeof(pPacketQueue->aPacketQueue[0])); // alloc and init queue if ((pPacketQueue = DirtyMemAlloc(iQueueSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) { NetPrintf(("dirtynet: could not alloc %d bytes for packet queue\n", iQueueSize)); return(NULL); } ds_memclr(pPacketQueue, iQueueSize); // set base info pPacketQueue->iNumPackets = 0; pPacketQueue->iMaxPackets = iMaxPackets; pPacketQueue->iMemGroup = iMemGroup; pPacketQueue->pMemGroupUserData = pMemGroupUserData; // latency/packet loss simulation setup pPacketQueue->uLatencyTime = NetTick(); //$$temp - testing //pPacketQueue->uLatency = 100; //pPacketQueue->uDeviation = 5; //pPacketQueue->uPacketLoss = 5*65536; // return queue to caller return(pPacketQueue); } /*F********************************************************************************/ /*! \Function SocketPacketQueueDestroy \Description Destroy packet queue \Input *pPacketQueue - packet queue to destroy \Version 02/21/2014 (jbrookes) */ /********************************************************************************F*/ void SocketPacketQueueDestroy(SocketPacketQueueT *pPacketQueue) { DirtyMemFree(pPacketQueue, SOCKET_MEMID, pPacketQueue->iMemGroup, pPacketQueue->pMemGroupUserData); } /*F********************************************************************************/ /*! \Function SocketPacketQueueResize \Description Resize a packet queue, if the max packet size is different \Input *pPacketQueue - packet queue to resize (may be null) \Input iMaxPackets - new size of queue, in packets (max 127) \Input iMemGroup - memgroup to alloc/free with \Input *pMemGroupUserData - memgroup user data to alloc/free with \Output SocketPacketQueueT * - pointer to resized packet queue \Notes If the new max queue size is less than the number of packets in the current queue, packets will be overwritten in the usual manner (older discarded in favor of newer). \Version 05/30/2014 (jbrookes) */ /********************************************************************************F*/ SocketPacketQueueT *SocketPacketQueueResize(SocketPacketQueueT *pPacketQueue, int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData) { uint8_t aPacketData[SOCKET_MAXUDPRECV]; SocketPacketQueueT *pNewPacketQueue; struct sockaddr PacketAddr; int32_t iPacketSize; // enforce min/max queue sizes iMaxPackets = DS_CLAMP(iMaxPackets, 1, DIRTYNET_PACKETQUEUEMAX); // if we have a queue and it's already the right size, return it if ((pPacketQueue != NULL) && (pPacketQueue->iMaxPackets == iMaxPackets)) { return(pPacketQueue); } // create new queue NetPrintf(("dirtynet: [%p] re-creating socket packet queue with %d max packets\n", pPacketQueue, iMaxPackets)); if ((pNewPacketQueue = SocketPacketQueueCreate(iMaxPackets, iMemGroup, pMemGroupUserData)) == NULL) { NetPrintf(("dirtynet: could not allocate new packet queue\n")); return(pPacketQueue); } // copy old data (if any) over, and destroy the old packet queue if (pPacketQueue != NULL) { while ((iPacketSize = SocketPacketQueueRem(pPacketQueue, aPacketData, sizeof(aPacketData), &PacketAddr)) > 0) { SocketPacketQueueAdd(pNewPacketQueue, aPacketData, iPacketSize, &PacketAddr); } SocketPacketQueueDestroy(pPacketQueue); } // return resized queue to caller return(pNewPacketQueue); } /*F********************************************************************************/ /*! \Function SocketPacketQueueAdd \Description Add a packet to packet queue \Input *pPacketQueue - packet queue to add to \Input *pPacketData - packet data to add to queue \Input iPacketSize - size of packet data \Input *pPacketAddr - remote address associated with packet \Output int32_t - >=0: number of bytes buffered, -1: packet too large \Version 07/28/2020 (mclouatre) */ /********************************************************************************F*/ int32_t SocketPacketQueueAdd(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr) { return(SocketPacketQueueAdd2(pPacketQueue, pPacketData, iPacketSize, pPacketAddr, FALSE)); } /*F********************************************************************************/ /*! \Function SocketPacketQueueAdd2 \Description Add a packet to packet queue \Input *pPacketQueue - packet queue to add to \Input *pPacketData - packet data to add to queue \Input iPacketSize - size of packet data \Input *pPacketAddr - remote address associated with packet \Input bPartialAllowed - allow consuming only a portion of the submitted data (NX only) \Output int32_t - >=0: number of bytes buffered, -1: packet too large \Version 02/21/2014 (jbrookes) */ /********************************************************************************F*/ int32_t SocketPacketQueueAdd2(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr, uint32_t bPartialAllowed) { SocketPacketQueueEntryT *pQueueEntry; if (bPartialAllowed == FALSE) { // reject if packet data is too large if (iPacketSize > SOCKET_MAXUDPRECV) { NetPrintf(("dirtynet: [%p] packet too large to add to queue\n", pPacketQueue)); return(-1); } } // if queue is full, overwrite oldest member if (pPacketQueue->iNumPackets == pPacketQueue->iMaxPackets) { NetPrintf(("dirtynet: [%p] add to full queue; oldest entry will be overwritten\n", pPacketQueue)); pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; pPacketQueue->uPacketDrop += 1; } else { pPacketQueue->iNumPackets += 1; if (pPacketQueue->uPacketMax < (unsigned)pPacketQueue->iNumPackets) { pPacketQueue->uPacketMax = (unsigned)pPacketQueue->iNumPackets; } } // set packet entry NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry added (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets)); pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail]; if (iPacketSize > (signed)sizeof(pQueueEntry->aPacketData)) { iPacketSize = sizeof(pQueueEntry->aPacketData); } ds_memcpy_s(pQueueEntry->aPacketData, sizeof(pQueueEntry->aPacketData), pPacketData, iPacketSize); if (pPacketAddr) { ds_memcpy(&pQueueEntry->PacketAddr, pPacketAddr, sizeof(pQueueEntry->PacketAddr)); } else { ds_memclr(&pQueueEntry->PacketAddr, sizeof(pQueueEntry->PacketAddr)); pQueueEntry->PacketAddr.sa_family = AF_UNSPEC; } pQueueEntry->iPacketSize = iPacketSize; pQueueEntry->uPacketTick = NetTick(); // add to queue pPacketQueue->iPacketTail = (pPacketQueue->iPacketTail + 1) % pPacketQueue->iMaxPackets; NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); // return number of bytes buffered return(pQueueEntry->iPacketSize); } /*F********************************************************************************/ /*! \Function SocketPacketQueueAlloc \Description Alloc a packet queue entry. This is used when receiving data directly into the packet queue data buffer. \Input *pPacketQueue - packet queue to alloc entry from \Output SocketPacketQueueEntryT * - packet queue entry \Version 02/24/2014 (jbrookes) */ /********************************************************************************F*/ SocketPacketQueueEntryT *SocketPacketQueueAlloc(SocketPacketQueueT *pPacketQueue) { SocketPacketQueueEntryT *pQueueEntry; // if queue is full, alloc over oldest member if (pPacketQueue->iNumPackets == pPacketQueue->iMaxPackets) { // print a warning if we're altering a slot that is in use if (pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail].iPacketSize != -1) { NetPrintf(("dirtynet: [%p] alloc to full queue; oldest entry will be overwritten\n", pPacketQueue)); pPacketQueue->uPacketDrop += 1; } pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; } else { pPacketQueue->iNumPackets += 1; if (pPacketQueue->uPacketMax < (unsigned)pPacketQueue->iNumPackets) { pPacketQueue->uPacketMax = (unsigned)pPacketQueue->iNumPackets; } } // allocate queue entry pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail]; NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry alloc (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets)); // mark packet as allocated pQueueEntry->iPacketSize = -1; // add to queue pPacketQueue->iPacketTail = (pPacketQueue->iPacketTail + 1) % pPacketQueue->iMaxPackets; NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); return(pQueueEntry); } /*F********************************************************************************/ /*! \Function SocketPacketQueueAllocUndo \Description Undo a recent call to SocketPacketQueueAlloc(). Cannot be used if packet queue has been altered in between calling SocketPacketQueueAlloc() and SocketPacketQueueAllocUndo() \Input *pPacketQueue - packet queue to undo alloc entry from \Version 09/04/2018 (mclouatre) */ /********************************************************************************F*/ void SocketPacketQueueAllocUndo(SocketPacketQueueT *pPacketQueue) { pPacketQueue->iNumPackets -= 1; NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] recent alloc undone from tail (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets)); // modular arithmetic is not used here to avoid complications when the substraction underflows pPacketQueue->iPacketTail -= 1; if (pPacketQueue->iPacketTail < 0) { pPacketQueue->iPacketTail = pPacketQueue->iMaxPackets - 1; } NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); } /*F********************************************************************************/ /*! \Function SocketPacketQueueRem \Description Remove a packet from packet queue \Input *pPacketQueue - packet queue to remove from \Input *pPacketData - [out] storage for packet data or NULL \Input iPacketSize - size of packet output data buffer \Input *pPacketAddr - [out] storage for packet addr or NULL \Output int32_t - positive=size of packet, zero=no packet, negative=failure \Notes The packet data and address outputs are optional if you are just trying to remove the entry at the head. \Version 02/21/2014 (jbrookes) */ /********************************************************************************F*/ int32_t SocketPacketQueueRem(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr) { SocketPacketQueueEntryT *pQueueEntry; uint32_t uCurTick = NetTick(); // nothing to do if queue is empty if (pPacketQueue->iNumPackets == 0) { return(0); } // get head packet pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; // nothing to do if packet was allocated and has not been filled if (pQueueEntry->iPacketSize < 0) { return(0); } // apply simulated latency, if enabled if (pPacketQueue->uLatency != 0) { // compare to current latency if (NetTickDiff(uCurTick, pQueueEntry->uPacketTick) < (signed)pPacketQueue->uLatency + pPacketQueue->iDeviationTime) { NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] latency=%d, deviation=%d\n", pPacketQueue, NetTickDiff(uCurTick, pPacketQueue->uLatencyTime), pPacketQueue->iDeviationTime)); return(0); } // update packet receive timestamp SockaddrInSetMisc(&pQueueEntry->PacketAddr, uCurTick); // recalculate deviation pPacketQueue->iDeviationTime = (signed)NetRand(pPacketQueue->uDeviation*2) - (signed)pPacketQueue->uDeviation; } // get packet size to copy (will truncate if output buffer is too small) if (iPacketSize > pQueueEntry->iPacketSize) { iPacketSize = pQueueEntry->iPacketSize; } // copy out packet data and source if (pPacketData != NULL) { ds_memcpy(pPacketData, pQueueEntry->aPacketData, iPacketSize); } if (pPacketAddr != NULL) { ds_memcpy(pPacketAddr, &pQueueEntry->PacketAddr, sizeof(pQueueEntry->PacketAddr)); } // remove packet from queue pPacketQueue->iNumPackets -= 1; NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry removed from head in %dms (%d entries)\n", pPacketQueue, pPacketQueue->iPacketHead, NetTickDiff(NetTick(), pQueueEntry->uPacketTick), pPacketQueue->iNumPackets)); pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail)); // simulate packet loss if (pPacketQueue->uPacketLoss != 0) { uint32_t uRand = NetRand(100*65536); if (uRand < pPacketQueue->uPacketLoss) { NetPrintf(("dirtynet: [%p] lost packet (rand=%d, comp=%d)!\n", pPacketQueue, uRand, pPacketQueue->uPacketLoss)); return(0); } } // return success return(pQueueEntry->iPacketSize); } /*F********************************************************************************/ /*! \Function SocketPacketQueueRemStream \Description Remove a stream packet from packet queue \Input *pPacketQueue - packet queue to add to \Input *pPacketData - [out] storage for packet data or NULL \Input iPacketSize - size of packet output data buffer \Output int32_t - positive=amount of data read, zero=no packet, negative=failure \Notes The packet data output is optional if you are just trying to remove the entry at the head. \Version 11/20/2017 (eesponda) */ /********************************************************************************F*/ int32_t SocketPacketQueueRemStream(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize) { SocketPacketQueueEntryT *pQueueEntry; int32_t iPacketRead; // nothing to do if queue is empty if (pPacketQueue->iNumPackets == 0) { return(0); } // get head packet pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; // nothing to do if packet was allocated and has not been filled if (pQueueEntry->iPacketSize < 0) { return(0); } // get packet size to copy iPacketRead = DS_MIN(iPacketSize, pQueueEntry->iPacketSize); // copy the packet data and offset by amount read if (pPacketData != NULL) { ds_memcpy(pPacketData, pQueueEntry->aPacketData, iPacketRead); pPacketData += iPacketRead; iPacketSize -= iPacketRead; } // if we copied less than the size of the entry adjust the entry as needed, otherwise remove packet from queue if ((iPacketRead > 0) && (iPacketRead < pQueueEntry->iPacketSize)) { memmove(pQueueEntry->aPacketData, pQueueEntry->aPacketData+iPacketRead, pQueueEntry->iPacketSize-iPacketRead); pQueueEntry->iPacketSize -= iPacketRead; } else { pPacketQueue->iNumPackets -= 1; pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets; } // continue to copy if we still have space in the buffer if (iPacketSize > 0) { iPacketRead += SocketPacketQueueRemStream(pPacketQueue, pPacketData, iPacketSize); } return(iPacketRead); } /*F********************************************************************************/ /*! \Function SocketPacketQueueGetHead \Description Get pointers to data associated with queue head. \Input *pPacketQueue - packet queue to remove from \Input **ppPacketData - [out] to be filled with pointer to packet data (can be NULL) \Input *pPacketSize - [out] to be filled with size of packet data (can be NULL) \Input **ppPacketAddr - [out] to be filled with pointer to packet addr (can be NULL) \Output int32_t - positive=size of packet, zero=no packet \Version 08/07/2020 (mclouatre) */ /********************************************************************************F*/ int32_t SocketPacketQueueGetHead(SocketPacketQueueT *pPacketQueue, uint8_t **ppPacketData, int32_t *pPacketSize, struct sockaddr **ppPacketAddr) { SocketPacketQueueEntryT *pQueueEntry; // nothing to do if queue is empty if (pPacketQueue->iNumPackets == 0) { return(0); } // get head packet pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; // nothing to do if packet was allocated and has not been filled if (pQueueEntry->iPacketSize < 0) { return(0); } // fill output variables if (ppPacketData != NULL) { *ppPacketData = pQueueEntry->aPacketData; } if (pPacketSize != NULL) { *pPacketSize = pQueueEntry->iPacketSize; } if (ppPacketAddr != NULL) { *ppPacketAddr = &pQueueEntry->PacketAddr; } // return success return(pQueueEntry->iPacketSize); } /*F********************************************************************************/ /*! \Function SocketPacketQueueTouchHead \Description Update queue's head entry such that it no longer includes successfully consumed portion of data. \Input *pPacketQueue - packet queue to remove from \Input iConsumedSize - amount of data consumed from head entry (in bytes) \Output int32_t - 0 success, negative: error \Version 08/07/2020 (mclouatre) */ /********************************************************************************F*/ int32_t SocketPacketQueueTouchHead(SocketPacketQueueT *pPacketQueue, int32_t iConsumedSize) { SocketPacketQueueEntryT *pQueueEntry; // nothing to do if queue is empty if (pPacketQueue->iNumPackets == 0) { return(-1); } // get head packet pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead]; // nothing to do if packet was allocated and has not been filled if (pQueueEntry->iPacketSize < 0) { return(-2); } // update size of packet queue entry pQueueEntry->iPacketSize -= iConsumedSize; // remove consumed data from data buffer memmove(pQueueEntry->aPacketData, pQueueEntry->aPacketData + iConsumedSize, pQueueEntry->iPacketSize); // return success return(0); } /*F********************************************************************************/ /*! \Function SocketPacketQueueControl \Description Control socket packet queue options \Input *pPacketQueue - packet queue control function; different selectors control different behaviors \Input iControl - control selector \Input iValue - selector specific \Output int32_t - selector result \Notes iControl can be one of the following: \verbatim 'pdev' - set simulated packet deviation 'plat' - set simulated packet latency 'plos' - set simulated packet loss \endverbatim \Version 10/07/2014 (jbrookes) */ /********************************************************************************F*/ int32_t SocketPacketQueueControl(SocketPacketQueueT *pPacketQueue, int32_t iControl, int32_t iValue) { if (iControl == 'pdev') { NetPrintf(("dirtynet: setting simulated packet deviation=%dms\n", iValue)); pPacketQueue->uDeviation = iValue; return(0); } if (iControl == 'plat') { NetPrintf(("dirtynet: setting simulated packet latency=%dms\n", iValue)); pPacketQueue->uLatency = iValue; return(0); } if (iControl == 'plos') { NetPrintf(("dirtynet: setting simulated packet loss to %d.%d\n", iValue >> 16, iValue & 0xffff)); pPacketQueue->uPacketLoss = iValue; return(0); } return(-1); } /*F********************************************************************************/ /*! \Function SocketPacketQueueStatus \Description Get status of socket packet queue \Input *pPacketQueue - packet queue to get status of \Input iStatus - status selector \Output int32_t - selector result \Notes iStatus can be one of the following: \verbatim 'pful' - TRUE if queue is full, FALSE otherwise 'pdrp' - number of packets overwritten due to queue overflow 'pmax' - maximum number of packets in queue (high water) 'pnum' - number of packets in queue 'psiz' - queue size (in packets) \endverbatim \Version 10/20/2015 (jbrookes) */ /********************************************************************************F*/ int32_t SocketPacketQueueStatus(SocketPacketQueueT *pPacketQueue, int32_t iStatus) { if (iStatus == 'pful') { return(pPacketQueue->iMaxPackets == pPacketQueue->iNumPackets ? TRUE : FALSE); } if (iStatus == 'pdrp') { return((signed)pPacketQueue->uPacketDrop); } if (iStatus == 'pmax') { return((signed)pPacketQueue->uPacketMax); } if (iStatus == 'pnum') { return(pPacketQueue->iNumPackets); } if (iStatus == 'psiz') { return(pPacketQueue->iMaxPackets); } // invalid selector return(-1); } /* Rate functions */ /*F********************************************************************************/ /*! \Function SocketRateUpdate \Description Update socket data rate estimation \Input *pRate - state used to store rate estimation data \Input iData - amount of data being sent/recv \Input *pOpName - indicates send or recv (for debug use only) \Notes Rate estimation is based on a rolling 16-deep history of ~100ms samples of data rate (the actual period may vary slightly based on update rate and tick resolution) covering a total of ~1600-1700ms. We also keep track of the rate we are called at (excluding multiple calls within the same tick) so we can estimate how much data we need to have sent by the next time we are called. This is important because we will always be shooting lower than our cap if we don't consider this factor, and the amount we are shooting lower by increases the slower the update rate. \Version 08/20/2014 (jbrookes) */ /********************************************************************************F*/ void SocketRateUpdate(SocketRateT *pRate, int32_t iData, const char *pOpName) { const uint32_t uMaxIndex = (unsigned)(sizeof(pRate->aDataHist)/sizeof(pRate->aDataHist[0])); uint32_t uCurTick = NetTick(), uOldTick; uint32_t uIndex, uCallRate, uCallSum, uDataSum; int32_t iTickDiff; // reserve tick=0 as uninitialized if (uCurTick == 0) { uCurTick = 1; } // initialize update tick counters if (pRate->uLastTick == 0) { pRate->uLastTick = NetTickDiff(uCurTick, 2); // initialize rate tick counter to current-100 so as to force immediate history update pRate->uLastRateTick = NetTickDiff(uCurTick, 100); // start off at max rate pRate->uCurRate = pRate->uNextRate = pRate->uMaxRate; } // exclude error results if (iData < 0) { return; } // update the data pRate->aDataHist[pRate->uDataIndex] += iData; /* update the call count, only only if time has elapsed since our last call, since we want the true update rate) */ if (NetTickDiff(uCurTick, pRate->uLastTick) > 1) { pRate->aCallHist[pRate->uDataIndex] += 1; } // update last update tick pRate->uLastTick = uCurTick; /* update timestamp, but only if it hasn't been updated already. we do this so we can correctly update the rate calculation continuously with the current sample */ if (pRate->aTickHist[pRate->uDataIndex] == 0) { pRate->aTickHist[pRate->uDataIndex] = uCurTick; } // get oldest tick & sum recorded data & callcounts for (uIndex = (pRate->uDataIndex + 1) % uMaxIndex, uOldTick = 0, uCallSum = 0, uDataSum = 0; ; uIndex = (uIndex + 1) % uMaxIndex) { // skip uninitialized tick values if ((uOldTick == 0) && (pRate->aTickHist[uIndex] != 0)) { uOldTick = pRate->aTickHist[uIndex]; } // update call sum uCallSum += pRate->aCallHist[uIndex]; // update data sum uDataSum += pRate->aDataHist[uIndex]; // quit when we've hit every entry if (uIndex == pRate->uDataIndex) { break; } } // update rate estimation if ((iTickDiff = NetTickDiff(uCurTick, uOldTick)) > 0) { // calculate call rate uCallRate = (uCallSum > 0) ? iTickDiff/uCallSum : 0; // update current rate estimation pRate->uCurRate = (uDataSum * 1000) / iTickDiff; // update next rate estimation (fudge slightly by 2x as it gives us better tracking to our desired rate) pRate->uNextRate = (uDataSum * 1000) / (iTickDiff+(uCallRate*2)); #if DIRTYNET_RATEDEBUG if (pRate->uCurRate != 0) { NetPrintf(("dirtynet: [%p] rate=%4.2fkb nextrate=%4.2f callrate=%dms tickdiff=%d tick=%d\n", pRate, ((float)pRate->uCurRate)/1024.0f, ((float)pRate->uNextRate)/1024.0f, uCallRate, iTickDiff, uCurTick)); } #endif } // move to next slot in history every 100ms if (NetTickDiff(uCurTick, pRate->uLastRateTick) >= 100) { pRate->uDataIndex = (pRate->uDataIndex + 1) % uMaxIndex; pRate->aDataHist[pRate->uDataIndex] = 0; pRate->aTickHist[pRate->uDataIndex] = 0; pRate->aCallHist[pRate->uDataIndex] = 0; pRate->uLastRateTick = uCurTick; #if DIRTYNET_RATEDEBUG if (pRate->uCurRate != 0) { NetPrintf(("dirtynet: [%p] %s=%5d rate=%4.2fkb/s indx=%2d tick=%08x diff=%d\n", pRate, pOpName, uDataSum, ((float)pRate->uCurRate)/1024.0f, pRate->uDataIndex, uCurTick, iTickDiff)); } #endif } } /*F********************************************************************************/ /*! \Function SocketRateThrottle \Description Throttles data size to send or recv based on calculated data rate and configured max rate. \Input *pRate - state used to calculate rate \Input iSockType - socket type (SOCK_DGRAM, SOCK_STREAM, etc) \Input iData - amount of data being sent/recv \Input *pOpName - indicates send or recv (for debug use only) \Output int32_t - amount of data to send/recv \Version 08/20/2014 (jbrookes) */ /********************************************************************************F*/ int32_t SocketRateThrottle(SocketRateT *pRate, int32_t iSockType, int32_t iData, const char *pOpName) { int32_t iRateDiff; // max rate of zero means rate throttling is disabled if ((pRate->uMaxRate == 0) || (iSockType != SOCK_STREAM)) { return(iData); } // enforce min max update rate if (pRate->uMaxRate < DIRTYNET_MIN_THROTTLE_RATE) { NetPrintf(("dirtynet: [%p] clamping max rate throttle of %d to %d", pRate->uMaxRate, DIRTYNET_MIN_THROTTLE_RATE)); pRate->uMaxRate = DIRTYNET_MIN_THROTTLE_RATE; } // if we're exceeding the max rate, update rate estimation and return no data if ((iRateDiff = pRate->uMaxRate - pRate->uNextRate) <= 0) { NetPrintfVerbose((DIRTYNET_RATEDEBUG, 0, "dirtynet: [%p] exceeding max rate; clamping to zero\n", pRate)); iData = 0; } else if (iData > iRateDiff) // return a limited amount of data so as not to exceed max rate { /* clamp in multiples of the typical TCP maximum segment size, so as not to generate fragmented packets note that the TCP maximum segment size is equal to our minimum throttle rate, otherwise setting the uMaxRate to less than this value would result in iData always being equal to 0. */ iData = (iRateDiff / DIRTYNET_MIN_THROTTLE_RATE) * DIRTYNET_MIN_THROTTLE_RATE; NetPrintfVerbose((DIRTYNET_RATEDEBUG, 0, "dirtynet: [%p] exceeding max rate; clamping to %d bytes from %d bytes\n", pRate, iRateDiff, pRate->uMaxRate - pRate->uNextRate)); } // if we are returning no data, update the rate as the caller won't if (iData == 0) { SocketRateUpdate(pRate, 0, pOpName); } return(iData); } /* Send Callback functions */ /*F********************************************************************************/ /*! \Function SocketSendCallbackAdd \Description Register a new socket send callback \Input aCbEntries - collection of callbacks to add to \Input *pCbEntry - entry to be added \Output int32_t - zero=success; negative=failure \Version 07/18/2014 (mclouatre) */ /********************************************************************************F*/ int32_t SocketSendCallbackAdd(SocketSendCallbackEntryT aCbEntries[], SocketSendCallbackEntryT *pCbEntry) { int32_t iRetCode = -1; // default to failure int32_t iEntryIndex; for(iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++) { if (aCbEntries[iEntryIndex].pSendCallback == NULL) { aCbEntries[iEntryIndex].pSendCallback = pCbEntry->pSendCallback; aCbEntries[iEntryIndex].pSendCallref = pCbEntry->pSendCallref; NetPrintf(("dirtynet: adding send callback (%p, %p)\n", pCbEntry->pSendCallback, pCbEntry->pSendCallref)); iRetCode = 0; // success break; } } return(iRetCode); } /*F********************************************************************************/ /*! \Function SocketSendCallbackRem \Description Unregister a socket send callback that was already registered. \Input aCbEntries - collection of callbacks to remove from \Input *pCbEntry - entry to be removed \Output int32_t - zero=success; negative=failure \Version 07/18/2014 (mclouatre) */ /********************************************************************************F*/ int32_t SocketSendCallbackRem(SocketSendCallbackEntryT aCbEntries[], SocketSendCallbackEntryT *pCbEntry) { int32_t iRetCode = -1; // default to failure int32_t iEntryIndex; for(iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++) { if (aCbEntries[iEntryIndex].pSendCallback == pCbEntry->pSendCallback && aCbEntries[iEntryIndex].pSendCallref == pCbEntry->pSendCallref) { NetPrintf(("dirtynet: removing send callback (%p, %p)\n", aCbEntries[iEntryIndex].pSendCallback, aCbEntries[iEntryIndex].pSendCallref)); aCbEntries[iEntryIndex].pSendCallback = NULL; aCbEntries[iEntryIndex].pSendCallref = NULL; iRetCode = 0; // success break; } } return(iRetCode); } /*F********************************************************************************/ /*! \Function SocketSendCallbackInvoke \Description Invoke all send callbacks in specified collection. Only one of the callback is supposed to return "handled"... warn if not the case. \Input aCbEntries - collection of callbacks to be invoked \Input pSocket - socket reference \Input iType - socket type \Input *pBuf - the data to be sent \Input iLen - size of data \Input *pTo - the address to send to (NULL=use connection address) \Output int32_t - 0 = send not handled; >0 = send successfully handled (bytes sent); <0 = send handled but failed (SOCKERR_XXX) \Version 07/18/2014 (mclouatre) */ /********************************************************************************F*/ int32_t SocketSendCallbackInvoke(SocketSendCallbackEntryT aCbEntries[], SocketT *pSocket, int32_t iType, const char *pBuf, int32_t iLen, const struct sockaddr *pTo) { int32_t iRetCode = 0; // default to send not handled int32_t iResult; int32_t iEntryIndex; for (iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++) { // we expect zero or one send callback to handle the send, more than one indicates an invalid condition if (aCbEntries[iEntryIndex].pSendCallback != NULL) { if((iResult = aCbEntries[iEntryIndex].pSendCallback(pSocket, iType, (const uint8_t *)pBuf, iLen, pTo, aCbEntries[iEntryIndex].pSendCallref)) != 0) { if (iRetCode == 0) { iRetCode = iResult; } else { NetPrintf(("dirtynet: critical error - send handled by more than one callback (iEntryIndex = %d, iResult = %d)\n", iEntryIndex, iResult)); } } } } return(iRetCode); } /* Addr map functions */ #ifndef DIRTYCODE_NX /*F*************************************************************************************/ /*! \Function _Sockaddr6SetAddrV4Mapped \Description Set a V4-mapped IPv6 in6_addr \Input *pAddr6 - [out] storage for IPv4-mapped IPv6 address \Input uAddr4 - IPv4 address to map \Version 03/01/2016 (jbrookes) */ /************************************************************************************F*/ static void _Sockaddr6SetAddrV4Mapped(struct in6_addr *pAddr6, uint32_t uAddr4) { ds_memclr(pAddr6, sizeof(*pAddr6)); pAddr6->s6_addr[10] = 0xff; pAddr6->s6_addr[11] = 0xff; pAddr6->s6_addr[12] = (uint8_t)(uAddr4 >> 24); pAddr6->s6_addr[13] = (uint8_t)(uAddr4 >> 16); pAddr6->s6_addr[14] = (uint8_t)(uAddr4 >> 8); pAddr6->s6_addr[15] = (uint8_t)(uAddr4); } /*F*************************************************************************************/ /*! \Function _Sockaddr6SetV4Mapped \Description Set a V4-mapped IPv6 sockaddr6 \Input *pResult6 - [out] storage for IPv4-mapped IPv6 sockaddr \Input *pSource4 - IPv4 address to map \Version 03/01/2016 (jbrookes) */ /************************************************************************************F*/ static void _Sockaddr6SetV4Mapped(struct sockaddr_in6 *pResult6, const struct sockaddr *pSource4) { pResult6->sin6_family = AF_INET6; pResult6->sin6_port = SocketNtohs(SockaddrInGetPort(pSource4)); pResult6->sin6_flowinfo = 0; _Sockaddr6SetAddrV4Mapped(&pResult6->sin6_addr, SockaddrInGetAddr(pSource4)); pResult6->sin6_scope_id = 0; } /*F*************************************************************************************/ /*! \Function _SocketAddrMapAlloc \Description Alloc (or re-alloc) address map entry list \Input *pAddrMap - address map \Input iNumEntries - new entry list size \Input iMemGroup - memory group \Input *pMemGroupUserData - memory group user data \Output int32_t - negative=error, zero=success \Version 04/17/2013 (jbrookes) */ /************************************************************************************F*/ static int32_t _SocketAddrMapAlloc(SocketAddrMapT *pAddrMap, int32_t iNumEntries, int32_t iMemGroup, void *pMemGroupUserData) { int32_t iOldEntryListSize = 0, iNewEntryListSize = iNumEntries*sizeof(SocketAddrMapEntryT); SocketAddrMapEntryT *pNewEntries; // allocate new map memory if ((pNewEntries = (SocketAddrMapEntryT *)DirtyMemAlloc(iNewEntryListSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL) { return(-1); } // clear new map memory ds_memclr(pNewEntries, iNewEntryListSize); // copy previous map data if (pAddrMap->pMapEntries != NULL) { iOldEntryListSize = pAddrMap->iNumEntries*sizeof(SocketAddrMapEntryT); ds_memcpy(pNewEntries, pAddrMap->pMapEntries, iOldEntryListSize); DirtyMemFree(pAddrMap->pMapEntries, SOCKET_MEMID, iMemGroup, pMemGroupUserData); } // update state pAddrMap->iNumEntries = iNumEntries; pAddrMap->pMapEntries = pNewEntries; pAddrMap->iMemGroup = iMemGroup; pAddrMap->pMemGroupUserData = pMemGroupUserData; // return success return(0); } /*F*************************************************************************************/ /*! \Function _SocketAddrMapGet \Description Find a map entry, based on specified address \Input *pAddrMap - address map \Input *pAddr - address to get entry for \Input iAddrSize - size of address \Output SockAddrMapEntryT * - pointer to map entry or NULL if not found \Version 04/17/2013 (jbrookes) */ /************************************************************************************F*/ static SocketAddrMapEntryT *_SocketAddrMapGet(const SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize) { const struct sockaddr_in6 *pAddr6 = (const struct sockaddr_in6 *)pAddr; SocketAddrMapEntryT *pMapEntry; int32_t iMapEntry; for (iMapEntry = 0; iMapEntry < pAddrMap->iNumEntries; iMapEntry += 1) { pMapEntry = &pAddrMap->pMapEntries[iMapEntry]; if ((pAddr->sa_family == AF_INET) && (pMapEntry->iVirtualAddress == SockaddrInGetAddr(pAddr))) { return(pMapEntry); } if ((pAddr->sa_family == AF_INET6) && (!memcmp(&pAddr6->sin6_addr, &pMapEntry->SockAddr6.sin6_addr, sizeof(pAddr6->sin6_addr)))) { return(pMapEntry); } } return(NULL); } /*F*************************************************************************************/ /*! \Function _SocketAddrMapSet \Description Initialize a map entry \Input *pAddrMap - address map \Input *pMapEntry - entry to set \Input *pAddr6 - IPv6 address to set \Input iAddrSize - size of address \Version 04/17/2013 (jbrookes) */ /************************************************************************************F*/ static void _SocketAddrMapSet(SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize) { pMapEntry->iRefCount = 1; pMapEntry->iVirtualAddress = pAddrMap->iNextVirtAddr; pAddrMap->iNextVirtAddr = (pAddrMap->iNextVirtAddr + 1) & 0x00ffffff; ds_memcpy(&pMapEntry->SockAddr6, pAddr6, iAddrSize); NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] add map %A to %a\n", pMapEntry - pAddrMap->pMapEntries, pAddr6, pMapEntry->iVirtualAddress)); } /*F*************************************************************************************/ /*! \Function _SocketAddrMapRemap \Description Alter the value of an initialized map entry \Input *pAddrMap - address map \Input *pMapEntry - entry to set \Input *pAddr6 - IPv6 address to set \Input iAddrSize - size of address \Version 12/03/2015 (amakoukji) */ /************************************************************************************F*/ static void _SocketAddrMapRemap(const SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize) { if (memcmp(&pMapEntry->SockAddr6, pAddr6, iAddrSize) == 0) { NetPrintf(("dirtynet: [%d] attempt to remap %a to identical IPv6 address %A\n", pMapEntry - pAddrMap->pMapEntries, pMapEntry->iVirtualAddress, pAddr6)); } NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] remapped %A -> %A for virtual address %a\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pAddr6, pMapEntry->iVirtualAddress)); ds_memcpy(&pMapEntry->SockAddr6, pAddr6, iAddrSize); } /*F*************************************************************************************/ /*! \Function _SocketAddrMapDel \Description Dereference (and clear, if no more references) a map entry \Input *pAddrMap - address map \Input *pMapEntry - entry to del \Version 04/17/2013 (jbrookes) */ /************************************************************************************F*/ static void _SocketAddrMapDel(SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry) { NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] del map %A to %a (decremented refcount to %d)\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pMapEntry->iVirtualAddress, pMapEntry->iRefCount-1)); if (--pMapEntry->iRefCount == 0) { ds_memclr(pMapEntry, sizeof(*pMapEntry)); } } /*F*************************************************************************************/ /*! \Function _SocketAddrMapAdd \Description Add an IPv6 address to the address mapping table \Input *pAddrMap - address map \Input *pAddr6 - address to add to the mapping table \Input iAddrSize - size of address \Output int32_t - SOCKMAP_ERROR=error, else virtual IPv4 address for newly mapped IPv6 address \Version 04/17/2013 (jbrookes) */ /************************************************************************************F*/ static int32_t _SocketAddrMapAdd(SocketAddrMapT *pAddrMap, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize) { int32_t iMapEntry; // find an empty slot for (iMapEntry = 0; iMapEntry < pAddrMap->iNumEntries; iMapEntry += 1) { if (pAddrMap->pMapEntries[iMapEntry].iVirtualAddress == 0) { _SocketAddrMapSet(pAddrMap, &pAddrMap->pMapEntries[iMapEntry], pAddr6, iAddrSize); return(pAddrMap->pMapEntries[iMapEntry].iVirtualAddress); } } // if no empty slot, realloc the array if ((_SocketAddrMapAlloc(pAddrMap, pAddrMap->iNumEntries+8, pAddrMap->iMemGroup, pAddrMap->pMemGroupUserData)) < 0) { return(SOCKMAP_ERROR); } // try the add again return(_SocketAddrMapAdd(pAddrMap, pAddr6, iAddrSize)); } /*F*************************************************************************************/ /*! \Function SocketAddrMapInit \Description Initialize a Socket Address Map \Input *pAddrMap - address map \Input iMemGroup - memory group \Input *pMemGroupUserData - memory group user data \Version 03/03/2016 (jbrookes) */ /************************************************************************************F*/ void SocketAddrMapInit(SocketAddrMapT *pAddrMap, int32_t iMemGroup, void *pMemGroupUserData) { // set up address map ds_memclr(pAddrMap, sizeof(*pAddrMap)); pAddrMap->iMemGroup = iMemGroup; pAddrMap->pMemGroupUserData = pMemGroupUserData; // init ipv6 map virtual address pAddrMap->iNextVirtAddr = NetTick() & 0x00ffffff; } /*F*************************************************************************************/ /*! \Function SocketAddrMapShutdown \Description Clean up a Socket Address Map \Input *pAddrMap - address map \Version 03/03/2016 (jbrookes) */ /************************************************************************************F*/ void SocketAddrMapShutdown(SocketAddrMapT *pAddrMap) { if (pAddrMap->pMapEntries != NULL) { DirtyMemFree(pAddrMap->pMapEntries, SOCKET_MEMID, pAddrMap->iMemGroup, pAddrMap->pMemGroupUserData); } } /*F*************************************************************************************/ /*! \Function SocketAddrMapAddress \Description Map an IPv6 address and return a virtual IPv4 address that can be used to reference it. \Input *pAddrMap - address map \Input *pAddr - address to add to the mapping table \Input iAddrSize - size of address \Output int32_t - SOCKMAP_ERROR=error, else virtual IPv4 address for newly mapped IPv6 address \Version 04/17/2013 (jbrookes) */ /************************************************************************************F*/ int32_t SocketAddrMapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize) { const struct sockaddr_in6 *pAddr6 = (const struct sockaddr_in6 *)pAddr; SocketAddrMapEntryT *pMapEntry; // if IPv4, just return the IPv4 address directly, without mapping if (pAddr->sa_family == AF_INET) { return(SockaddrInGetAddr(pAddr)); } // we only map ipv6 addresses if ((pAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6))) { NetPrintf(("dirtynet: can't map address of type %d size=%d\n", pAddr->sa_family, iAddrSize)); return(SOCKMAP_ERROR); } // force address size to in6 (is this necessary??) iAddrSize = sizeof(struct sockaddr_in6); // if it's an IPv4-mapped IPv6 address, return the IPv4 address if (_SockaddrIn6IsIPv4(pAddr6) || _SockaddrIn6IsZero(pAddr6)) { return(SockaddrIn6GetAddr4(pAddr6)); } // see if this address is already mapped if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pAddr, iAddrSize)) != NULL) { pMapEntry->iRefCount += 1; NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] map %A to %a (incremented refcount to %d)\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pMapEntry->iVirtualAddress, pMapEntry->iRefCount)); return(pMapEntry->iVirtualAddress); } // add it to the map return(_SocketAddrMapAdd(pAddrMap, pAddr6, iAddrSize)); } /*F*************************************************************************************/ /*! \Function SocketAddrRemapAddress \Description Remap an existing IPv6 address and return a virtual IPv4 address that can be used to reference it. \Input *pAddrMap - address map \Input *pOldAddr - address that should be in mapping table \Input *pNewAddr - address to update to the mapping table \Input iAddrSize - size of address \Output int32_t - SOCKMAP_ERROR=error, else virtual IPv4 address for remapped IPv6 address \Version 12/03/2015 (amakoukji) */ /************************************************************************************F*/ int32_t SocketAddrRemapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pOldAddr, const struct sockaddr *pNewAddr, int32_t iAddrSize) { SocketAddrMapEntryT *pMapEntry; // if IPv4, just return the IPv4 address directly, without mapping if (pNewAddr->sa_family == AF_INET) { return(SockaddrInGetAddr(pNewAddr)); } // we only map ipv6 addresses if ((pNewAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6))) { NetPrintf(("dirtynet: can't remap address of type %d size=%d\n", pNewAddr->sa_family, iAddrSize)); return(SOCKMAP_ERROR); } iAddrSize = sizeof(struct sockaddr_in6); // see if this address is already mapped if ((pMapEntry = _SocketAddrMapGet(pAddrMap, (const struct sockaddr *)pOldAddr, iAddrSize)) != NULL) { NetPrintf(("dirtynet: attempting to remap virtual address %a, from %A to %A\n", pMapEntry->iVirtualAddress, pOldAddr, pNewAddr)); _SocketAddrMapRemap(pAddrMap, pMapEntry, (const struct sockaddr_in6 *)pNewAddr, iAddrSize); return(pMapEntry->iVirtualAddress); } // did not find the address NetPrintf(("dirtynet: attempt to remap unmapped address %A, attempting to add mapping instead\n", pNewAddr)); return(SocketAddrMapAddress(pAddrMap, (const struct sockaddr *)pNewAddr, iAddrSize)); } /*F*************************************************************************************/ /*! \Function SocketAddrUnmapAddress \Description Removes an address mapping from the mapping table. \Input *pAddrMap - address map \Input *pAddr - address to remove from the mapping table \Input iAddrSize - size of address \Output int32_t - negative=error, else zero \Version 04/18/2013 (jbrookes) */ /************************************************************************************F*/ int32_t SocketAddrUnmapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize) { SocketAddrMapEntryT *pMapEntry; // we only map ipv6 addresses if ((pAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6))) { NetPrintf(("dirtynet: can't unmap address of type %d size=%d\n", pAddr->sa_family, iAddrSize)); return(-1); } iAddrSize = sizeof(struct sockaddr_in6); // get the map entry for this address if ((pMapEntry = _SocketAddrMapGet(pAddrMap, (const struct sockaddr *)pAddr, iAddrSize)) == NULL) { NetPrintf(("dirtynet: address unmap operation on an address not in the table\n")); return(-2); } // unmap it _SocketAddrMapDel(pAddrMap, pMapEntry); return(0); } /*F*************************************************************************************/ /*! \Function SocketAddrMapGet \Description Check if an address is in the address map, and if so return its mapped equivalent \Input *pAddrMap - address map \Input *pResult - [out] storage for result \Input *pSource - source address \Input *pNameLen - [out] storage for result length \Output struct sockaddr * - pointer to result address, or NULL if not found \Version 04/18/2013 (jbrookes) */ /************************************************************************************F*/ struct sockaddr *SocketAddrMapGet(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen) { SocketAddrMapEntryT *pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen); struct sockaddr *pReturn = NULL; if (pMapEntry != NULL) { if (pResult->sa_family == AF_INET) { SockaddrInit(pResult, AF_INET); SockaddrInSetAddr(pResult, pMapEntry->iVirtualAddress); pReturn = pResult; } else if (pResult->sa_family == AF_INET6) { ds_memcpy_s(pResult, sizeof(*pResult), &pMapEntry->SockAddr6, sizeof(pMapEntry->SockAddr6)); pReturn = pResult; } } return(pReturn); } /*F*************************************************************************************/ /*! \Function SocketAddrMapTranslate \Description Translate the following: - IPv4 real address to IPV4-mapped IPv6 address - IPv4 virtual address to IPv6 address - IPv6 address to IPv4 address \Input *pAddrMap - address map \Input *pResult - [out] storage for translated result \Input *pSource - source address \Input *pNameLen - [out] storage for result length \Output struct sockaddr * - pointer to resulting IPv6 address \Version 04/15/2013 (jbrookes) */ /************************************************************************************F*/ struct sockaddr *SocketAddrMapTranslate(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen) { SocketAddrMapEntryT *pMapEntry; struct sockaddr *pReturn = NULL; // handle broken use cases where the family is not specified if ((pSource->sa_family != AF_INET) && (pSource->sa_family != AF_INET6)) { NetPrintf(("dirtynet: unsupported source family %d in SocketAddrMapTranslate(); assuming AF_INET\n", pSource->sa_family)); ((struct sockaddr *)pSource)->sa_family = AF_INET; } if ((pResult->sa_family != AF_INET) && (pResult->sa_family != AF_INET6)) { NetPrintf(("dirtynet: unsupported result family %d in SocketAddrMapTranslate(); assuming AF_INET\n", pResult->sa_family)); pResult->sa_family = AF_INET; } // handle IPv4->IPv6 if ((pSource->sa_family == AF_INET) && (pResult->sa_family == AF_INET6)) { struct sockaddr_in6 *pResult6 = (struct sockaddr_in6 *)pResult; uint32_t uAddr = SockaddrInGetAddr(pSource); // handle a regular IPv4 address if ((uAddr == 0) || ((uAddr >> 24) != 0)) { ds_memclr(pResult, sizeof(*pResult)); _Sockaddr6SetV4Mapped(pResult6, pSource); *pNameLen = sizeof(*pResult6); pReturn = pResult; } // get IPv6 address from virtual IPv4 else if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen)) != NULL) { ds_memcpy_s(pResult6, sizeof(*pResult6), &pMapEntry->SockAddr6, sizeof(pMapEntry->SockAddr6)); pResult6->sin6_port = SocketNtohs(SockaddrInGetPort(pSource)); *pNameLen = sizeof(*pResult6); pReturn = pResult; } else { NetPrintf(("dirtynet: could not find address for virtual address %a\n", SockaddrInGetAddr(pSource))); } } // handle IPv6->IPv4 else if ((pSource->sa_family == AF_INET6) && (pResult->sa_family == AF_INET)) { struct sockaddr_in6 *pSource6 = (struct sockaddr_in6 *)pSource; // translate IPv6 to virtual IPv4 address if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen)) != NULL) { struct sockaddr *pResult4 = (struct sockaddr *)pResult; SockaddrInit(pResult4, AF_INET); SockaddrInSetAddr(pResult4, pMapEntry->iVirtualAddress); SockaddrInSetPort(pResult4, SocketHtons(pSource6->sin6_port)); *pNameLen = sizeof(*pResult4); pReturn = pResult; } // is this an IPv4-mapped IPv6 address? else if (_SockaddrIn6IsIPv4(pSource6)) { struct sockaddr *pResult4 = (struct sockaddr *)pResult; uint32_t uAddress = SockaddrIn6GetAddr4(pSource6); SockaddrInit(pResult4, AF_INET); SockaddrInSetAddr(pResult4, uAddress); SockaddrInSetPort(pResult4, SocketHtons(pSource6->sin6_port)); *pNameLen = sizeof(*pResult4); pReturn = pResult; } } else { *pNameLen = (pResult->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); ds_memcpy_s(pResult, *pNameLen, pSource, *pNameLen); pReturn = pResult; } if (pReturn != NULL) { NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 2, "dirtynet: map translate %A->%A\n", pSource, pReturn)); } else { NetPrintf(("dirtynet: map translate %A failed\n", pSource)); pReturn = (struct sockaddr *)pSource; *pNameLen = sizeof(*pSource); } return(pReturn); } #endif