netportparser.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014.2/
  • net/
  • netportparser.cc
  • View
  • Commits
  • Open Download .zip Download (18 KB)
// -*- mode: C++; tab-width: 8; -*-
// vi:ts=8 sw=4 noexpandtab autoindent

/**
 * netportparser.cc
 *
 * Copyright 2011 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 *
 * Description:
 *     Parse a P4PORT into pieces.
 *     pseudo-regex syntax loose definition:
 *
 *     p4port         ::= [prefix:][host:]port ;
 *     prefix         ::= "rsh"|"tcp"|"tcp4"|"tcp6"|"ssl"|"ssl4"|"ssl6" ;
 *       NB: "tcp"   means non-ssl, IPv4 only
 *           "tcp4"  means non-ssl, IPv4 only (synonym for "tcp")
 *           "tcp6"  means non-ssl, IPv6 only
 *           "tcp46" means non-ssl, IPv4 or IPv6 (IPv4 preferred)
 *           "tcp64" means non-ssl, IPv4 or IPv6 (IPv6 preferred)
 *           "ssl"   means ssl,     IPv4 or IPv6 (IPv4 preferred)
 *           "ssl4"  means ssl,     IPv4 only (synonym for "ssl")
 *           "ssl6"  means ssl,     IPv6 only
 *           "ssl46" means ssl,     IPv4 or IPv6 (IPv4 preferred)
 *           "ssl64" means ssl,     IPv4 or IPv6 (IPv6 preferred)
 *     NB: host may optionally be enclosed in square brackets: [...]
 *     host           := hostname |ipv4dottedquad | ipv6hex | ipv6mappedv4 ;
 *     hostname       := label[. label]* ;
 *     label          := [A-Za-z0-9-_]+ ;    // LDH; by convention (but all octets are allowed)
 *     ipv4dottedquad := decoctet.decoctet.decoctet.decoctet ;
 *     digit          := [0-9] ;
 *     decoctet       := digit{1,3} ;
 *     hexchar        := [0-9A-Fa-f] ;
 *     hexword        := hexchar{1,4} ;
 *       NB: leading 0s may (but need not) be omitted
 *     ipv6hex        := hexword:hexword:hexword:hexword:hexword:hexword ;
 *       NB: 64-bit network prefix and 64-bit interface id [eg, host address]
 *                  (unless high 3 bits are 0)
 *           Exactly one pair of adjacent colons indicates enough 0 hexwords to fill to 8 hexwords total
 *
 *           unspecified    ::0 (IN6ADDR_ANY)
 *           loopback       ::1 (IN6ADDR_LOOPBACK)
 *           link-local     FE80::* (FE80::/10, 54 bits 0,         64-bits interface id)
 *           site-local     FEC0::* (FEC0::/10, 54 bits subnet id, 64-bits interface id) [deprecated]
 *           anycast        interface id of 0
 *
 *           See RFC 4291 "IP Version 6 Addressing Architecture"
 *           See RFC 5952 "A Recommendation for IPv6 Address Text Representation"
 *     ipv6mappedv4   := hexword:hexword:hexword:hexword:ipv4dottedquad 
 *                    |  hexword:hexword:hexword:hexword:hexword:hexword ;
 *       NB: Upper 80 bits are 0, then 16 bits of 1, then 32-bit IPv4 address in dotted-quad or hex
 *           So normally written as ::FFFF:127.0.0.1
 *           See RFC 6052 "IPv6 Addressing of IPv4/IPv6 Translators"
 *
 *     (1) If the text from the beginning of the string to the first colon
 *         matches one of our known prefixes, remember it as the prefix
 *         and skip past the colon.
 *     (2) The host portion may be enclosed in square brackets; if so, the port
 *         is the part after the right bracket (it may/should be preceded by a colon).
 *         See "RFC 3986 : Uniform Resource Identifier (URI): Generic Syntax",
 *             "Section 3.2.2.  Host".
 *     (3) Otherwise the text after the last colon (or the entire string if no colon)
 *         is the port.  It is allowed to be empty (for bind() OS chooses port).
 *     (4) Anything left is the host (name or address [IPv4 or IPv6]).
 *
 *     (a) IPv4: ":0" means "localhost:0" for connect, and "*:0" for bind
 *     (b) IPv4: "" and ":" mean ":0"
 *     (c) IPv6: ":::nnnn" means host="::", port="nnnn"
 *     (d) IPv6: ":::0" means "localhost::0" for connect, and "*::0" for bind
 *
 *     We don't parse the host field!  But we do grovel through it a bit ...
 *
 *     Hostname syntax:
 *     * see RFC 952 "DOD INTERNET HOST TABLE SPECIFICATION"
 *       - DNS labels are LDH (Letters, Digits, and Hyphen), and start with a letter.
 *     * see RFC 1123 "Requirements for Internet Hosts -- Application and Support"
 *       - now DNS labels may also start with a digit.
 *     * see RFC 2181 "Clarifications to the DNS Specification"
 *       - DNS label octets may contain any binary value (but we don't support this).
 *     * IDNA (non-ASCII DNS names [eg, Unicode])
 *       - we do NOT support IDNA at this time.
 *       - see RFC 3492 "Punycode: A Bootstring encoding of Unicode
 *                           for Internationalized Domain Names in Applications (IDNA)"
 *       - see RFC 5890 "Internationalized Domain Names for Applications (IDNA):
 *                           Definitions and Document Framework"
 *       - see RFC 5891 "Internationalized Domain Names in Applications (IDNA): Protocol"
 */

