// -*- mode: C++; tab-width: 4; -*- // vi:ts=8 sw=4 noexpandtab autoindent /* * NetUtils * * Copyright 2011 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ // define this before including netportipv6.h to get the Winsock typedefs # define INCL_WINSOCK_API_TYPEDEFS 1 # include "netportipv6.h" // must be included before stdhdrs.h # include <stdhdrs.h> # include <error.h> # include <ctype.h> # include "strbuf.h" # include "netport.h" # include "netipaddr.h" # include "netutils.h" # include "netsupport.h" # include "debug.h" # include "netdebug.h" typedef unsigned int p4_uint32_t; // don't conflict with any other definitions of uint32_t // this *should* be defined everywhere that supports IPv6, but just in case ... # ifndef IN6_IS_ADDR_UNSPECIFIED # define IN6_IS_ADDR_UNSPECIFIED(a) \ (((const p4_uint32_t *) (a))[0] == 0 \ && ((const p4_uint32_t *) (a))[1] == 0 \ && ((const p4_uint32_t *) (a))[2] == 0 \ && ((const p4_uint32_t *) (a))[3] == 0) # endif // see net/netportipv6.h for a description of the _MSC_VER values /* * sigh. VS 2010 defines inet_ntop() and inet_pton(), but its implementation * calls code in ws2_32.dll; that code doesn't exist until Vista or Server 2008. * We need to run on XP SP2 and Server 2003 SP1 so we'll just always use our own * version. */ # if defined(OS_MINGW) || (defined(OS_NT) && defined(_MSC_VER)) # if defined(_MSC_VER) # define DLL_IMPORT __declspec( dllimport ) # define DLL_EXPORT __declspec( dllexport ) // VS 2005 or VS 2008 (VS 2010 is >= 1600) # if (_MSC_VER < 1400) // before VS 2005 # error This version (_MSC_VER) of MS Visual Studio does not support IPv6. # endif # define INET_NTOP_RET_TYPE PCSTR # define INET_NTOP_SRC_TYPE PVOID # define INET_PTON_SRC_TYPE PCSTR # else // OS_MINGW # define DLL_IMPORT # define DLL_EXPORT # define INET_NTOP_RET_TYPE const char * # define INET_NTOP_SRC_TYPE const void * # define INET_PTON_SRC_TYPE const char * # endif # define INET_PTON_RET_TYPE int // use the real Windows (ASCII) definitions if possible # ifdef LPFN_INET_NTOP typedef LPFN_INET_NTOPA pfunc_ntop_t; # else typedef INET_NTOP_RET_TYPE (WSAAPI * pfunc_ntop_t)(INT, INET_NTOP_SRC_TYPE, PSTR, size_t); # endif # ifdef LPFN_INET_PTON typedef LPFN_INET_PTONA pfunc_pton_t; # else typedef INET_PTON_RET_TYPE (WSAAPI * pfunc_pton_t)(INT, INET_PTON_SRC_TYPE, PVOID); # endif // mingw32 or Visual Studio // job063342: the name of the winsock dll static const TCHAR *WINSOCK_DLL = TEXT("ws2_32.dll"); /* * MINGW doesn't currently (v4.5) provide inet_ntop() or inet_pton() * This is Windows-only code, so I'm setting the socket error via * WSASetLastError(); I *think* that I'm using the correct values. */ // our private implementation of inet_ntop static INET_NTOP_RET_TYPE p4_inet_ntop_impl( int af, INET_NTOP_SRC_TYPE src, char *hostbuf, // should be at least NI_MAXHOST bytes TYPE_SOCKLEN size) { *hostbuf = '\0'; // initialize to empty string if( af == AF_INET ) { struct sockaddr_in in; ::memset( &in, 0, sizeof(in) ); in.sin_family = AF_INET; ::memcpy( &in.sin_addr, src, sizeof(struct in_addr) ); int stat = ::getnameinfo( reinterpret_cast<struct sockaddr *>(&in), sizeof(struct sockaddr_in), hostbuf, size, NULL, 0, NI_NUMERICHOST ); if( stat ) ::WSASetLastError( stat ); return stat == 0 ? hostbuf : NULL; } else if( af == AF_INET6 ) { struct sockaddr_in6 in; ::memset( &in, 0, sizeof(in) ); in.sin6_family = AF_INET6; ::memcpy( &in.sin6_addr, src, sizeof(struct in_addr6) ); int stat = ::getnameinfo( reinterpret_cast<struct sockaddr *>(&in), sizeof(struct sockaddr_in6), hostbuf, size, NULL, 0, NI_NUMERICHOST ); if( stat ) ::WSASetLastError( stat ); return stat == 0 ? hostbuf : NULL; } // unsupported address family requested ::WSASetLastError( WSAEAFNOSUPPORT ); return NULL; } /* * Wrapper function to call the system inet_ntop() function if it exists, * or our private implementation if it doesn't. * Windows only. */ INET_NTOP_RET_TYPE p4_inet_ntop( int af, INET_NTOP_SRC_TYPE src, char *hostbuf, // should be at least NI_MAXHOST bytes TYPE_SOCKLEN size) { // assume that the winsock dll doesn't change while we're running static pfunc_ntop_t p_inet_ntop = reinterpret_cast<pfunc_ntop_t>( ::GetProcAddress(::GetModuleHandle(WINSOCK_DLL), "inet_ntop") ); if( p_inet_ntop ) return p_inet_ntop( af, src, hostbuf, size ); else return p4_inet_ntop_impl( af, src, hostbuf, size ); } // our private implementation of inet_pton static INET_PTON_RET_TYPE p4_inet_pton_impl( int af, INET_PTON_SRC_TYPE src, void *dst) // must be at least INET6_ADDRSTRLEN bytes { if( (af != AF_INET) && (af != AF_INET6) ) { // errno = EAFNOSUPPORT; return -1; } struct addrinfo hints; struct addrinfo *res; ::memset( &hints, 0, sizeof(struct addrinfo) ); hints.ai_family = af; if( ::getaddrinfo(src, NULL, &hints, &res) != 0 ) { return 0; } if( res == NULL ) { // shouldn't happen return 0; } // return the first address // job063342 : just the address part, not the family, port, etc const void *addrp = NetUtils::GetInAddr(res->ai_addr); const size_t addrlen = NetUtils::GetAddrSize(res->ai_addr); ::memcpy( dst, addrp, addrlen ); ::freeaddrinfo( res ); return 1; } /* * Wrapper function to call the system inet_pton() function if it exists, * or our private implementation if it doesn't. * Windows only. */ INET_PTON_RET_TYPE p4_inet_pton( int af, INET_PTON_SRC_TYPE src, void *dst) // must be at least INET6_ADDRSTRLEN bytes { // assume that the winsock dll doesn't change while we're running static pfunc_pton_t p_inet_pton = reinterpret_cast<pfunc_pton_t>( ::GetProcAddress(::GetModuleHandle(WINSOCK_DLL), "inet_pton") ); if( p_inet_pton ) return p_inet_pton( af, src, dst ); else return p4_inet_pton_impl( af, src, dst ); } # endif // OS_MINGW || (OS_NT && Visual Studio) # if defined(OS_MINGW) || defined(OS_NT) /* * IPv4 only! * return zero on failure, non-zero on success * * Accepts 1 to 4 numeric fields, separated by periods. * Each field can be decimal, octal (if preceded by "0"), * or hex (if preceded by "0x" or "0X"); * If just one field, it's the host number * (and so is right-justified). * We don't allow leading or trailing whitespace. * We do allow addr to be NULL, in which case we simply * return non-zero if cp points to a valid IPv4 address (or fragment). * * Fills *addr (if non-NULL) in network byte order. * * If some future version of Visual Studio defines this routine * then this will cause a conflict. At that time we'll ifdef this * for the appropriate value of _MSC_VER. */ int inet_aton( const char *cp, in_addr *addr) { int base = 10; bool valid = false; // have we seen a valid digit string yet? p4_uint32_t val = 0; int chunk_index = 0; p4_uint32_t chunks[4]; unsigned char ch; // max (host) values for each chunk static p4_uint32_t limits[4] = { 0xFFFFFFFF, // 32 bits (/0) 0x00FFFFFF, // 24 bits (/8) 0x0000FFFF, // 16 bits (/16) 0x000000FF // 8 bits (/24) }; while( ch = *cp ) { val = 0; // re-initialize for each chunk base = 10; // each field starts as decimal // each chunk may switch the base if( ch == '0') { if( ((ch = *++cp) == 'x') || (ch == 'X') ) { base = 16; cp++; } else { base = 8; valid = true; } // I guess they mean just a value of 0 if( !*cp ) break; } while( ch = *cp ) { // compute the value of this chunk if( isdigit(ch) ) { if( (base == 8) && (ch == '8' || ch == '9') ) return 0; // illegal digit in an octal number valid = true; val = (val * base) + (ch - '0'); cp++; } else if( (base == 16) && isxdigit(ch) ) { valid = true; val = (val << 4) + (ch + 10 - (islower(ch) ? 'a' : 'A')); cp++; } else { break; } } if( ch == '.' ) { // chunk_index counts the number of dots (max 3) if( (chunk_index > 2) || (val > limits[chunk_index]) ) return 0; chunks[chunk_index++] = val; valid = false; cp++; } else if( !ch ) { break; } else { // anything else is invalid return 0; } } // Must have seen at least one valid digit in this chunk to continue; // a trailing dot will fail here. if( !valid ) return 0; /* * Now chunks has the value of each chunk (delimited by a following '.') * and val has the last value (not followed by a '.'), * and chunk_index is the index of where the next chunk (if any) should * be written; it counts the number of dots seen. */ // return failure if any chunk exceed its limit for( int i = 0; i < chunk_index; i++ ) { if( chunks[i] > limits[i] ) return 0; } // and check the last part (which isn't in a chunk) if( val > limits[chunk_index] ) return 0; switch( chunk_index ) { case 0: // 192 (0.32), so a host number break; case 1: // 192.168 (8.24) val |= (chunks[0] << 24); break; case 2: // 192.168.1 (8.8.16) val |= (chunks[0] << 24) | (chunks[1] << 16); break; case 3: // 192.168.1.2 (8.8.8.8) val |= (chunks[0] << 24) | (chunks[1] << 16) | (chunks[2] << 8); break; } if( addr ) addr->s_addr = htonl(val); return 1; } # endif // OS_MINGW || OS_NT /* * convenience wrapper for setsockopt */ int NetUtils::setsockopt( const char *module, int sockfd, int level, int optname, const SOCKOPT_T *optval, socklen_t optlen, const char *name ) { int retval = ::setsockopt( sockfd, level, optname, (char *)optval, optlen ); if( retval < 0 ) { if( DEBUG_CONNECT ) { StrBuf errnum; Error::StrNetError( errnum ); p4debug.printf( "%s setsockopt(%s, %d) failed, error = %s\n", module, name, *reinterpret_cast<const int *>(optval), errnum.Text() ); } } return retval; } /* * Get IPv4 or IPv6 sin[6]_addr ptr convenience function. * Returns the sockaddr's sin_addr or sin6_addr pointer, * depending on the sockaddr's family. * Returns NULL if the sockaddr is neither IPv4 nor IPv6. */ const void * NetUtils::GetInAddr(const struct sockaddr *sa) { if( sa->sa_family == AF_INET ) { return &(reinterpret_cast<const sockaddr_in *>(sa))->sin_addr; } else if( sa->sa_family == AF_INET6 ) { return &(reinterpret_cast<const sockaddr_in6 *>(sa))->sin6_addr; } else { return NULL; } } /** * Get IPv4 or IPv6 sockaddr size convenience function. * Returns 0 if the sockaddr is neither IPv4 nor IPv6. */ size_t NetUtils::GetAddrSize(const sockaddr *sa) { if( sa->sa_family == AF_INET ) { return sizeof *(reinterpret_cast<const sockaddr_in *>(sa)); } else if( sa->sa_family == AF_INET6 ) { return sizeof *(reinterpret_cast<const sockaddr_in6 *>(sa)); } else { return 0; } } /* * Get IPv4 or IPv6 sin[6]_port convenience function. * Returns the sockaddr's sin_addr or sin6_addr port, * depending on the sockaddr's family. * Returns -1 if the sockaddr is neither IPv4 nor IPv6. */ int NetUtils::GetInPort(const sockaddr *sa) { int port; if( sa->sa_family == AF_INET ) { port = (reinterpret_cast<const sockaddr_in *>(sa))->sin_port & 0xFFFF; } else if( sa->sa_family == AF_INET6 ) { port = (reinterpret_cast<const sockaddr_in6 *>(sa))->sin6_port & 0xFFFF; } else { return -1; } return ntohs( port ); } /* * Return true iff this address is unspecified ("0.0.0.0" or "::"). * [static] */ bool NetUtils::IsAddrUnspecified(const sockaddr *sa) { if( sa->sa_family == AF_INET ) { const struct in_addr *iap = &(reinterpret_cast<const sockaddr_in *>(sa))->sin_addr; const p4_uint32_t *ap = reinterpret_cast<const p4_uint32_t *>(iap); return *ap == INADDR_ANY; } else if( sa->sa_family == AF_INET6 ) { return IN6_IS_ADDR_UNSPECIFIED( &(reinterpret_cast<const sockaddr_in6 *>(sa))->sin6_addr ); } else { return true; // huh? I guess we'll call it unspecified } } /* * Set this address to the appropriate wildcard address. * Return true iff it had a valid family. * [static] */ bool NetUtils::SetAddrUnspecified(sockaddr *sa) { if( sa->sa_family == AF_INET ) { struct in_addr *iap = &(reinterpret_cast<sockaddr_in *>(sa))->sin_addr; p4_uint32_t *ap = reinterpret_cast<p4_uint32_t *>(iap); *ap = INADDR_ANY; return true; } else if( sa->sa_family == AF_INET6 ) { struct in6_addr *in6 = &(reinterpret_cast<sockaddr_in6 *>(sa))->sin6_addr; p4_uint32_t *a = reinterpret_cast<p4_uint32_t *>(in6); a[0] = 0; a[1] = 0; a[2] = 0; a[3] = 0; return true; } return false; } /* * Is this an IPv6 sockaddr? * [static] */ bool NetUtils::IsAddrIPv6(const sockaddr *sa) { return sa->sa_family == AF_INET6; } /* * Simple function to test whether or not a string looks like an IP address in * dotted notation, or not. The test is just that it contains only numbers and * exactly 3 '.' chars - nothing more sophisticated than that. * However, an arbitrary port specification (digits) may be appended after a ':'. * NB: Unlike IPv6 addresses, IPv4 addresses may NOT be enclosed in square brackets. * * If allowPrefix is true then allow a partial address (fewer than 3 periods), * but prohibit a port in such a partial address. * [static] */ bool NetUtils::IsIpV4Address( const char *str, bool allowPrefix ) { int numDots = 0; int numColons = 0; bool seenPort = false; for( const char *cp = str; *cp; cp++ ) { if( *cp == ':' ) { // no more than one colon allowed in an IPv4 address if( ++numColons > 1 ) break; } if( *cp == '.' ) { numDots++; continue; } if( !isdigit(*cp & 0xFF) ) return false; } if( numDots > 3 || numColons > 1 ) return false; if( allowPrefix ) { return (numDots == 3) || (numColons == 0); } return (numDots == 3); } /* * Simple function to test whether or not a string looks like an IPv6 address in * hex colon (or IPv4-mapped dotted) notation, or not. The test is just: * - it contains only hexadecimal digits and the ':' char, * optionally followed by a zone id ('%' and any alphanumeric chars). * - if it contains any periods then it must end in a valid-looking complete * IPv4 embedded address (optionally followed by a scope-id). * - and there must be at least 2 colons (not counting the scope-id portion, if any). * - allow the address optionally to be enclosed by square brackets, eg: [::1] * - nothing more sophisticated than that. * * - TODO: check that IPv4-mapped addresses start with 80 bits of zero followed by * 16 bits of 1, followed by an IPv4 address. * * Accept an allowPrefix 2nd argument to match IsIpV4Address(), but ignore it; * IPv6 addresses and prefixes must always have at least 2 colons, * and we don't allow partial mapped IPv4 addresses (that's just plain silly). * [static] */ bool NetUtils::IsIpV6Address( const char *str, bool /* allowPrefix */ ) { int numColons = 0; int numDots = 0; bool brackets = (*str == '['); if( brackets ) str++; for( const char *cp = str; *cp; cp++ ) { switch( *cp ) { case '%': while( *++cp ) { if( !isalnum(*cp & 0xFF) ) return false; } return (numColons >= 2) && (numDots == 0 || numDots == 3); break; case ':': // no colons allowed in mapped-V4 section if( numDots > 0 ) return false; numColons++; break; case '.': numDots++; break; case ']': // allow a right bracket only at the end // and only if str began with a left bracket if( !brackets || cp[1] ) return false; break; default: if( !isxdigit(*cp & 0xFF) ) return false; break; } } return (numColons >= 2) && (numDots == 0 || numDots == 3); } /* * Simple function to check whether an IP is loopback, as defined by * the IANA as: * IPv4 = 127.0.0.1/8 * IPv6 = ::1 * * IPv6 mapped IPv4 loopback addresses that theoretically are valid, * but it's unlikely that we'll ever encounter them in the wild. */ bool NetUtils::IsLocalAddress( const char *addr ) { static const NetIPAddr localV4(StrRef("127.0.0.1"), 8); static const NetIPAddr localV6(StrRef("::1"), 128); static const NetIPAddr localMapped(StrRef("::ffff:127.0.0.1"), 104); // 80 + 16 + 8 // empty string means connect to localhost or bind to all interfaces (including local) if( *addr == '\0' ) return true; const NetIPAddr tgtAddr(StrRef(addr), 0); if( tgtAddr.IsTypeV4() ) return tgtAddr.Match(localV4); if( tgtAddr.IsTypeV6() ) return tgtAddr.Match(localV6) || tgtAddr.Match(localMapped); return false; } // return a printable address void NetUtils::GetAddress( int family, const sockaddr *addr, int raf_flags, StrBuf &printableAddress) { # ifndef OS_NT typedef const void *INADDR_PTR_TYPE; # else typedef PVOID INADDR_PTR_TYPE; # endif if( (family != AF_INET) && (family != AF_INET6) ) { // don't worry about RAF_NAME and RAF_PORT if we don't understand the address family printableAddress.Set( "unknown" ); return; } printableAddress.Clear(); printableAddress.Alloc( P4_INET6_ADDRSTRLEN ); printableAddress.SetLength(0); printableAddress.Terminate(); // default to numeric host string; we'll clear this if we get a name bool wantNumericHost = true; bool isIPv6 = IsAddrIPv6(addr); // don't try to DNS-resolve an unspecified address -- it'll just timeout after a few seconds anyway if( (raf_flags & RAF_NAME) && !IsAddrUnspecified(addr) ) { // try to get the (DNS) name of the server; fall back to the numeric form of the hostname. int bufsize = (NI_MAXHOST >= P4_INET6_ADDRSTRLEN) ? NI_MAXHOST : P4_INET6_ADDRSTRLEN ; printableAddress.Alloc( bufsize ); # ifdef NI_NAMEREQD // try the modern way (getnameinfo) /* * For IPv4 do not pass NI_NAMEREQD, so if it can't get the name, * getnameinfo will fill in the numeric form of the hostname. * For IPv6 do pass NI_NAMEREQD, so we can add the "[...]" later * with the numeric address. */ const int flags = isIPv6 ? NI_NAMEREQD : 0; if( !::getnameinfo( addr, GetAddrSize(addr), printableAddress.Text(), NI_MAXHOST, NULL, 0, flags ) ) { printableAddress.SetLength(); wantNumericHost = false; } # else // no, try it the old-fashioned non-re-entrant way (gethostbyaddr) struct hostent *h = NULL; if( h = ::gethostbyaddr( GetInAddr(addr), GetAddrSize(addr), addr->sa_family ) && h->h_name ) { printableAddress << h->h_name; wantNumericHost = false; } # endif // NI_NAMEREQD } // either the caller wanted the numeric form, or we tried to get a hostname but failed if( wantNumericHost ) { char *buf = printableAddress.Text(); // format IPv6 numeric addresses nicely to make them easier to read (and unambiguous) if( isIPv6 ) { printableAddress.Set( "[" ); buf++; } // just get the numeric form of the hostname. if( ::inet_ntop( family, (INADDR_PTR_TYPE)GetInAddr(addr), buf, INET6_ADDRSTRLEN ) ) { printableAddress.SetLength(); } else { // I give up printableAddress.Set( "unknown" ); } if( isIPv6 ) printableAddress.Append( "]" ); } if( raf_flags & RAF_PORT ) { // caller also wants the portnum int portnum = GetInPort( addr ); StrNum numbuf( portnum ); printableAddress.Append( ":" ); printableAddress.Append(&numbuf); } } # ifdef OS_NT // initialize windows networking // returns 0 on success, error code on failure // [static] int NetUtils::InitNetwork() { WSADATA wsaData; int starterr = WSAStartup(MAKEWORD(2,2), &wsaData); if (starterr != 0) { int err = WSAGetLastError(); return err; } return 0; } // cleanup windows networking // [static] void NetUtils::CleanupNetwork() { WSACleanup(); } #else int NetUtils::InitNetwork() { return 0; } void NetUtils::CleanupNetwork() { } # endif // OS_NT