netutils.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014-1/
  • net/
  • netutils.cc
  • View
  • Commits
  • Open Download .zip Download (19 KB)
// -*- 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 "netutils.h"
# include "netsupport.h"
# include "debug.h"
# include "netdebug.h"

// a guess at a good buffer size; big enough for a max IPv6 address plus surrounding "[...]"
#define P4_INET6_ADDRSTRLEN	(INET6_ADDRSTRLEN+2)

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 == 0;
	}
	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
	}
}

/*
 * 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);
}

// 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
# Change User Description Committed
#1 15902 Matt Attaway A second renaming that I will not obliterate as a badge of shame
//guest/perforce_software/p4/2014_1/net/netutils.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.1/net/netutils.cc
#1 12188 Matt Attaway Move 'main' p4 into a release specific directory in prep for new releases
//guest/perforce_software/p4/net/netutils.cc
#1 9129 Matt Attaway Initial commit of the 2014.1 p4/p4api source code