# include <stdhdrs.h>
# include <strbuf.h>
# include <ctype.h>
# include <error.h>
# include <msgserver.h>
# include <msgrpc.h>
# include <hostenv.h>
# include "netport.h"
# include "netportipv6.h"
# include "netportparser.h"
# include "netutils.h"
# include "debug.h"
# include "tunable.h"

/*
 * In 2012.1 and 2012.2, IPv6 infrastructure is in place but is neither documented
 * nor supported.  Customers will use only IPv4; IPv6 is reserved for internal use
 * and testing.  Therefore we force all P4PORT strings to use IPv4 unless they
 * explicitly specify IPv6.  Transport prefixes of "tcp:" or "ssl:" (or none)
 * therefore are equivalent to "tcp4:" and "ssl4:"
 *
 * In 2013.1 IPv6 will be fully documented and supported, and we will continue
 * to support IPv4.  Users will be able to force IPv4, force IPv6, prefer IPv4,
 * or prefer IPv6.  At that time, the meaning of no prefix, "tcp:", and "ssl:"
 * will change: always forcing IPv4 will be optional during address resolution.
 *
 * If the user requests compliance with RFC 3484 via the "net.rfc3484" tunable,
 * then the prefixes "tcp:" and "ssl:" will mean that we let the OS choose between
 * IPv4 and IPv6 in conformance to RFC 3484, which specifies the precedence in
 * which addresses should be resolved.
 * If they don't set "net.rfc3484" (or set it to 0) then they will continue to
 * prefer IPv4, as in earlier releases.
 *
 * See RFC 3484 "Default Address Selection for Internet Protocol version 6 (IPv6)".
 * Note that it is optional for host administrators to be able to modify this
 * precedence table:
 * - linux/Unix:        edit /etc/gai.conf
 * - Windows:           netsh.exe interface ipv6
 * - Mac OS X:          appears to modify precedence dynamically based on
 *                      routing timing to favor working addresses.
 *
 * Uncomment the definition of RFC_3484_COMPLIANT to enable the 2013.1 behavior
 * (and do the same in <tests/portparsertest.cc> to pass the unit tests).
 * In 2013.1 we can remove this macro and the ifdef's, but for now this allows
 * us easily to test both current and new behavior.
 */

# define RFC_3484_COMPLIANT

/*
 * If we allow RFC 3484 compliance, return true iff the user has enabled it
 * (via the "net.rfc3484" tunable).  Otherwise return false.
 */
static bool
HonorRFC3484()
{
# ifdef RFC_3484_COMPLIANT
	return p4tunable.Get( P4TUNE_NET_RFC3484 );
# else
	return false;
# endif // RFC_3484_COMPLIANT
}

/*
 * Orthodox Canonical Form (OCF) methods
 */


/**
 * default ctor
 */
NetPortParser::NetPortParser()
: mPortString("")
, mTransport("")
, mHost("")
, mPort("")
, mHostPort("")
, mPortColon(false)
{
	mPrefix.mType = PT_NONE;
	mPrefix.mName = "";
} // default ctor

NetPortParser::NetPortParser(
	const StrRef &portstr)
: mPortString(portstr)
, mTransport("")
, mHost("")
, mPort("")
, mHostPort("")
, mPortColon(false)
{
	mPrefix.mType = PT_NONE;
	mPrefix.mName = "";
	Parse();
}

