// -*- 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 ::= "jsh"|"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) , mExtraTransports(NULL) { mPrefix.mType = PT_NONE; mPrefix.mName = ""; } // default ctor NetPortParser::NetPortParser( const StrRef &portstr) : mPortString(portstr) , mTransport("") , mHost("") , mPort("") , mHostPort("") , mPortColon(false) , mExtraTransports(NULL) { mPrefix.mType = PT_NONE; mPrefix.mName = ""; Parse(); } NetPortParser::NetPortParser( const StrRef &portstr, const Prefix *extraTransports) : mPortString(portstr) , mTransport("") , mHost("") , mPort("") , mHostPort("") , mPortColon(false) , mExtraTransports(extraTransports) { mPrefix.mType = PT_NONE; mPrefix.mName = ""; Parse(); } NetPortParser::NetPortParser( const char *portstr) : mPortString(portstr) , mTransport("") , mHost("") , mPort("") , mHostPort("") , mPortColon(false) , mExtraTransports(NULL) { 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) , mExtraTransports(rhs.mExtraTransports) { } // 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; mExtraTransports = rhs.mExtraTransports; } 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; if( mExtraTransports != rhs.mExtraTransports ) 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::MayJSH() const { // JSH is never optional; either you must use JSH or you must not return MustJSH(); } bool NetPortParser::MustJSH() const { return mPrefix.mType == PT_JSH; } 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[] = { {"jsh", PT_JSH}, // java flavor of rsh {"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 = sizeof(prefixes)/sizeof(prefixes[0]) - 1; // 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; } // try the user-supplied table if( mExtraTransports ) { for( pfx = mExtraTransports; 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: or jsh: cmd // store the cmd in the host field if( pfx->mType == PT_JSH || pfx->mType == PT_RSH ) { mPrefix = *pfx; mHost.Set( str ); mHostPort.Set( str ); mTransport = mPrefix.mName; return; } } else if( strcmp(str, "jsh") == 0 ) { // java flavor of "rsh" // "jsh" should be used only by p4d after seeing "-ij" pfx = FindPrefix( "jsh", 3 ); 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 ); } if( NetUtils::IsIpV6Address(mHost.Text()) ) { const char *bp = mHost.Text(); const char *ep = &bp[mHost.Length()-1]; // store the trailing %zoneid, if any for( const char *p = ep; p > bp; p-- ) { if( *p == '%' ) { mZoneID.Set( p, ep-p+1 ); break; } } } // 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( !MustJSH() && !MustRSH() && !mPortColon && (mPort.Length() <= 0) ) { e->Set( MsgServer::PortMissing ) << String(); return false; } return true; } void NetPortParser::Parse(const StrRef &portstr) { mPortString = portstr; Parse(); }