#include #include #include "ipv6text.h" #include "xash3d_types.h" #ifdef _WIN32 #ifndef snprintf #define snprintf _snprintf #endif #endif void IPv6IPToString( char *pszOutText, const unsigned char *ip ) { // Find the longest run of consecutive zero quads. // If there's a tie, we want the leftmost one. int idxLongestRunStart = -1; int nLongestRun = 1; // It must be at least 2 quads in a row, a single 0 must not be compressed int nCurrentRun = 0, idxQuad; char *p; qboolean bNeedColon; for ( idxQuad = 0 ; idxQuad < 8 ; ++idxQuad ) { // Zero if ( ip[idxQuad*2] || ip[idxQuad*2 + 1] ) { // Terminate run nCurrentRun = 0; } else { // Extend (or begin) run ++nCurrentRun; // Longer than previously found run? if ( nCurrentRun > nLongestRun ) { nLongestRun = nCurrentRun; idxLongestRunStart = idxQuad - nCurrentRun + 1; } } } // Print the quads p = pszOutText; idxQuad = 0; bNeedColon = false; while ( idxQuad < 8 ) { // Run of compressed zeros? if ( idxQuad == idxLongestRunStart ) { *(p++) = ':'; *(p++) = ':'; bNeedColon = false; idxQuad += nLongestRun; } else { // Lowercase hex digits, with leading zeros omitted static const char hexdigits[] = "0123456789abcdef"; unsigned quad; // Colon to separate from previous, unless // we are first or immediately follow compressed zero "::" if ( bNeedColon ) *(p++) = ':'; // Next quad should should print a separator bNeedColon = true; // Assemble 16-bit quad value from the two bytes quad = ( (unsigned)ip[idxQuad*2] << 8U ) | ip[idxQuad*2 + 1]; // Manually do the hex number formatting. if ( quad >= 0x0010 ) { if ( quad >= 0x0100 ) { if ( quad >= 0x1000 ) *(p++) = hexdigits[ quad >> 12U ]; *(p++) = hexdigits[ ( quad >> 8U ) & 0xf ]; } *(p++) = hexdigits[ ( quad >> 4U ) & 0xf ]; } // Least significant digit, which is always printed *(p++) = hexdigits[ quad & 0xf ]; // On to the next one ++idxQuad; } } // String terminator *p = '\0'; } void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope ) { char *p = pszOutText; // Open bracket *(p++) = '['; // Print in the IP IPv6IPToString( p, ip ); // Find the end of the string while (*p) ++p; if ( scope ) { // And now the scope. Max 32-digit scope number is 10 digits snprintf( p, 12, "%%%d", scope ); // Find the end of the string while (*p) ++p; } // And now the rest. Max 16-digit port number is 6 digits snprintf( p, 8, "]:%u", (unsigned int)port ); } static inline int ParseIPv6Addr_HexDigitVal( char c ) { if ( c >= '0' && c <= '9' ) return c - '0'; if ( c >= 'a' && c <= 'f' ) return c - ('a' - 0xa); if ( c >= 'A' && c <= 'F' ) return c - ('A' - 0xa); return -1; } static inline int ParseIPv6Addr_DecimalDigitVal( char c ) { if ( c >= '0' && c <= '9' ) return c - '0'; return -1; } static bool ParseIPv6Addr_IsSpace( char c ) { // Newlines don't count, intentionally return c == ' ' || c == '\t'; } bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope ) { unsigned char *d, *pZeroFill, *pEndIP; const char *s; qboolean bQuadMustFollow; int nPort; while ( ParseIPv6Addr_IsSpace( *pszText ) ) ++pszText; s = pszText; // Skip opening bracket, if present if ( *s == '[' ) { ++s; while ( ParseIPv6Addr_IsSpace( *s ) ) ++s; } // Special case for leading "::" bQuadMustFollow = true; d = pOutIP; pZeroFill = NULL; pEndIP = pOutIP + 16; if ( s[0] == ':' && s[1] == ':' ) { pZeroFill = d; s += 2; bQuadMustFollow = false; } // Parse quads until we get to the end for (;;) { // Next thing must be a quad, or end of input. Is it a quad? int quadDigit = ParseIPv6Addr_HexDigitVal( *s ); const char *pszStartQuad; int quad; if ( quadDigit < 0 ) { if ( bQuadMustFollow ) return false; break; } // No room for more quads? if ( d >= pEndIP ) return false; pszStartQuad = s; ++s; quad = quadDigit; // Now parse up to three additional characters quadDigit = ParseIPv6Addr_HexDigitVal( *s ); if ( quadDigit >= 0 ) { quad = ( quad << 4 ) | quadDigit; ++s; quadDigit = ParseIPv6Addr_HexDigitVal( *s ); if ( quadDigit >= 0 ) { quad = ( quad << 4 ) | quadDigit; ++s; quadDigit = ParseIPv6Addr_HexDigitVal( *s ); if ( quadDigit >= 0 ) { quad = ( quad << 4 ) | quadDigit; ++s; } } } // Check if we hit a period, which would happen if we // have an IPv4 dotted decimal. For example, "::ffff:192.168.1.210" if ( *s == '.' ) { // Make sure we would have room to store four more bytes. unsigned char *pEndDottedDecimal = d+4; if ( pEndDottedDecimal > pEndIP ) return false; // Parse 4 octets s = pszStartQuad; for (;;) { // Parse 1-3 decimal digits int octet = ParseIPv6Addr_DecimalDigitVal( *s ); int dig; if ( octet < 0 ) return false; ++s; dig = ParseIPv6Addr_DecimalDigitVal( *s ); if ( dig >= 0 ) { ++s; octet = octet*10 + dig; dig = ParseIPv6Addr_DecimalDigitVal( *s ); if ( dig >= 0 ) { ++s; octet = octet*10 + dig; // Make sure value is in range. if ( octet > 255 ) return false; } } *(d++) = (unsigned char)octet; // All done? if ( d >= pEndDottedDecimal ) break; // Next thing must be dot dot separator if ( *s != '.' ) return false; // Eat dot ++s; } break; } // Stash it in the next slot, ignoring for now the issue // of compressed zeros *(d++) = (unsigned char)( quad >> 8 ); *(d++) = (unsigned char)quad; // Only valid character for the IP portion is a colon. // Anything else ends the IP portion if ( *s != ':' ) break; // Compressed zeros? if ( s[1] == ':' ) { // Eat '::' s += 2; // Can only have one range of compressed zeros if ( pZeroFill ) return false; // Remember where to insert the compressed zeros pZeroFill = d; // An IP can end with '::' bQuadMustFollow = false; } else { // If they have filed the entire IP with no compressed zeros, // then this is unambiguously a port number. That's not // necessarily the best style, but it *is* unambiguous // what it should mean, so let's allow it. If there // are compressed zeros, then this is ambiguous, and we will // always interpret it as a quad. if ( !pZeroFill && d >= pEndIP ) break; // leave ':' as next character, for below // Eat ':' ++s; // A single colon must be followed by another quad bQuadMustFollow = true; } } // End of the IP. Do we have compressed zeros? if ( pZeroFill ) { // How many zeros do we need to fill? intptr_t nZeros = pEndIP - d; if ( nZeros <= 0 ) return false; // Shift the quads after the bytes to the end memmove( pZeroFill+nZeros, pZeroFill, d-pZeroFill ); // And now fill the zeros memset( pZeroFill, 0, nZeros ); } else { // No compressed zeros. Just make sure we filled the IP exactly if ( d != pEndIP ) return false; } if ( *s == '%' ) { // Parse scope number uint32_t unScope = 0; int nScopeDigit; ++s; nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s ); if ( nScopeDigit < 0 ) return false; unScope = (uint32_t)nScopeDigit; for (;;) { ++s; if ( *s == '\0' || *s == ']' || ParseIPv6Addr_IsSpace( *s ) ) break; nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s ); if ( nScopeDigit < 0 ) return false; unScope = unScope * 10 + nScopeDigit; } if ( pOutScope ) *pOutScope = unScope; } else { if ( pOutScope ) *pOutScope = 0; } // If we started with a bracket, then the next character MUST be a bracket. // (And this is the only circumstance in which a closing bracket would be legal) if ( *pszText == '[' ) { while ( ParseIPv6Addr_IsSpace( *s ) ) ++s; if ( *s != ']' ) return false; ++s; } // Now we are definitely at the end of the IP. Do we have a port? // We support all of the syntaxes mentioned in RFC5952 section 6 other // than the ambiguous case if ( *s == ':' || *s == '#' || *s == '.' || *s == 'p' || *s == 'P' ) { ++s; } else { while ( ParseIPv6Addr_IsSpace( *s ) ) ++s; if ( *s == '\0' ) { // Parsed IP without port OK if ( pOutPort ) *pOutPort = -1; return true; } if ( strncmp( s, "port", 4 ) == 0 ) { s += 4; while ( ParseIPv6Addr_IsSpace( *s ) ) ++s; } else { // Extra stuff after the IP which isn't whitespace or a port return false; } } // We have a port. If they didn't ask for it, that's considered a parse failure. if ( !pOutPort ) return false; // Parse port number nPort = ParseIPv6Addr_DecimalDigitVal( *s ); if ( nPort < 0 ) return false; for (;;) { int portDigit; ++s; if ( *s == '\0' || ParseIPv6Addr_IsSpace( *s ) ) break; portDigit = ParseIPv6Addr_DecimalDigitVal( *s ); if ( portDigit < 0 ) return false; nPort = nPort * 10 + portDigit; if ( nPort > 0xffff ) return false; } // Consume trailing whitespace; confirm nothing else in the input while ( ParseIPv6Addr_IsSpace( *s ) ) ++s; if ( *s != '\0' ) return false; *pOutPort = nPort; return true; }