NetPortParser::NetPortParser(
	const char *portstr)
: mPortString(portstr)
, mTransport("")
, mHost("")
, mPort("")
, mHostPort("")
, mPortColon(false)
{
	mPrefix.mType = PT_NONE;
	mPrefix.mName = "";
	Parse();
}

/**
 * copy ctor
 */
NetPortParser::NetPortParser(
	const NetPortParser &rhs)
: mPortString(rhs.mPortString)
, mTransport(rhs.mTransport)
, mHost(rhs.mHost)
, mPort(rhs.mPort)
, mHostPort(rhs.mHostPort)
, mPrefix(rhs.mPrefix)
, mPortColon(rhs.mPortColon)
{
} // copy ctor

/**
 * dtor
 */
NetPortParser::~NetPortParser()
{
} // dtor

/**
 * assignment op
 */
const NetPortParser    &
NetPortParser::operator=(
	const NetPortParser &rhs)
{
	if( this != &rhs ) {
	    mPortString = rhs.mPortString;
	    mTransport = rhs.mTransport;
	    mHost = rhs.mHost;
	    mPort = rhs.mPort;
	    mHostPort = rhs.mHostPort;
	    mPortColon = rhs.mPortColon;
	    mPrefix = rhs.mPrefix;
	}

	return *this;
} // op=

/**
 * op==
 */
bool
NetPortParser::operator==(
	const NetPortParser &rhs) const
{
	if( this == &rhs ) {
	    return true;
	}

	if( mPortString != rhs.mPortString )
	    return false;
	if( mTransport != rhs.mTransport )
	    return false;
	if( mHost != rhs.mHost )
	    return false;
	if( mPort != rhs.mPort )
	    return false;
	if( mHostPort != rhs.mHostPort )
	    return false;
	if( mPortColon != rhs.mPortColon )
	    return false;
	if( mPrefix.mType != rhs.mPrefix.mType )
	    return false;

	return true;
} // op==

/**
 * op!=
 */
bool
NetPortParser::operator!=(
	const NetPortParser &rhs) const
{
	return !(*this == rhs);
} // op!=

// accessors

/*
 * RFC 3484 specifies a precedence for deciding the order of returned
 * addresses to be used for connect/bind.  If we're following RFC 3484
 * we use the list returned by getaddrinfo() as-is.  Otherwise we'll
 * filter out rejected families (via MustIPv4() and MustIPv6()) and then
 * try the preferred family (if any) first.  This filtering and ordering
 * is applied to the already-ordered list returned by getaddrinfo().
 *
 * In 2012.1, NetPortParser.MustRfc3484() will always return false,
 * but in 2013.1 and later it will return the value of "-v net.rfc3484"
 * if the transport didn't specify an IPv4 or IPv6 preference; eg,
 * no transport prefix, or a prefix of "tcp:" or "ssl:" (and the host
 * wasn't a numeric address).
 *
 * If this causes problems for customers then we might want
 * to continue to return false until a later date.
 *
 * See RFC 3484 "Default Address Selection for Internet Protocol version 6 (IPv6)".
 */
bool
NetPortParser::MustRfc3484() const
{
	switch( mPrefix.mType )
	{
	case PT_NONE:
	case PT_TCP:
	case PT_SSL:
	    return HonorRFC3484();
	default:
	    return false;
	}
}

bool
NetPortParser::MayIPv4() const
{
	switch( mPrefix.mType )
	{
	case PT_NONE:
	case PT_TCP:
	case PT_TCP4:
	case PT_TCP46:
	case PT_TCP64:
	case PT_SSL:
	case PT_SSL4:
	case PT_SSL46:
	case PT_SSL64:
	    return true;
	default:
	    return false;
	}
}

bool
NetPortParser::MayIPv6() const
{
	switch( mPrefix.mType )
	{
	case PT_TCP6:
	case PT_TCP46:
	case PT_TCP64:
	case PT_SSL6:
	case PT_SSL46:
	case PT_SSL64:
	    return true;
	case PT_NONE:
	case PT_TCP:
	case PT_SSL:
	    return HonorRFC3484();
	default:
	    return false;
	}
}

bool
NetPortParser::PreferIPv4() const
{
	switch( mPrefix.mType )
	{
	case PT_NONE:
	case PT_TCP:
	case PT_SSL:
	    return !HonorRFC3484();
	case PT_TCP4:
	case PT_TCP46:
	case PT_SSL4:
	case PT_SSL46:
	    return true;
	default:
	    return false;
	}
}

