// -*- mode: C++; tab-width: 8; -*- // vi:ts=8 sw=4 noexpandtab autoindent /** * netipaddr.cc - Parse an IPv4 or IPv6 address and compare it (honoring CIDR). * * Copyright 2012 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ #include <stdhdrs.h> #include <strbuf.h> #include "netportipv6.h" #include "netport.h" #include "netutils.h" #include "netipaddr.h" // we know that an IPv6 address is 128 bits (16 bytes) #define IPV6_ADDR_LEN 16 // uncomment to enable address parse/match debugging //#define NIPA_DEBUG #ifdef NIPA_DEBUG #include "debug.h" #endif // NIPA_DEBUG /* * Orthodox Canonical Form (OCF) methods */ // default ctor NetIPAddr::NetIPAddr() : m_text() , m_prefixlen(CIDR_UNSPEC) , m_type(IPADDR_INVALID) { } // normal ctor NetIPAddr::NetIPAddr(const StrPtr &addr, int prefixlen) : m_text(addr) , m_prefixlen(prefixlen) , m_type(IPADDR_INVALID) { Parse(); } // copy ctor NetIPAddr::NetIPAddr(const NetIPAddr &rhs) : m_text(rhs.m_text) , m_prefixlen(rhs.m_prefixlen) , m_type(rhs.m_type) , m_addr(rhs.m_addr) { } // dtor NetIPAddr::~NetIPAddr() { } // assignment op const NetIPAddr & NetIPAddr::operator=( const NetIPAddr &rhs) { if( this != &rhs ) { m_text = rhs.m_text; m_prefixlen = rhs.m_prefixlen; m_type = rhs.m_type; m_addr = rhs.m_addr; } return *this; } // op= // op== bool NetIPAddr::operator==( const NetIPAddr &rhs) const { if( this == &rhs ) { return true; } if( m_text != rhs.m_text || (m_prefixlen != rhs.m_prefixlen) || m_type != rhs.m_type ) { return false; } // m_addr is meaningful only if we're IPv4 or IPv6 if( IsTypeValid() && !IPAddrStorageEquals(m_addr, rhs.m_addr) ) return false; return true; } // op== // op!= bool NetIPAddr::operator!=( const NetIPAddr &rhs) const { return !(*this == rhs); } // op!= /* * Other methods */ void NetIPAddr::Parse() { const char *cp = m_text.Text(); m_type = IPADDR_INVALID; // try IPv4 first (most common), then IPv6 if( NetUtils::IsIpV4Address(cp, NetUtils::IPADDR_PREFIX_ALLOW) ) { // inet_aton will handle partial IPv4 addresses struct in_addr addr; if( inet_aton(cp, &addr) ) { struct sockaddr_in *sin = reinterpret_cast<struct sockaddr_in *>(&m_addr); sin->sin_family = AF_INET; sin->sin_port = 0; sin->sin_addr = addr; m_type = IPADDR_V4; } } else if( NetUtils::IsIpV6Address(cp, NetUtils::IPADDR_PREFIX_PROHIBIT) ) { StrBuf txt; if( *cp == '[' ) { // ignore surrounding brackets const char *p; for( p = cp+1; *p; p++ ) ; if( (p > cp+1) && (p[-1] == ']') ) { txt.Set( cp+1, p-cp-2 ); cp = txt.Text(); } } struct sockaddr_in6 *sin6 = reinterpret_cast<struct sockaddr_in6 *>(&m_addr); if( inet_pton(AF_INET6, cp, &sin6->sin6_addr) == 1 ) { sin6->sin6_family = AF_INET6; sin6->sin6_port = 0; m_type = IPADDR_V6; } } } void NetIPAddr::Set(const StrPtr &addr, int prefixlen) { m_text = addr; m_prefixlen = prefixlen; Parse(); } /* * Return this IPv4 address mapped to its IPv6 equivalent. * Just return ourselves if we're not IPv4. * * We can't convert anything but valid IPADDR_V4: * IPADDR_V6 conversion is a no-op * IPADDR_V4 maps into IPv6 (eg, prepend ::FFFF:) * IPADDR_INVALID (eg, text) returns itself * Our caller must check that conversion succeeded. * * Mapping: * 80 bits of 0 (10 bytes of 0x00) * 16 bits of 1 (2 bytes of 0xFF) * 32 bits of IPv4 address (4 bytes) */ const NetIPAddr NetIPAddr::MapV4toV6() const { // We convert only valid IPv4 addresses if( m_type != IPADDR_V4 ) return *this; NetIPAddr v6( *this ); v6.m_text.Set("::FFFF:"); v6.m_text.Append(m_text.Text()); v6.m_prefixlen = (m_prefixlen == CIDR_UNSPEC ? m_prefixlen : m_prefixlen + 96); // it's a little easier just to return v6.Parse(), but it's a little faster this way const sockaddr *v4_saddr = reinterpret_cast<const sockaddr *>(&m_addr); const sockaddr *v6_saddr = reinterpret_cast<const sockaddr *>(&v6.m_addr); const unsigned char *cv4_addr = static_cast<const unsigned char *>(NetUtils::GetInAddr(v4_saddr)); const unsigned char *cv6_addr = static_cast<const unsigned char *>(NetUtils::GetInAddr(v6_saddr)); unsigned char *v4_addr = const_cast<unsigned char *>(cv4_addr); unsigned char *v6_addr = const_cast<unsigned char *>(cv6_addr); int i; // left-pad 80 bits with 0 for( i = 0; i < 10; i++ ) { v6_addr[i] = 0; } // then pad 16 bits with 1 for( ; i < 12; i++ ) { v6_addr[i] = 0xFF; } // append the IPv4 address for( ; i < 16; i++ ) { v6_addr[i] = v4_addr[i-12]; } v6.m_type = IPADDR_V6; return v6; } /* * Construct a netmask corresponding to the given prefix length. * Set the leading prefixlen bits and clear the rest * Assume (correctly) that IPv6 addresses are stored as an array of 16 bytes. */ void Netmask6FromPrefixLen(struct in6_addr *mask, unsigned int prefixlen) { unsigned char *maskp = mask->s6_addr; if( prefixlen > NetIPAddr::CIDR_MAX_V6 ) prefixlen = NetIPAddr::CIDR_MAX_V6; memset( maskp, 0, IPV6_ADDR_LEN ); for( int i = prefixlen, j = 0; i > 0; i -= 8, ++j ) { maskp[j] = ((i >= 8) ? 0xFF : (0xFF << (8-i)) & 0xFF); } } /* * Compare two IPv6 socket addresses, masking by the prefix. * That is, compare that their network bits are equal. * Assume (correctly) that IPv6 addresses are stored as an array of 16 bytes. */ bool NetEqualsV6( const struct in6_addr *a, const struct in6_addr *b, int prefixlen) { // huh? if( !a || !b ) return false; struct in6_addr mask; Netmask6FromPrefixLen( &mask, (prefixlen == NetIPAddr::CIDR_UNSPEC ? NetIPAddr::CIDR_MAX_V6 : prefixlen) ); #ifdef NIPA_DEBUG char aaddr[36]; char baddr[36]; char maddr[36]; for( int i = 0; i < 16; i++ ) sprintf(&aaddr[2*i], "%02X", a->s6_addr[i]); for( int i = 0; i < 16; i++ ) sprintf(&baddr[2*i], "%02X", b->s6_addr[i]); for( int i = 0; i < 16; i++ ) sprintf(&maddr[2*i], "%02X", mask.s6_addr[i]); p4debug.printf( "%d: %s\n", __LINE__, aaddr ); p4debug.printf( "%d: %s\n", __LINE__, baddr ); p4debug.printf( "%d: %s\n", __LINE__, maddr ); #endif for( int i = 0; i < IPV6_ADDR_LEN; i++ ) { unsigned int mbyte = mask.s6_addr[i] & 0xFF; unsigned int abyte = a->s6_addr[i] & mbyte; unsigned int bbyte = b->s6_addr[i] & mbyte; if( abyte != bbyte ) return false; } return true; } bool NetIPAddr::Match(const NetIPAddr &target) const { #ifdef NIPA_DEBUG StrBuf mybuf; StrBuf tgtbuf; ToString(mybuf); target.ToString(tgtbuf); p4debug.printf(" NetIPAddr::Match(%s, %s)\n", mybuf.Text(), tgtbuf.Text()); #endif if( !IsTypeValid() || !target.IsTypeValid() ) return false; int netmaskbits = target.m_prefixlen; switch( m_type ) { case IPADDR_V4: if( target.IsTypeV6() ) { NetIPAddr v6( MapV4toV6() ); // return false if for some reason we couldn't convert to IPv6 return v6.IsTypeV6() ? v6.Match( target ) : false; } // do the first n bits match, where n == 0? yes if( netmaskbits == CIDR_MIN ) return true; // if no mask specified, they must all match if( netmaskbits == CIDR_UNSPEC ) netmaskbits = CIDR_MAX_V4; { const sockaddr *our_saddr = reinterpret_cast<const sockaddr *>(&m_addr); const sockaddr *tgt_saddr = reinterpret_cast<const sockaddr *>(&target.m_addr); /* * NB: inet_pton() etc always store addresses in network byte order, * for both IPv4 and IPv6. This is different than, eg, inet_addr(), * which returns a 32-bit address in host byte order. */ if( netmaskbits == CIDR_MAX_V4 ) { const void *our_addrp = NetUtils::GetInAddr(our_saddr); const void *tgt_addrp = NetUtils::GetInAddr(tgt_saddr); if( !our_addrp || !tgt_addrp ) return false; in_addr_t our_addr = *static_cast<const in_addr_t *>(our_addrp); in_addr_t tgt_addr = *static_cast<const in_addr_t *>(tgt_addrp); // all bits are network bits (!) so all must match return IN_ADDR_VAL(our_addr) == IN_ADDR_VAL(tgt_addr); } else { const void *our_addrp = NetUtils::GetInAddr(our_saddr); const void *tgt_addrp = NetUtils::GetInAddr(tgt_saddr); // huh? if( !our_addrp || !tgt_addrp ) return false; in_addr_t our_addr = *static_cast<const in_addr_t *>(our_addrp); in_addr_t tgt_addr = *static_cast<const in_addr_t *>(tgt_addrp); int netmask = ( 1 << (32 - netmaskbits) ) - 1; netmask = ~netmask; return (ntohl(IN_ADDR_VAL(our_addr)) & netmask) == (ntohl(IN_ADDR_VAL(tgt_addr)) & netmask); } } break; case IPADDR_V6: if( !target.IsTypeV6() ) { if( target.m_type == IPADDR_V4 ) { NetIPAddr tgt6 = target.MapV4toV6(); // return false if for some reason we couldn't convert to IPv6 return tgt6.IsTypeV6() ? Match( tgt6 ) : false; } else { return false; } } // do the first n bits match, where n == 0? yes if( netmaskbits == CIDR_MIN ) return true; // we know we're IPv6, so we know the actual type that GetInAddr will return { const sockaddr *our_saddr = reinterpret_cast<const sockaddr *>(&m_addr); const sockaddr *tgt_saddr = reinterpret_cast<const sockaddr *>(&target.m_addr); return NetEqualsV6(static_cast<const in6_addr *>(NetUtils::GetInAddr(our_saddr)), static_cast<const in6_addr *>(NetUtils::GetInAddr(tgt_saddr)), netmaskbits); } break; case IPADDR_INVALID: return false; } // quiet warning c4715: not all control paths return a value return false; } bool NetIPAddr::Match( const StrPtr &target, int prefixlen ) const { switch( m_type ) { case IPADDR_V4: case IPADDR_V6: return Match( NetIPAddr(target, prefixlen) ); case IPADDR_INVALID: return false; } // quiet warning c4715: not all control paths return a value return false; } /* * Compare two (IPv4 or IPv6) addresses for equality, byte-by-byte * [static] */ bool NetIPAddr::IPAddrStorageEquals( const ipaddr_storage &lhs, const ipaddr_storage &rhs) { const sockaddr *lhs_saddr = reinterpret_cast<const sockaddr *>(&lhs); const sockaddr *rhs_saddr = reinterpret_cast<const sockaddr *>(&rhs); size_t lhs_size = NetUtils::GetAddrSize(lhs_saddr); size_t rhs_size = NetUtils::GetAddrSize(rhs_saddr); if( lhs_size != rhs_size ) return false; const unsigned char *lhs_addr = static_cast<const unsigned char *>(NetUtils::GetInAddr(lhs_saddr)); const unsigned char *rhs_addr = static_cast<const unsigned char *>(NetUtils::GetInAddr(rhs_saddr)); for( int i = 0; i < lhs_size; i++ ) { if( lhs_addr[i] != rhs_addr[i] ) return false; } return true; } /* * Debugging -- return textual version of the address type * [static] */ const char * NetIPAddr::TypeName(IPAddrType t) { switch( t ) { case IPADDR_V4: return "IPv4"; case IPADDR_V6: return "IPv6"; case IPADDR_INVALID: return "<invalid>"; default: return "<unknown>"; } } /* * Debugging -- return textual version of the address type */ const char * NetIPAddr::TypeName() const { return TypeName(m_type); } /* * Debugging -- generate a textual representation of the address */ void NetIPAddr::ToString(StrBuf &buf) const { char numbuf[64]; const char *num = StrBuf::Itoa(m_prefixlen, &numbuf[63]); buf.Set("<"); buf.Append(m_text.Text()); buf.Append("/"); buf.Append(num); switch( m_type ) { case IPADDR_V4: buf.Append( "%v4" ); break; case IPADDR_V6: buf.Append( "%v6" ); break; case IPADDR_INVALID: buf.Append( "%!!" ); break; } buf.Append(">"); }