bool
NetPortParser::PreferIPv6() const
{
	switch( mPrefix.mType )
	{
	case PT_TCP6:
	case PT_TCP64:
	case PT_SSL6:
	case PT_SSL64:
	    return true;
	default:
	    return false;
	}
}

// explicitly listed IPv6?
bool
NetPortParser::WantIPv6() const
{
	switch( mPrefix.mType )
	{
	case PT_TCP6:
	case PT_TCP46:
	case PT_TCP64:
	case PT_SSL6:
	case PT_SSL46:
	case PT_SSL64:
	    return true;
	default:
	    return false;
	}
}

bool
NetPortParser::MayRSH() const
{
	// RSH is never optional; either you must use RSH or you must not
	return MustRSH();
}

bool
NetPortParser::MustRSH() const
{
	return mPrefix.mType == PT_RSH;
}

bool
NetPortParser::MustSSL() const
{
	switch( mPrefix.mType )
	{
	case PT_SSL:
	case PT_SSL4:
	case PT_SSL6:
	case PT_SSL46:
	case PT_SSL64:
	    return true;
	default:
	    return false;
	}
}

bool
NetPortParser::MustIPv4() const
{
	switch( mPrefix.mType )
	{
	case PT_NONE:
	case PT_TCP:
	case PT_SSL:
	    return !HonorRFC3484();
	case PT_TCP4:
	case PT_SSL4:
	    return true;
	default:
	    return false;
	}
}

bool
NetPortParser::MustIPv6() const
{
	return mPrefix.mType == PT_TCP6 || mPrefix.mType == PT_SSL6;
}

const StrBuf
NetPortParser::String(
	PPOpts opts) const
{
	StrBuf result;
	StrBuf tmp;

	if( opts & PPO_TRANSPORT )
	{
	    tmp = Transport();
	    if( tmp.Length() && (tmp != "tcp") )    // don't report the default transport
	    {
	        result.Set( tmp );
	        result.Append( ":" );
	    }
	}

	tmp = Host();
	if( tmp.Length() )
	{
	    result.Append( &tmp );
	}

	if( opts & PPO_PORT )
	{
	    result.Append( ":" );
	    tmp = Port();
	    result.Append( &tmp );
	}

	return result;
}
/**
 * NetPortParser::GetQualifiedP4Port
 *
 * @brief  Return the external view of P4PORT, if current
 *         p4port does not contain a host/ip addr try using
 *         the value of the addr stored in the server spec
 *
 * @param serverSpecAddr, string server spec address field
 * @param e,              Error reference to hand back any error
 */
const StrBuf
NetPortParser::GetQualifiedP4Port( StrBuf &serverSpecAddr, Error &e ) const
{
	StrBuf result;
	StrBuf tmpHost;

	// sanity check that the numeric port suffix exists.
	if( mPort.Length() == 0 )
	{
	    e.Set( MsgRpc::BadP4Port ) << String();
	    return String();
	}

	// if host is present just return the port string
	if( mHost.Length() != 0 )
	{
	    return String();
	}

	if( serverSpecAddr.Length() )
	{
	    NetPortParser npp( serverSpecAddr );
	    if( npp.mHost.Length() )
	    {
		return npp.String();
	    }
	}
	e.Set( MsgRpc::NoHostnameForPort );
	return String();
}

int
NetPortParser::PortNum() const
{
	return Port().Atoi();
}

/*
 * Other methods
 */

const NetPortParser::Prefix *
NetPortParser::FindPrefix(
	const char *prefix,
	int len)
{
	static const Prefix prefixes[] = {
	    {"rsh",     PT_RSH},
	    {"tcp",     PT_TCP},
	    {"tcp4",    PT_TCP4},
	    {"tcp6",    PT_TCP6},
	    {"tcp46",   PT_TCP46},
	    {"tcp64",   PT_TCP64},
	    {"ssl",     PT_SSL},
	    {"ssl4",    PT_SSL4},
	    {"ssl6",    PT_SSL6},
	    {"ssl46",   PT_SSL46},
	    {"ssl64",   PT_SSL64},
	    {"",        PT_NONE}
	};
	static const int kNoPrefix = 11;    // index of the PT_NONE entry

	if( (len < 3) || (len > 5) )
	{
	    // all of our prefixes are 3, 4, or 5 chars long
	    return &prefixes[kNoPrefix];
	}

	const Prefix *pfx;
	for( pfx = prefixes; pfx->mName[0]; ++pfx )
	{
	    if( strncmp(prefix, pfx->mName, len) == 0 )
	        return pfx;
	}

	return pfx;
}

void
NetPortParser::Parse()
{
	const Prefix *pfx = FindPrefix( "", 0 );    // init to PT_NONE
	const char *str = mPortString.Text();

	// find the prefix
	const char *p = strchr(str, ':');
	if( p )
	{
	    pfx = FindPrefix( str, p-str );
	    if( pfx->mType != PT_NONE )
	    {
	        // skip past valid prefix
	        str = ++p;
	    }

	    // don't parse rsh: cmd
	    // store the cmd in the host field
	    if( pfx->mType == PT_RSH )
	    {
	        mPrefix = *pfx;
	        mHost.Set( str );
	        mHostPort.Set( str );
	        mTransport = mPrefix.mName;
	        return;
	    }
	}
	else if( strcmp(str, "rsh") == 0 )
	{
	    pfx = FindPrefix( "rsh", 3 );
	    mPrefix = *pfx;
	    mHost.Set( str );
	    mHostPort.Set( str );
	    mTransport = mPrefix.mName;
	    return;
	}

	// address is optionally enclosed in square brackets
	const char *rbracket = NULL;
	if( *str == '[' )
	{
	    rbracket = strrchr(str, ']');
	    if( rbracket )
		++str;		// skip past the lbracket
	}

	// count the colons after the prefix (if any)
	int numColons = 0;
	const char *lastColon = NULL;
	for( const char *cp = str; *cp; cp++ )
	{
	    if( *cp == ':' )
	    {
	        lastColon = cp;
	        numColons++;
	    }
	}

	if( rbracket )
	{
	    mHost.Set( str, rbracket - str );
	    --str;	// backup to include the left bracket
	    mHostPort.Set( str );

	    // If there are matched brackets, the host is everything inside the brackets
	    // and the port is everything after the right bracket (skipping the
	    // following colon).
	    if( rbracket[1] == ':' )
	    {
		rbracket++;
		numColons--;    // don't count the port-marking colon
		mPortColon = true;
	    }
	    mPort.Set( rbracket+1 );    // may be empty
	}
	else if( lastColon )
	{
	    // if there is a colon, the port is everything after the last colon
	    // and host is everything before it (after the optional prefix)
	    mPort.Set( lastColon+1 );    // may be empty
	    mHost.Set( str, lastColon - str );
	    mHostPort.Set( str );
	    numColons--;    // don't count the port-marking colon
	    mPortColon = true;
	}
	else
	{
	    // if no colon, the entire string is the port
	    mPort.Set( str );
	    mHostPort.Set( str );
	}

	// remember the prefix
	mPrefix = *pfx;

	// If neither IPv4 nor IPv6 was specified but a numeric address was given,
	// determine the transport type from the address.
	bool	ssl = false;
	switch( pfx->mType )
	{
	case PT_SSL:
	    ssl = true;
	    // Fall through!
	case PT_NONE:
	case PT_TCP:
	    /*
	     * If there are 2 or more colons and it's a numeric IPv6 address,
	     * default to "tcp6"; (IPv6 numeric addresses must have at least
	     * 2 colons, and we don't accept colons in a hostname, so 2 or more
	     * means numeric IPv6 address).
	     *
	     * If 0 or 1 colons, default to "tcp4" if the host
	     * is an IPv4 numeric address.
	     */
	    if( numColons >= 2 )
	    {
	        if( NetUtils::IsIpV6Address(mHost.Text()) )
	            mPrefix = *FindPrefix( ssl ? "ssl6" : "tcp6", 4 );
	    }
	    else
	    {
	        if( NetUtils::IsIpV4Address(mHost.Text(), NetUtils::IPADDR_PREFIX_PROHIBIT) )
	            mPrefix = *FindPrefix( ssl ? "ssl4" : "tcp4", 4 );
	    }
	    break;
	}

	mTransport = mPrefix.mName;
}

// JIRA P4-10855, job065290
bool
NetPortParser::IsValid(Error *e) const
{
	if( !MustRSH() && !mPortColon && (mPort.Length() <= 0) )
	{
	    e->Set( MsgServer::PortMissing ) << String();
	    return false;
	}

	return true;
}

void
NetPortParser::Parse(const StrRef    &portstr)
{
	mPortString = portstr;
	Parse();
}
# Change User Description Committed
#2 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code