From dced19f55cf70ce7c0b881af5125d95b76321373 Mon Sep 17 00:00:00 2001 From: Anton Likhtarov Date: Fri, 4 Apr 2014 19:00:31 -0700 Subject: [PATCH] Move common/network/IPAddress.h and related to folly/ Summary: Moving our internal IP/Mac address libraries to folly/ Facebook: We want to get rid of common/ dependencies in Mcrouter since we're going to open source it. Also looking at the original commit, seems like it's been the intention all along, so I just did it. I tried to keep dependencies intact as much as possible. Changing projects to use this directly should be in separate diffs. Test Plan: Run folly/network and common/network unit tests. Generate the list of targets with: ``` fbgs /$FILE.h | cut -f1 -d: | xargs -L1 dirname | cut -f2- -d/ | sort | uniq ``` Then fbconfig + fbmake. Will fix contbuild failures. Revert Plan: Reviewed By: simpkins@fb.com FB internal diff: D1261089 --- folly/Format-inl.h | 6 + folly/IPAddress.cpp | 397 +++++++++++++ folly/IPAddress.h | 433 ++++++++++++++ folly/IPAddressException.h | 64 ++ folly/IPAddressV4.cpp | 276 +++++++++ folly/IPAddressV4.h | 277 +++++++++ folly/IPAddressV6.cpp | 880 +++++++++++++++++++++++++++ folly/IPAddressV6.h | 334 +++++++++++ folly/MacAddress.cpp | 156 +++++ folly/MacAddress.h | 219 +++++++ folly/detail/IPAddress.h | 178 ++++++ folly/test/IPAddressTest.cpp | 1056 +++++++++++++++++++++++++++++++++ folly/test/IPAddressTest.h | 166 ++++++ folly/test/MacAddressTest.cpp | 167 ++++++ 14 files changed, 4609 insertions(+) create mode 100644 folly/IPAddress.cpp create mode 100644 folly/IPAddress.h create mode 100644 folly/IPAddressException.h create mode 100644 folly/IPAddressV4.cpp create mode 100644 folly/IPAddressV4.h create mode 100644 folly/IPAddressV6.cpp create mode 100644 folly/IPAddressV6.h create mode 100644 folly/MacAddress.cpp create mode 100644 folly/MacAddress.h create mode 100644 folly/detail/IPAddress.h create mode 100644 folly/test/IPAddressTest.cpp create mode 100644 folly/test/IPAddressTest.h create mode 100644 folly/test/MacAddressTest.cpp diff --git a/folly/Format-inl.h b/folly/Format-inl.h index a4568b13..5575151c 100644 --- a/folly/Format-inl.h +++ b/folly/Format-inl.h @@ -21,6 +21,10 @@ #include "folly/Exception.h" #include "folly/Traits.h" +// Ignore -Wformat-nonliteral warnings within this file +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + namespace folly { namespace detail { @@ -1167,3 +1171,5 @@ toAppend(const Formatter& value, Tgt * result) { } } // namespace folly + +#pragma GCC diagnostic pop diff --git a/folly/IPAddress.cpp b/folly/IPAddress.cpp new file mode 100644 index 00000000..9e902a1d --- /dev/null +++ b/folly/IPAddress.cpp @@ -0,0 +1,397 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "IPAddress.h" + +#include +#include +#include +#include + +#include "folly/String.h" + +using std::ostream; +using std::string; +using std::vector; + +namespace folly { + +// free functions +size_t hash_value(const IPAddress& addr) { + return addr.hash(); +} +ostream& operator<<(ostream& os, const IPAddress& addr) { + os << addr.str(); + return os; +} +void toAppend(IPAddress addr, string* result) { + result->append(addr.str()); +} +void toAppend(IPAddress addr, fbstring* result) { + result->append(addr.str()); +} + +// public static +IPAddressV4 IPAddress::createIPv4(const IPAddress& addr) { + if (addr.isV4()) { + return addr.asV4(); + } else { + return addr.asV6().createIPv4(); + } +} + +// public static +IPAddressV6 IPAddress::createIPv6(const IPAddress& addr) { + if (addr.isV6()) { + return addr.asV6(); + } else { + return addr.asV4().createIPv6(); + } +} + +// public static +CIDRNetwork IPAddress::createNetwork(StringPiece ipSlashCidr, + int defaultCidr, /* = -1 */ + bool applyMask /* = true */) { + if (defaultCidr > std::numeric_limits::max()) { + throw std::range_error("defaultCidr must be <= UINT8_MAX"); + } + vector vec; + split("/", ipSlashCidr, vec); + vector::size_type elemCount = vec.size(); + + if (elemCount == 0 || // weird invalid string + elemCount > 2) { // invalid string (IP/CIDR/extras) + throw IPAddressFormatException("Invalid ipSlashCidr specified. ", + "Expected IP/CIDR format, got ", + "'", ipSlashCidr, "'"); + } + IPAddress subnet(vec.at(0)); + uint8_t cidr = (defaultCidr > -1) ? defaultCidr : (subnet.isV4() ? 32 : 128); + + if (elemCount == 2) { + try { + cidr = to(vec.at(1)); + } catch (...) { + throw IPAddressFormatException("Mask value ", + "'", vec.at(1), "' not a valid mask"); + } + } + if (cidr > subnet.bitCount()) { + throw IPAddressFormatException("CIDR value '", cidr, "' ", + "is > network bit count ", + "'", subnet.bitCount(), "'"); + } + return std::make_pair(applyMask ? subnet.mask(cidr) : subnet, cidr); +} + +// public static +IPAddress IPAddress::fromBinary(ByteRange bytes) { + if (bytes.size() == 4) { + return IPAddress(IPAddressV4::fromBinary(bytes)); + } else if (bytes.size() == 16) { + return IPAddress(IPAddressV6::fromBinary(bytes)); + } else { + string hexval = detail::Bytes::toHex(bytes.data(), bytes.size()); + throw IPAddressFormatException("Invalid address with hex value ", + "'", hexval, "'"); + } +} + +// public static +IPAddress IPAddress::fromLong(uint32_t src) { + return IPAddress(IPAddressV4::fromLong(src)); +} +IPAddress IPAddress::fromLongHBO(uint32_t src) { + return IPAddress(IPAddressV4::fromLongHBO(src)); +} + +// default constructor +IPAddress::IPAddress() + : addr_() + , family_(AF_UNSPEC) +{ +} + +// public string constructor +IPAddress::IPAddress(StringPiece addr) + : addr_() + , family_(AF_UNSPEC) +{ + string ip = addr.str(); // inet_pton() needs NUL-terminated string + auto throwFormatException = [&](const string& msg) { + throw IPAddressFormatException("Invalid IP '", ip, "': ", msg); + }; + + if (ip.size() < 2) { + throwFormatException("address too short"); + } + if (ip.front() == '[' && ip.back() == ']') { + ip = ip.substr(1, ip.size() - 2); + } + + // need to check for V4 address second, since IPv4-mapped IPv6 addresses may + // contain a period + if (ip.find(':') != string::npos) { + in6_addr ipAddr; + if (inet_pton(AF_INET6, ip.c_str(), &ipAddr) != 1) { + throwFormatException("inet_pton failed for V6 address"); + } + addr_ = IPAddressV46(IPAddressV6(ipAddr)); + family_ = AF_INET6; + } else if (ip.find('.') != string::npos) { + in_addr ipAddr; + if (inet_pton(AF_INET, ip.c_str(), &ipAddr) != 1) { + throwFormatException("inet_pton failed for V4 address"); + } + addr_ = IPAddressV46(IPAddressV4(ipAddr)); + family_ = AF_INET; + } else { + throwFormatException("invalid address format"); + } +} + +// public sockaddr constructor +IPAddress::IPAddress(const sockaddr* addr) + : addr_() + , family_(AF_UNSPEC) +{ + if (addr == nullptr) { + throw IPAddressFormatException("sockaddr == nullptr"); + } + family_ = addr->sa_family; + switch (addr->sa_family) { + case AF_INET: { + const sockaddr_in *v4addr = reinterpret_cast(addr); + addr_.ipV4Addr = IPAddressV4(v4addr->sin_addr); + break; + } + case AF_INET6: { + const sockaddr_in6 *v6addr = reinterpret_cast(addr); + addr_.ipV6Addr = IPAddressV6(v6addr->sin6_addr); + break; + } + default: + throw InvalidAddressFamilyException(addr->sa_family); + } +} + +// public ipv4 constructor +IPAddress::IPAddress(const IPAddressV4 ipV4Addr) + : addr_(ipV4Addr) + , family_(AF_INET) +{ +} + +// public ipv4 constructor +IPAddress::IPAddress(const in_addr ipV4Addr) + : addr_(IPAddressV4(ipV4Addr)) + , family_(AF_INET) +{ +} + +// public ipv6 constructor +IPAddress::IPAddress(const IPAddressV6& ipV6Addr) + : addr_(ipV6Addr) + , family_(AF_INET6) +{ +} + +// public ipv6 constructor +IPAddress::IPAddress(const in6_addr& ipV6Addr) + : addr_(IPAddressV6(ipV6Addr)) + , family_(AF_INET6) +{ +} + +// Assign from V4 address +IPAddress& IPAddress::operator=(const IPAddressV4& ipv4_addr) { + addr_ = IPAddressV46(ipv4_addr); + family_ = AF_INET; + return *this; +} + +// Assign from V6 address +IPAddress& IPAddress::operator=(const IPAddressV6& ipv6_addr) { + addr_ = IPAddressV46(ipv6_addr); + family_ = AF_INET6; + return *this; +} + +// public +bool IPAddress::inSubnet(StringPiece cidrNetwork) const { + auto subnetInfo = IPAddress::createNetwork(cidrNetwork); + return inSubnet(subnetInfo.first, subnetInfo.second); +} + +// public +bool IPAddress::inSubnet(const IPAddress& subnet, uint8_t cidr) const { + if (bitCount() == subnet.bitCount()) { + if (isV4()) { + return asV4().inSubnet(subnet.asV4(), cidr); + } else { + return asV6().inSubnet(subnet.asV6(), cidr); + } + } + // an IPv4 address can never belong in a IPv6 subnet unless the IPv6 is a 6to4 + // address and vice-versa + if (isV6()) { + const IPAddressV6& v6addr = asV6(); + const IPAddressV4& v4subnet = subnet.asV4(); + if (v6addr.is6To4()) { + return v6addr.getIPv4For6To4().inSubnet(v4subnet, cidr); + } + } else if (subnet.isV6()) { + const IPAddressV6& v6subnet = subnet.asV6(); + const IPAddressV4& v4addr = asV4(); + if (v6subnet.is6To4()) { + return v4addr.inSubnet(v6subnet.getIPv4For6To4(), cidr); + } + } + return false; +} + +// public +bool IPAddress::inSubnetWithMask(const IPAddress& subnet, + ByteRange mask) const { + auto mkByteArray4 = [&]() -> ByteArray4 { + ByteArray4 ba{{0}}; + std::memcpy(ba.data(), mask.begin(), std::min(mask.size(), 4)); + return ba; + }; + + if (bitCount() == subnet.bitCount()) { + if (isV4()) { + return asV4().inSubnetWithMask(subnet.asV4(), mkByteArray4()); + } else { + ByteArray16 ba{{0}}; + std::memcpy(ba.data(), mask.begin(), std::min(mask.size(), 16)); + return asV6().inSubnetWithMask(subnet.asV6(), ba); + } + } + + // an IPv4 address can never belong in a IPv6 subnet unless the IPv6 is a 6to4 + // address and vice-versa + if (isV6()) { + const IPAddressV6& v6addr = asV6(); + const IPAddressV4& v4subnet = subnet.asV4(); + if (v6addr.is6To4()) { + return v6addr.getIPv4For6To4().inSubnetWithMask(v4subnet, mkByteArray4()); + } + } else if (subnet.isV6()) { + const IPAddressV6& v6subnet = subnet.asV6(); + const IPAddressV4& v4addr = asV4(); + if (v6subnet.is6To4()) { + return v4addr.inSubnetWithMask(v6subnet.getIPv4For6To4(), mkByteArray4()); + } + } + return false; +} + +uint8_t IPAddress::getNthMSByte(size_t byteIndex) const { + const auto highestIndex = byteCount() - 1; + if (byteIndex > highestIndex) { + throw std::invalid_argument(to("Byte index must be <= ", + to(highestIndex), " for addresses of type :", + detail::familyNameStr(family()))); + } + if (isV4()) { + return asV4().bytes()[byteIndex]; + } + return asV6().bytes()[byteIndex]; +} + +// public +bool operator==(const IPAddress& addr1, const IPAddress& addr2) { + if (addr1.family() == addr2.family()) { + if (addr1.isV6()) { + return (addr1.asV6() == addr2.asV6()); + } else if (addr1.isV4()) { + return (addr1.asV4() == addr2.asV4()); + } else { + CHECK_EQ(addr1.family(), AF_UNSPEC); + // Two default initialized AF_UNSPEC addresses should be considered equal. + // AF_UNSPEC is the only other value for which an IPAddress can be + // created, in the default constructor case. + return true; + } + } + // addr1 is v4 mapped v6 address, addr2 is v4 + if (addr1.isIPv4Mapped()) { + if (IPAddress::createIPv4(addr1) == addr2.asV4()) { + return true; + } + } + // addr2 is v4 mapped v6 address, addr1 is v4 + if (addr2.isIPv4Mapped()) { + if (IPAddress::createIPv4(addr2) == addr1.asV4()) { + return true; + } + } + // we only compare IPv4 and IPv6 addresses + return false; +} + +bool operator<(const IPAddress& addr1, const IPAddress& addr2) { + if (addr1.family() == addr2.family()) { + if (addr1.isV6()) { + return (addr1.asV6() < addr2.asV6()); + } else if (addr1.isV4()) { + return (addr1.asV4() < addr2.asV4()); + } else { + CHECK_EQ(addr1.family(), AF_UNSPEC); + // Two default initialized AF_UNSPEC addresses can not be less than each + // other. AF_UNSPEC is the only other value for which an IPAddress can be + // created, in the default constructor case. + return false; + } + } + if (addr1.isV6()) { + // means addr2 is v4, convert it to a mapped v6 address and compare + return addr1.asV6() < addr2.asV4().createIPv6(); + } + if (addr2.isV6()) { + // means addr2 is v6, convert addr1 to v4 mapped and compare + return addr1.asV4().createIPv6() < addr2.asV6(); + } + return false; +} + +CIDRNetwork +IPAddress::longestCommonPrefix(const CIDRNetwork& one, const CIDRNetwork& two) { + if (one.first.family() != two.first.family()) { + throw std::invalid_argument(to("Can't compute " + "longest common prefix between addresses of different families. " + "Passed: ", detail::familyNameStr(one.first.family()), " and ", + detail::familyNameStr(two.first.family()))); + } + if (one.first.isV4()) { + auto prefix = IPAddressV4::longestCommonPrefix( + {one.first.asV4(), one.second}, + {two.first.asV4(), two.second}); + return {IPAddress(prefix.first), prefix.second}; + } else if (one.first.isV6()) { + auto prefix = IPAddressV6::longestCommonPrefix( + {one.first.asV6(), one.second}, + {two.first.asV6(), two.second}); + return {IPAddress(prefix.first), prefix.second}; + } else { + throw std::invalid_argument("Unknown address family"); + } + return {IPAddress(0), 0}; +} + +} // folly diff --git a/folly/IPAddress.h b/folly/IPAddress.h new file mode 100644 index 00000000..c5f57aad --- /dev/null +++ b/folly/IPAddress.h @@ -0,0 +1,433 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include // std::pair + +#include + +#include "folly/Format.h" +#include "folly/Range.h" +#include "folly/IPAddressException.h" +#include "folly/IPAddressV4.h" +#include "folly/IPAddressV6.h" +#include "folly/detail/IPAddress.h" + +namespace folly { + +class IPAddress; + +/** + * Pair of IPAddress, netmask + */ +typedef std::pair CIDRNetwork; + +/** + * Provides a unified interface for IP addresses. + * + * @note If you compare 2 IPAddress instances, v4-to-v6-mapped addresses are + * compared as V4 addresses. + * + * @note toLong/fromLong deal in network byte order, use toLongHBO/fromLongHBO + * if working in host byte order. + * + * Example usage: + * @code + * IPAddress v4addr("192.0.2.129"); + * IPAddress v6map("::ffff:192.0.2.129"); + * CHECK(v4addr.inSubnet("192.0.2.0/24") == + * v4addr.inSubnet(IPAddress("192.0.2.0"), 24)); + * CHECK(v4addr.inSubnet("192.0.2.128/30")); + * CHECK(!v4addr.inSubnet("192.0.2.128/32")); + * CHECK(v4addr.asV4().toLong() == 2164392128); + * CHECK(v4addr.asV4().toLongHBO() == 3221226113); + * CHECK(v4addr.isV4()); + * CHECK(v6addr.isV6()); + * CHECK(v4addr == v6map); + * CHECK(v6map.isIPv4Mapped()); + * CHECK(v4addr.asV4() == IPAddress::createIPv4(v6map)); + * CHECK(IPAddress::createIPv6(v4addr) == v6map.asV6()); + * @encode + */ +class IPAddress : boost::totally_ordered { + public: + // return the V4 representation of the address, converting it from V6 to V4 if + // needed. Note that this will throw an IPAddressFormatException if the V6 + // address is not IPv4Mapped. + static IPAddressV4 createIPv4(const IPAddress& addr); + + // return the V6 representation of the address, converting it from V4 to V6 if + // needed. + static IPAddressV6 createIPv6(const IPAddress& addr); + + /** + * Create a network and mask from a CIDR formatted address string. + * @param [in] ipSlashCidr IP/CIDR formatted string to split + * @param [in] defaultCidr default value if no /N specified (if defaultCidr + * is -1, will use /32 for IPv4 and /128 for IPv6) + * @param [in] mask apply mask on the address or not, + * e.g. 192.168.13.46/24 => 192.168.13.0/24 + * @throws IPAddressFormatException if invalid address + * @return pair with IPAddress network and uint8_t mask + */ + static CIDRNetwork createNetwork( + StringPiece ipSlashCidr, int defaultCidr = -1, bool mask = true); + + /** + * Return a string representation of a CIDR block created with createNetwork. + * @param [in] network, pair of address and cidr + * + * @return string representing the netblock + */ + static std::string networkToString(const CIDRNetwork& network) { + return network.first.str() + "/" + std::to_string(network.second); + } + + /** + * Create a new IPAddress instance from the provided binary data + * in network byte order. + * @throws IPAddressFormatException if len is not 4 or 16 + */ + static IPAddress fromBinary(ByteRange bytes); + + /** + * Create an IPAddress from a 32bit long (network byte order). + * @throws IPAddressFormatException + */ + static IPAddress fromLong(uint32_t src); + // Same as above, but host byte order + static IPAddress fromLongHBO(uint32_t src); + + // Given 2 IPAddress,mask pairs extract the longest common IPAddress, + // mask pair + static CIDRNetwork longestCommonPrefix(const CIDRNetwork& one, + const CIDRNetwork& two); + + /** + * Constructs an uninitialized IPAddress. + */ + IPAddress(); + + /** + * Parse an IPAddress from a string representation. + * + * Formats accepted are exactly the same as the ones accepted by inet_pton(), + * using AF_INET6 if the string contains colons, and AF_INET otherwise; + * with the exception that the whole address can optionally be enclosed + * in square brackets. + * + * @throws IPAddressFormatException + */ + explicit IPAddress(StringPiece ip); + + /** + * Create an IPAddress from a sockaddr. + * @throws IPAddressFormatException if nullptr or not AF_INET or AF_INET6 + */ + explicit IPAddress(const sockaddr* addr); + + // Create an IPAddress from a V4 address + /* implicit */ IPAddress(const IPAddressV4 ipV4Addr); + /* implicit */ IPAddress(const in_addr addr); + + // Create an IPAddress from a V6 address + /* implicit */ IPAddress(const IPAddressV6& ipV6Addr); + /* implicit */ IPAddress(const in6_addr& addr); + + // Assign from V4 address + IPAddress& operator=(const IPAddressV4& ipV4Addr); + + // Assign from V6 address + IPAddress& operator=(const IPAddressV6& ipV6Addr); + + /** + * Converts an IPAddress to an IPAddressV4 instance. + * @note This is not some handy convenience wrapper to convert an IPv4 address + * to a mapped IPv6 address. If you want that use + * IPAddress::createIPv6(addr) + * @throws IPAddressFormatException is not a V4 instance + */ + const IPAddressV4& asV4() const { + if (!isV4()) { + auto familyName = detail::familyNameStr(family()); + throw InvalidAddressFamilyException("Can't convert address with family ", + familyName, " to AF_INET address"); + } + return addr_.ipV4Addr; + } + + /** + * Converts an IPAddress to an IPAddressV6 instance. + * @throws InvalidAddressFamilyException is not a V6 instance + */ + const IPAddressV6& asV6() const { + if (!isV6()) { + auto familyName = detail::familyNameStr(family()); + throw InvalidAddressFamilyException("Can't convert address with family ", + familyName, " to AF_INET6 address"); + } + return addr_.ipV6Addr; + } + + // Return sa_family_t of IPAddress + sa_family_t family() const { return family_; } + + // Populate sockaddr_storage with an appropriate value + int toSockaddrStorage(sockaddr_storage *dest, uint16_t port = 0) const { + if (dest == nullptr) { + throw IPAddressFormatException("dest must not be null"); + } + memset(dest, 0, sizeof(sockaddr_storage)); + dest->ss_family = family(); + if (isV4()) { + sockaddr_in *sin = reinterpret_cast(dest); + sin->sin_addr = asV4().toAddr(); + sin->sin_port = port; + return sizeof(*sin); + } else if (isV6()) { + sockaddr_in6 *sin = reinterpret_cast(dest); + sin->sin6_addr = asV6().toAddr(); + sin->sin6_port = port; + return sizeof(*sin); + } else { + throw InvalidAddressFamilyException(family()); + } + } + + /** + * Check if the address is found in the specified CIDR netblock. + * + * This will return false if the specified cidrNet is V4, but the address is + * V6. It will also return false if the specified cidrNet is V6 but the + * address is V4. This method will do the right thing in the case of a v6 + * mapped v4 address. + * + * @note This is slower than the below counterparts. If perf is important use + * one of the two argument variations below. + * @param [in] ipSlashCidr address in "192.168.1.0/24" format + * @throws IPAddressFormatException if no /mask + * @return true if address is part of specified subnet with cidr + */ + bool inSubnet(StringPiece ipSlashCidr) const; + + /** + * Check if an IPAddress belongs to a subnet. + * @param [in] subnet Subnet to check against (e.g. 192.168.1.0) + * @param [in] cidr CIDR for subnet (e.g. 24 for /24) + * @return true if address is part of specified subnet with cidr + */ + bool inSubnet(const IPAddress& subnet, uint8_t cidr) const; + + /** + * Check if an IPAddress belongs to the subnet with the given mask. + * This is the same as inSubnet but the mask is provided instead of looked up + * from the cidr. + * @param [in] subnet Subnet to check against + * @param [in] mask The netmask for the subnet + * @return true if address is part of the specified subnet with mask + */ + bool inSubnetWithMask(const IPAddress& subnet, ByteRange mask) const; + + // @return true if address is a v4 mapped address + bool isIPv4Mapped() const { + return isV6() && asV6().isIPv4Mapped(); + } + + // @return true if this is an IPAddressV4 instance + bool isV4() const { return (family_ == AF_INET); } + + // @return true if this is an IPAddressV6 instance + bool isV6() const { return (family_ == AF_INET6); } + + // @return true if this address is all zeros + bool isZero() const { + return isV4() ? asV4().isZero() + : asV6().isZero(); + } + + // Number of bits in the address representation. + size_t bitCount() const { + return isV4() ? IPAddressV4::bitCount() + : IPAddressV6::bitCount(); + } + // Number of bytes in the address representation. + size_t byteCount() const { + return bitCount() / 8; + } + //get nth most significant bit - 0 indexed + bool getNthMSBit(size_t bitIndex) const { + return detail::getNthMSBitImpl(*this, bitIndex, family()); + } + //get nth most significant byte - 0 indexed + uint8_t getNthMSByte(size_t byteIndex) const; + //get nth bit - 0 indexed + bool getNthLSBit(size_t bitIndex) const { + return getNthMSBit(bitCount() - bitIndex - 1); + } + //get nth byte - 0 indexed + uint8_t getNthLSByte(size_t byteIndex) const { + return getNthMSByte(byteCount() - byteIndex - 1); + } + /** + * Get human-readable string representation of the address. + * + * This prints a string representation of the address, for human consumption + * or logging. The string will take the form of a JSON object that looks like: + * {family:'AF_INET|AF_INET6', addr:'address', hash:long}. + */ + std::string toJson() const { + return isV4() ? asV4().toJson() + : asV6().toJson(); + } + + // Hash of address + std::size_t hash() const { + return isV4() ? asV4().hash() + : asV6().hash(); + } + + // Return true if the address qualifies as localhost. + bool isLoopback() const { + return isV4() ? asV4().isLoopback() + : asV6().isLoopback(); + } + + // Return true if the address qualifies as broadcast. + bool isLinkLocalBroadcast() const { + return isV4() ? asV4().isLinkLocalBroadcast() + : asV6().isLinkLocalBroadcast(); + } + + /** + * Return true if the address is a special purpose address, as per rfc6890 + * (i.e. 0.0.0.0). + * For V6, true if the address is not in one of global scope blocks: + * 2000::/3, ffxe::/16. + */ + bool isNonroutable() const { + return isV4() ? asV4().isNonroutable() + : asV6().isNonroutable(); + } + + /** + * Return true if the address is private, as per rfc1918 and rfc4193 + * (for example, 192.168.xxx.xxx or fc00::/7 addresses) + */ + bool isPrivate() const { + return isV4() ? asV4().isPrivate() + : asV6().isPrivate(); + } + + // Return true if the address is a multicast address. + bool isMulticast() const { + return isV4() ? asV4().isMulticast() + : asV6().isMulticast(); + } + + /** + * Creates IPAddress instance with all but most significant numBits set to 0. + * @param [in] numBits number of bits to mask + * @throws abort if numBits > bitCount() + * @return IPAddress instance with bits set to 0 + */ + IPAddress mask(uint8_t numBits) const { + return isV4() ? IPAddress(std::move(asV4().mask(numBits))) + : IPAddress(std::move(asV6().mask(numBits))); + } + + /** + * Provides a string representation of address. + * @note The string representation is calculated on demand. + * @throws IPAddressFormatException on inet_ntop error + */ + std::string str() const { + return isV4() ? asV4().str() + : asV6().str(); + } + + /** + * Return the fully qualified string representation of the address. + * For V4 addresses this is the same as calling str(). For V6 addresses + * this is the hex representation with : characters inserted every 4 digits. + */ + std::string toFullyQualified() const { + return isV4() ? asV4().toFullyQualified() + : asV6().toFullyQualified(); + } + + // Address version (4 or 6) + uint8_t version() const { + return isV4() ? asV4().version() + : asV6().version(); + } + + /** + * Access to address bytes, in network byte order. + */ + const unsigned char* bytes() const { + return isV4() ? asV4().bytes() : asV6().bytes(); + } + + private: + typedef union IPAddressV46 { + IPAddressV4 ipV4Addr; + IPAddressV6 ipV6Addr; + // default constructor + IPAddressV46() { + std::memset(this, 0, sizeof(IPAddressV46)); + } + explicit IPAddressV46(const IPAddressV4& addr): ipV4Addr(addr) {} + explicit IPAddressV46(const IPAddressV6& addr): ipV6Addr(addr) {} + } IPAddressV46; + IPAddressV46 addr_; + sa_family_t family_; +}; + +// boost::hash uses hash_value() so this allows boost::hash to work +// automatically for IPAddress +std::size_t hash_value(const IPAddress& addr); +std::ostream& operator<<(std::ostream& os, const IPAddress& addr); +// Define toAppend() to allow IPAddress to be used with folly::to +void toAppend(IPAddress addr, std::string* result); +void toAppend(IPAddress addr, fbstring* result); + +/** + * Return true if two addresses are equal. + * + * @note This takes into consideration V4 mapped addresses as well. If one + * address is v4 mapped we compare the v4 addresses. + * + * @return true if the two addresses are equal. + */ +bool operator==(const IPAddress& addr1, const IPAddress& addr2); +// Return true if addr1 < addr2 +bool operator<(const IPAddress& addr1, const IPAddress& addr2); + +} // folly + +namespace std { +template<> +struct hash { + size_t operator()(const folly::IPAddress& addr) const { + return addr.hash(); + } +}; +} // std diff --git a/folly/IPAddressException.h b/folly/IPAddressException.h new file mode 100644 index 00000000..8aa4a9b6 --- /dev/null +++ b/folly/IPAddressException.h @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "folly/Conv.h" +#include "folly/detail/IPAddress.h" + +namespace folly { + +/** + * Exception for invalid IP addresses. + */ +class IPAddressFormatException : public std::exception { + public: + explicit IPAddressFormatException(const std::string& msg) + : msg_(msg) {} + IPAddressFormatException( + const IPAddressFormatException& exception_) = default; + template + explicit IPAddressFormatException(Args&&... args) + : msg_(to(std::forward(args)...)) {} + + virtual ~IPAddressFormatException() noexcept {} + virtual const char *what(void) const noexcept { + return msg_.c_str(); + } + + private: + const std::string msg_; +}; + +class InvalidAddressFamilyException : public IPAddressFormatException { + public: + explicit InvalidAddressFamilyException(const std::string& msg) + : IPAddressFormatException(msg) {} + InvalidAddressFamilyException( + const InvalidAddressFamilyException& ex) = default; + explicit InvalidAddressFamilyException(sa_family_t family) + : IPAddressFormatException("Address family " + + detail::familyNameStr(family) + + " is not AF_INET or AF_INET6") {} + template + explicit InvalidAddressFamilyException(Args&&... args) + : IPAddressFormatException(std::forward(args)...) {} +}; + +} // folly diff --git a/folly/IPAddressV4.cpp b/folly/IPAddressV4.cpp new file mode 100644 index 00000000..e42416b9 --- /dev/null +++ b/folly/IPAddressV4.cpp @@ -0,0 +1,276 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "IPAddressV4.h" + +#include +#include + +#include "folly/Format.h" +#include "folly/IPAddress.h" +#include "folly/IPAddressV6.h" + +using std::ostream; +using std::string; + +namespace folly { + +// free functions +size_t hash_value(const IPAddressV4& addr) { + return addr.hash(); +} +ostream& operator<<(ostream& os, const IPAddressV4& addr) { + os << addr.str(); + return os; +} +void toAppend(IPAddressV4 addr, string* result) { + result->append(addr.str()); +} +void toAppend(IPAddressV4 addr, fbstring* result) { + result->append(addr.str()); +} + + +// public static +IPAddressV4 IPAddressV4::fromLong(uint32_t src) { + in_addr addr; + addr.s_addr = src; + return IPAddressV4(addr); +} + +IPAddressV4 IPAddressV4::fromLongHBO(uint32_t src) { + in_addr addr; + addr.s_addr = htonl(src); + return IPAddressV4(addr); +} + +// static public +uint32_t IPAddressV4::toLong(StringPiece ip) { + auto str = ip.str(); + in_addr addr; + if (inet_pton(AF_INET, str.c_str(), &addr) != 1) { + throw IPAddressFormatException("Can't convert invalid IP '", ip, "' ", + "to long"); + } + return addr.s_addr; +} + +// static public +uint32_t IPAddressV4::toLongHBO(StringPiece ip) { + return ntohl(IPAddressV4::toLong(ip)); +} + +// public default constructor +IPAddressV4::IPAddressV4() { +} + +// ByteArray4 constructor +IPAddressV4::IPAddressV4(const ByteArray4& src) + : addr_(src) +{ +} + +// public string constructor +IPAddressV4::IPAddressV4(StringPiece addr) + : addr_() +{ + auto ip = addr.str(); + if (inet_pton(AF_INET, ip.c_str(), &addr_.inAddr_) != 1) { + throw IPAddressFormatException("Invalid IPv4 address '", addr, "'"); + } +} + +// in_addr constructor +IPAddressV4::IPAddressV4(const in_addr src) + : addr_(src) +{ +} + +// public +void IPAddressV4::setFromBinary(ByteRange bytes) { + if (bytes.size() != 4) { + throw IPAddressFormatException("Invalid IPv4 binary data: length must " + "be 4 bytes, got ", bytes.size()); + } + memcpy(&addr_.inAddr_.s_addr, bytes.data(), sizeof(in_addr)); +} + +// public +IPAddressV6 IPAddressV4::createIPv6() const { + ByteArray16 ba{{0}}; + ba[10] = 0xff; + ba[11] = 0xff; + std::memcpy(&ba[12], bytes(), 4); + return IPAddressV6(ba); +} + +// public +string IPAddressV4::toJson() const { + return format( + "{{family:'AF_INET', addr:'{}', hash:{}}}", str(), hash()).str(); +} + +// public +bool IPAddressV4::inSubnet(StringPiece cidrNetwork) const { + auto subnetInfo = IPAddress::createNetwork(cidrNetwork); + auto addr = subnetInfo.first; + if (!addr.isV4()) { + throw IPAddressFormatException("Address '", addr.toJson(), "' ", + "is not a V4 address"); + } + return inSubnetWithMask(addr.asV4(), fetchMask(subnetInfo.second)); +} + +// public +bool IPAddressV4::inSubnetWithMask(const IPAddressV4& subnet, + const ByteArray4 cidrMask) const { + const ByteArray4 mask = detail::Bytes::mask(toByteArray(), cidrMask); + const ByteArray4 subMask = detail::Bytes::mask(subnet.toByteArray(), + cidrMask); + return (mask == subMask); +} + +// public +bool IPAddressV4::isNonroutable() const { + auto ip = toLongHBO(); + return isPrivate() || + (ip <= 0x00FFFFFF) || // 0.0.0.0-0.255.255.255 + (ip >= 0xC0000000 && ip <= 0xC00000FF) || // 192.0.0.0-192.0.0.255 + (ip >= 0xC0000200 && ip <= 0xC00002FF) || // 192.0.2.0-192.0.2.255 + (ip >= 0xC6120000 && ip <= 0xC613FFFF) || // 198.18.0.0-198.19.255.255 + (ip >= 0xC6336400 && ip <= 0xC63364FF) || // 198.51.100.0-198.51.100.255 + (ip >= 0xCB007100 && ip <= 0xCB0071FF) || // 203.0.113.0-203.0.113.255 + (ip >= 0xE0000000 && ip <= 0xFFFFFFFF); // 224.0.0.0-255.255.255.255 +} + +// public +bool IPAddressV4::isPrivate() const { + auto ip = toLongHBO(); + return + (ip >= 0x0A000000 && ip <= 0x0AFFFFFF) || // 10.0.0.0-10.255.255.255 + (ip >= 0x7F000000 && ip <= 0x7FFFFFFF) || // 127.0.0.0-127.255.255.255 + (ip >= 0xA9FE0000 && ip <= 0xA9FEFFFF) || // 169.254.0.0-169.254.255.255 + (ip >= 0xAC100000 && ip <= 0xAC1FFFFF) || // 172.16.0.0-172.31.255.255 + (ip >= 0xC0A80000 && ip <= 0xC0A8FFFF); // 192.168.0.0-192.168.255.255 +} + +// public +bool IPAddressV4::isMulticast() const { + return (toLongHBO() & 0xf0000000) == 0xe0000000; +} + +// public +IPAddressV4 IPAddressV4::mask(size_t numBits) const { + static const auto bits = bitCount(); + if (numBits > bits) { + throw IPAddressFormatException("numBits(", numBits, + ") > bitsCount(", bits, ")"); + } + + ByteArray4 ba = detail::Bytes::mask(fetchMask(numBits), addr_.bytes_); + return IPAddressV4(ba); +} + +// public +// Taken from TSocketAddress::getAddressStrIPv4Fast +string IPAddressV4::str() const { + char buf[INET_ADDRSTRLEN] = {0}; + const uint8_t* ip = addr_.bytes_.data(); + int pos = 0; + for (int k = 0; k < 4; ++k) { + uint8_t num = ip[k]; + + if (num >= 200) { + buf[pos++] = '2'; + num -= 200; + } else if (num >= 100) { + buf[pos++] = '1'; + num -= 100; + } + + // num < 100 + if (ip[k] >= 10) { + buf[pos++] = '0' + num / 10; + buf[pos++] = '0' + num % 10; + } else { + buf[pos++] = '0' + num; + } + + buf[pos++] = '.'; + } + buf[pos-1] = '\0'; + string ipAddr(buf); + return std::move(ipAddr); +} + +// public +uint8_t IPAddressV4::getNthMSByte(size_t byteIndex) const { + const auto highestIndex = byteCount() - 1; + if (byteIndex > highestIndex) { + throw std::invalid_argument(to("Byte index must be <= ", + to(highestIndex), " for addresses of type :", + detail::familyNameStr(AF_INET))); + } + return bytes()[byteIndex]; +} +// protected +const ByteArray4 IPAddressV4::fetchMask(size_t numBits) { + static const uint8_t bits = bitCount(); + if (numBits > bits) { + throw IPAddressFormatException("IPv4 addresses are 32 bits"); + } + // masks_ is backed by an array so is zero indexed + return masks_[numBits]; +} + +// static private +const std::array IPAddressV4::masks_ = {{ + {{0x00, 0x00, 0x00, 0x00}}, + {{0x80, 0x00, 0x00, 0x00}}, + {{0xc0, 0x00, 0x00, 0x00}}, + {{0xe0, 0x00, 0x00, 0x00}}, + {{0xf0, 0x00, 0x00, 0x00}}, + {{0xf8, 0x00, 0x00, 0x00}}, + {{0xfc, 0x00, 0x00, 0x00}}, + {{0xfe, 0x00, 0x00, 0x00}}, + {{0xff, 0x00, 0x00, 0x00}}, + {{0xff, 0x80, 0x00, 0x00}}, + {{0xff, 0xc0, 0x00, 0x00}}, + {{0xff, 0xe0, 0x00, 0x00}}, + {{0xff, 0xf0, 0x00, 0x00}}, + {{0xff, 0xf8, 0x00, 0x00}}, + {{0xff, 0xfc, 0x00, 0x00}}, + {{0xff, 0xfe, 0x00, 0x00}}, + {{0xff, 0xff, 0x00, 0x00}}, + {{0xff, 0xff, 0x80, 0x00}}, + {{0xff, 0xff, 0xc0, 0x00}}, + {{0xff, 0xff, 0xe0, 0x00}}, + {{0xff, 0xff, 0xf0, 0x00}}, + {{0xff, 0xff, 0xf8, 0x00}}, + {{0xff, 0xff, 0xfc, 0x00}}, + {{0xff, 0xff, 0xfe, 0x00}}, + {{0xff, 0xff, 0xff, 0x00}}, + {{0xff, 0xff, 0xff, 0x80}}, + {{0xff, 0xff, 0xff, 0xc0}}, + {{0xff, 0xff, 0xff, 0xe0}}, + {{0xff, 0xff, 0xff, 0xf0}}, + {{0xff, 0xff, 0xff, 0xf8}}, + {{0xff, 0xff, 0xff, 0xfc}}, + {{0xff, 0xff, 0xff, 0xfe}}, + {{0xff, 0xff, 0xff, 0xff}} +}}; + +} // folly diff --git a/folly/IPAddressV4.h b/folly/IPAddressV4.h new file mode 100644 index 00000000..98198a3d --- /dev/null +++ b/folly/IPAddressV4.h @@ -0,0 +1,277 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include "folly/Hash.h" +#include "folly/Range.h" +#include "folly/detail/IPAddress.h" + +namespace folly { + +class IPAddress; +class IPAddressV4; +class IPAddressV6; + +/** + * Pair of IPAddressV4, netmask + */ +typedef std::pair CIDRNetworkV4; + +/** + * Specialization for IPv4 addresses + */ +typedef std::array ByteArray4; + +/** + * IPv4 variation of IPAddress. + * + * Added methods: toLong, toLongHBO and createIPv6 + * + * @note toLong/fromLong deal in network byte order, use toLongHBO/fromLongHBO + * if working in host byte order. + * + * @see IPAddress + */ +class IPAddressV4 : boost::totally_ordered { + public: + // create an IPAddressV4 instance from a uint32_t (network byte order) + static IPAddressV4 fromLong(uint32_t src); + // same as above but host byte order + static IPAddressV4 fromLongHBO(uint32_t src); + + /** + * Create a new IPAddress instance from the provided binary data. + * @throws IPAddressFormatException if the input length is not 4 bytes. + */ + static IPAddressV4 fromBinary(ByteRange bytes) { + IPAddressV4 addr; + addr.setFromBinary(bytes); + return addr; + } + + /** + * Convert a IPv4 address string to a long in network byte order. + * @param [in] ip the address to convert + * @return the long representation of the address + */ + static uint32_t toLong(StringPiece ip); + // Same as above, but in host byte order. + // This is slightly slower than toLong. + static uint32_t toLongHBO(StringPiece ip); + + /** + * Default constructor for IPAddressV4. + * + * The address value will be 0.0.0.0 + */ + IPAddressV4(); + + // Create an IPAddressV4 from a string + // @throws IPAddressFormatException + explicit IPAddressV4(StringPiece ip); + + // ByteArray4 constructor + explicit IPAddressV4(const ByteArray4& src); + + // in_addr constructor + explicit IPAddressV4(const in_addr src); + + // Return the V6 mapped representation of the address. + IPAddressV6 createIPv6() const; + + // Return the long (network byte order) representation of the address. + uint32_t toLong() const { + return toAddr().s_addr; + } + + // Return the long (host byte order) representation of the address. + // This is slightly slower than toLong. + uint32_t toLongHBO() const { + return ntohl(toLong()); + } + + /** + * @see IPAddress#bitCount + * @returns 32 + */ + static size_t bitCount() { return 32; } + + /** + * @See IPAddress#toJson + */ + std::string toJson() const; + + size_t hash() const { + static const uint32_t seed = AF_INET; + uint32_t hashed = hash::fnv32_buf(&addr_, 4); + return hash::hash_combine(seed, hashed); + } + + // @see IPAddress#inSubnet + // @throws IPAddressFormatException if string doesn't contain a V4 address + bool inSubnet(StringPiece cidrNetwork) const; + + // return true if address is in subnet + bool inSubnet(const IPAddressV4& subnet, uint8_t cidr) const { + return inSubnetWithMask(subnet, fetchMask(cidr)); + } + bool inSubnetWithMask(const IPAddressV4& subnet, const ByteArray4 mask) const; + + // @see IPAddress#isLoopback + bool isLoopback() const { + return (INADDR_LOOPBACK == toLongHBO()); + } + + // @see IPAddress#isNonroutable + bool isNonroutable() const; + + // @see IPAddress#isPrivate + bool isPrivate() const; + + // @see IPAddress#isMulticast + bool isMulticast() const; + + // @see IPAddress#isZero + bool isZero() const { + return detail::Bytes::isZero(bytes(), 4); + } + + bool isLinkLocalBroadcast() const { + return (INADDR_BROADCAST == toLongHBO()); + } + + // @see IPAddress#mask + IPAddressV4 mask(size_t numBits) const; + + // @see IPAddress#str + std::string str() const; + + // return underlying in_addr structure + in_addr toAddr() const { return addr_.inAddr_; } + + sockaddr_in toSockAddr() const { + sockaddr_in addr; + memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + memcpy(&addr.sin_addr, &addr_.inAddr_, sizeof(in_addr)); + return addr; + } + + ByteArray4 toByteArray() const { + ByteArray4 ba{{0}}; + std::memcpy(ba.data(), bytes(), 4); + return std::move(ba); + } + + // @see IPAddress#toFullyQualified + std::string toFullyQualified() const { return str(); } + + // @see IPAddress#version + size_t version() const { return 4; } + + /** + * Return the mask associated with the given number of bits. + * If for instance numBits was 24 (e.g. /24) then the V4 mask returned should + * be {0xff, 0xff, 0xff, 0x00}. + * @param [in] numBits bitmask to retrieve + * @throws abort if numBits == 0 or numBits > bitCount() + * @return mask associated with numBits + */ + static const ByteArray4 fetchMask(size_t numBits); + + // Given 2 IPAddressV4,mask pairs extract the longest common IPAddress, + // mask pair + static CIDRNetworkV4 longestCommonPrefix( + const CIDRNetworkV4& one, const CIDRNetworkV4& two) { + auto prefix = + detail::Bytes::longestCommonPrefix(one.first.addr_.bytes_, one.second, + two.first.addr_.bytes_, two.second); + return {IPAddressV4(prefix.first), prefix.second}; + } + // Number of bytes in the address representation. + static size_t byteCount() { return 4; } + //get nth most significant bit - 0 indexed + bool getNthMSBit(size_t bitIndex) const { + return detail::getNthMSBitImpl(*this, bitIndex, AF_INET); + } + //get nth most significant byte - 0 indexed + uint8_t getNthMSByte(size_t byteIndex) const; + //get nth bit - 0 indexed + bool getNthLSBit(size_t bitIndex) const { + return getNthMSBit(bitCount() - bitIndex - 1); + } + //get nth byte - 0 indexed + uint8_t getNthLSByte(size_t byteIndex) const { + return getNthMSByte(byteCount() - byteIndex - 1); + } + + const unsigned char* bytes() const { return addr_.bytes_.data(); } + + private: + union AddressStorage { + in_addr inAddr_; + ByteArray4 bytes_; + AddressStorage() { + std::memset(this, 0, sizeof(AddressStorage)); + } + explicit AddressStorage(const ByteArray4 bytes): bytes_(bytes) {} + explicit AddressStorage(const in_addr addr): inAddr_(addr) {} + } addr_; + + static const std::array masks_; + + /** + * Set the current IPAddressV4 object to have the address specified by bytes. + * @throws IPAddressFormatException if bytes.size() is not 4. + */ + void setFromBinary(ByteRange bytes); +}; + +// boost::hash uses hash_value() so this allows boost::hash to work +// automatically for IPAddressV4 +size_t hash_value(const IPAddressV4& addr); +std::ostream& operator<<(std::ostream& os, const IPAddressV4& addr); +// Define toAppend() to allow IPAddressV4 to be used with to +void toAppend(IPAddressV4 addr, std::string* result); +void toAppend(IPAddressV4 addr, fbstring* result); + +/** + * Return true if two addresses are equal. + */ +inline bool operator==(const IPAddressV4& addr1, const IPAddressV4& addr2) { + return (addr1.toLong() == addr2.toLong()); +} +// Return true if addr1 < addr2 +inline bool operator<(const IPAddressV4& addr1, const IPAddressV4& addr2) { + return (addr1.toLongHBO() < addr2.toLongHBO()); +} + +} // folly + +namespace std { +template<> +struct hash { + size_t operator()(const folly::IPAddressV4 addr) const { + return addr.hash(); + } +}; +} // std diff --git a/folly/IPAddressV6.cpp b/folly/IPAddressV6.cpp new file mode 100644 index 00000000..65101d44 --- /dev/null +++ b/folly/IPAddressV6.cpp @@ -0,0 +1,880 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "IPAddressV6.h" + +#include +#include + +#include "folly/Format.h" +#include "folly/IPAddress.h" +#include "folly/IPAddressV4.h" +#include "folly/MacAddress.h" + +using std::ostream; +using std::string; + +namespace folly { + +// public static const +const uint32_t IPAddressV6::PREFIX_TEREDO = 0x20010000; +const uint32_t IPAddressV6::PREFIX_6TO4 = 0x2002; + +// free functions +size_t hash_value(const IPAddressV6& addr) { + return addr.hash(); +} +ostream& operator<<(ostream& os, const IPAddressV6& addr) { + os << addr.str(); + return os; +} +void toAppend(IPAddressV6 addr, string* result) { + result->append(addr.str()); +} +void toAppend(IPAddressV6 addr, fbstring* result) { + result->append(addr.str()); +} + +// public default constructor +IPAddressV6::IPAddressV6() { +} + +// public string constructor +IPAddressV6::IPAddressV6(StringPiece addr) { + auto ip = addr.str(); + + // Allow addresses surrounded in brackets + if (ip.size() < 2) { + throw IPAddressFormatException("Invalid IPv6 address '", ip, + "': address too short"); + } + if (ip.front() == '[' && ip.back() == ']') { + ip = ip.substr(1, ip.size() - 2); + } + + if (inet_pton(AF_INET6, ip.c_str(), &addr_.in6Addr_) != 1) { + throw IPAddressFormatException("Invalid IPv6 address '", ip, "'"); + } +} + +// in6_addr constructor +IPAddressV6::IPAddressV6(const in6_addr& src) + : addr_(src) +{ +} + +// ByteArray16 constructor +IPAddressV6::IPAddressV6(const ByteArray16& src) + : addr_(src) +{ +} + +// link-local constructor +IPAddressV6::IPAddressV6(LinkLocalTag, MacAddress mac) + : addr_(mac) { +} + +IPAddressV6::AddressStorage::AddressStorage(MacAddress mac) { + // The link-local address uses modified EUI-64 format, + // See RFC 4291 sections 2.5.1, 2.5.6, and Appendix A + const auto* macBytes = mac.bytes(); + memcpy(&bytes_.front(), "\xfe\x80\x00\x00\x00\x00\x00\x00", 8); + bytes_[8] = macBytes[0] ^ 0x02; + bytes_[9] = macBytes[1]; + bytes_[10] = macBytes[2]; + bytes_[11] = 0xff; + bytes_[12] = 0xfe; + bytes_[13] = macBytes[3]; + bytes_[14] = macBytes[4]; + bytes_[15] = macBytes[5]; +} + +void IPAddressV6::setFromBinary(ByteRange bytes) { + if (bytes.size() != 16) { + throw IPAddressFormatException("Invalid IPv6 binary data: length must " + "be 16 bytes, got ", bytes.size()); + } + memcpy(&addr_.in6Addr_.s6_addr, bytes.data(), sizeof(in6_addr)); +} + +// public +IPAddressV4 IPAddressV6::createIPv4() const { + if (!isIPv4Mapped()) { + throw IPAddressFormatException("addr is not v4-to-v6-mapped"); + } + const unsigned char* by = bytes(); + return IPAddressV4(detail::Bytes::mkAddress4(&by[12])); +} + +// convert two uint8_t bytes into a uint16_t as hibyte.lobyte +static inline uint16_t unpack(uint8_t lobyte, uint8_t hibyte) { + return ((uint16_t)hibyte << 8) | (uint16_t)lobyte; +} + +// given a src string, unpack count*2 bytes into dest +// dest must have as much storage as count +static inline void unpackInto(const unsigned char* src, + uint16_t* dest, + size_t count) { + for (int i = 0, hi = 1, lo = 0; i < count; i++) { + dest[i] = unpack(src[hi], src[lo]); + hi += 2; + lo += 2; + } +} + +// public +IPAddressV4 IPAddressV6::getIPv4For6To4() const { + if (!is6To4()) { + throw IPAddressV6::TypeError(format( + "Invalid IP '{}': not a 6to4 address", str()).str()); + } + // convert 16x8 bytes into first 4x16 bytes + uint16_t ints[4] = {0,0,0,0}; + unpackInto(bytes(), ints, 4); + // repack into 4x8 + union { + unsigned char bytes[4]; + in_addr addr; + } ipv4; + ipv4.bytes[0] = (uint8_t)((ints[1] & 0xFF00) >> 8); + ipv4.bytes[1] = (uint8_t)(ints[1] & 0x00FF); + ipv4.bytes[2] = (uint8_t)((ints[2] & 0xFF00) >> 8); + ipv4.bytes[3] = (uint8_t)(ints[2] & 0x00FF); + return IPAddressV4(ipv4.addr); +} + +// public +bool IPAddressV6::isIPv4Mapped() const { + // v4 mapped addresses have their first 10 bytes set to 0, the next 2 bytes + // set to 255 (0xff); + const unsigned char* by = bytes(); + + // check if first 10 bytes are 0 + for (int i = 0; i < 10; i++) { + if (by[i] != 0x00) { + return false; + } + } + // check if bytes 11 and 12 are 255 + if (by[10] == 0xff && by[11] == 0xff) { + return true; + } + return false; +} + +// public +IPAddressV6::Type IPAddressV6::type() const { + // convert 16x8 bytes into first 2x16 bytes + uint16_t ints[2] = {0,0}; + unpackInto(bytes(), ints, 2); + + if ((((uint32_t)ints[0] << 16) | ints[1]) == IPAddressV6::PREFIX_TEREDO) { + return Type::TEREDO; + } + + if ((uint32_t)ints[0] == IPAddressV6::PREFIX_6TO4) { + return Type::T6TO4; + } + + return Type::NORMAL; +} + +// public +string IPAddressV6::toJson() const { + return format( + "{{family:'AF_INET6', addr:'{}', hash:{}}}", str(), hash()).str(); +} + +// public +size_t IPAddressV6::hash() const { + if (isIPv4Mapped()) { + /* An IPAddress containing this object would be equal (i.e. operator==) + to an IPAddress containing the corresponding IPv4. + So we must make sure that the hash values are the same as well */ + return IPAddress::createIPv4(*this).hash(); + } + + static const uint64_t seed = AF_INET6; + uint64_t hash1 = 0, hash2 = 0; + hash::SpookyHashV2::Hash128(&addr_, 16, &hash1, &hash2); + return hash::hash_combine(seed, hash1, hash2); +} + +// public +bool IPAddressV6::inSubnet(StringPiece cidrNetwork) const { + auto subnetInfo = IPAddress::createNetwork(cidrNetwork); + auto addr = subnetInfo.first; + if (!addr.isV6()) { + throw IPAddressFormatException("Address '", addr.toJson(), "' ", + "is not a V6 address"); + } + return inSubnetWithMask(addr.asV6(), fetchMask(subnetInfo.second)); +} + +// public +bool IPAddressV6::inSubnetWithMask(const IPAddressV6& subnet, + const ByteArray16& cidrMask) const { + const ByteArray16 mask = detail::Bytes::mask(toByteArray(), cidrMask); + const ByteArray16 subMask = detail::Bytes::mask(subnet.toByteArray(), + cidrMask); + return (mask == subMask); +} + +// public +bool IPAddressV6::isLoopback() const { + const unsigned char* by = bytes(); + for (int i = 0; i < 15; i++) { + if (by[i] != 0x00) { + return false; + } + } + return (by[15] == 0x01); +} + +bool IPAddressV6::isRoutable() const { + return + // 2000::/3 is the only assigned global unicast block + inBinarySubnet({{0x20, 0x00}}, 3) || + // ffxe::/16 are global scope multicast addresses, + // which are eligible to be routed over the internet + (isMulticast() && getMulticastScope() == 0xe); +} + +bool IPAddressV6::isLinkLocalBroadcast() const { + static const IPAddressV6 kLinkLocalBroadcast("ff02::1"); + return *this == kLinkLocalBroadcast; +} + +// public +bool IPAddressV6::isPrivate() const { + return isLoopback() || inBinarySubnet({{0xfc, 0x00}}, 7); +} + +bool IPAddressV6::isLinkLocal() const { + return inBinarySubnet({{0xfe, 0x80}}, 10); +} + +bool IPAddressV6::isMulticast() const { + return addr_.bytes_[0] == 0xff; +} + +uint8_t IPAddressV6::getMulticastFlags() const { + DCHECK(isMulticast()); + return ((addr_.bytes_[1] >> 4) & 0xf); +} + +uint8_t IPAddressV6::getMulticastScope() const { + DCHECK(isMulticast()); + return (addr_.bytes_[1] & 0xf); +} + +IPAddressV6 IPAddressV6::getSolicitedNodeAddress() const { + // Solicted node addresses must be constructed from unicast (or anycast) + // addresses + DCHECK(!isMulticast()); + + uint8_t bytes[16] = { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00 }; + bytes[13] = addr_.bytes_[13]; + bytes[14] = addr_.bytes_[14]; + bytes[15] = addr_.bytes_[15]; + return IPAddressV6::fromBinary(ByteRange(bytes, 16)); +} + +// public +IPAddressV6 IPAddressV6::mask(size_t numBits) const { + static const auto bits = bitCount(); + if (numBits > bits) { + throw IPAddressFormatException("numBits(", numBits, ") > bitCount(", + bits, ")"); + } + ByteArray16 ba = detail::Bytes::mask(fetchMask(numBits), addr_.bytes_); + return IPAddressV6(ba); +} + +// public +string IPAddressV6::str() const { + char buffer[INET6_ADDRSTRLEN] = {0}; + if (!inet_ntop(AF_INET6, &addr_.in6Addr_, buffer, INET6_ADDRSTRLEN)) { + throw IPAddressFormatException("Invalid address with hex ", + "'", detail::Bytes::toHex(bytes(), 16), "'"); + } + string ip(buffer); + return std::move(ip); +} + +// public +string IPAddressV6::toFullyQualified() const { + auto asHex = detail::Bytes::toHex(bytes(), 16); + uint8_t chunks = asHex.size() / 4; + for (int chunk = 1; chunk < chunks; chunk++) { + // position changes as new characters are inserted + int pos = (chunk*4) + (chunk - 1); + asHex.insert(pos, ":"); + } + return asHex; +} + +// public +uint8_t IPAddressV6::getNthMSByte(size_t byteIndex) const { + const auto highestIndex = byteCount() - 1; + if (byteIndex > highestIndex) { + throw std::invalid_argument(to("Byte index must be <= ", + to(highestIndex), " for addresses of type :", + detail::familyNameStr(AF_INET6))); + } + return bytes()[byteIndex]; +} + +// protected +const ByteArray16 IPAddressV6::fetchMask(size_t numBits) { + static const uint8_t bits = bitCount(); + if (numBits > bits) { + throw IPAddressFormatException("IPv6 addresses are 128 bits."); + } + // masks_ is backed by an array so is zero indexed + return masks_[numBits]; +} + +// protected +bool IPAddressV6::inBinarySubnet(const std::array addr, + size_t numBits) const { + const unsigned char* subbytes = mask(numBits).bytes(); + return (std::memcmp(addr.data(), subbytes, 2) == 0); +} + +// static private +const std::array IPAddressV6::masks_ = {{ +/* /0 */ {{ 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /1 */ {{ 0x80,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /2 */ {{ 0xc0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /3 */ {{ 0xe0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /4 */ {{ 0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /5 */ {{ 0xf8,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /6 */ {{ 0xfc,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /7 */ {{ 0xfe,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /8 */ {{ 0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /9 */ {{ 0xff,0x80,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /10 */ {{ 0xff,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /11 */ {{ 0xff,0xe0,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /12 */ {{ 0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /13 */ {{ 0xff,0xf8,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /14 */ {{ 0xff,0xfc,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /15 */ {{ 0xff,0xfe,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /16 */ {{ 0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /17 */ {{ 0xff,0xff,0x80,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /18 */ {{ 0xff,0xff,0xc0,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /19 */ {{ 0xff,0xff,0xe0,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /20 */ {{ 0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /21 */ {{ 0xff,0xff,0xf8,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /22 */ {{ 0xff,0xff,0xfc,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /23 */ {{ 0xff,0xff,0xfe,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /24 */ {{ 0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /25 */ {{ 0xff,0xff,0xff,0x80, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /26 */ {{ 0xff,0xff,0xff,0xc0, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /27 */ {{ 0xff,0xff,0xff,0xe0, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /28 */ {{ 0xff,0xff,0xff,0xf0, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /29 */ {{ 0xff,0xff,0xff,0xf8, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /30 */ {{ 0xff,0xff,0xff,0xfc, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /31 */ {{ 0xff,0xff,0xff,0xfe, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /32 */ {{ 0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /33 */ {{ 0xff,0xff,0xff,0xff, + 0x80,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /34 */ {{ 0xff,0xff,0xff,0xff, + 0xc0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /35 */ {{ 0xff,0xff,0xff,0xff, + 0xe0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /36 */ {{ 0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /37 */ {{ 0xff,0xff,0xff,0xff, + 0xf8,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /38 */ {{ 0xff,0xff,0xff,0xff, + 0xfc,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /39 */ {{ 0xff,0xff,0xff,0xff, + 0xfe,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /40 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /41 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0x80,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /42 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /43 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xe0,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /44 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /45 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xf8,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /46 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xfc,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /47 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xfe,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /48 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /49 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0x80,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /50 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xc0,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /51 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xe0,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /52 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /53 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xf8,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /54 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xfc,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /55 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xfe,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /56 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /57 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x80, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /58 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xc0, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /59 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xe0, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /60 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /61 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf8, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /62 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xfc, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /63 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xfe, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /64 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /65 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0x80,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /66 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xc0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /67 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xe0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /68 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /69 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xf8,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /70 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xfc,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /71 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xfe,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /72 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /73 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0x80,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /74 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /75 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xe0,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /76 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /77 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xf8,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /78 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xfc,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /79 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xfe,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /80 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /81 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0x80,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /82 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xc0,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /83 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xe0,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /84 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /85 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xf8,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /86 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xfc,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /87 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xfe,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /88 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00 }}, +/* /89 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x80, + 0x00,0x00,0x00,0x00 }}, +/* /90 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xc0, + 0x00,0x00,0x00,0x00 }}, +/* /91 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xe0, + 0x00,0x00,0x00,0x00 }}, +/* /92 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0, + 0x00,0x00,0x00,0x00 }}, +/* /93 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf8, + 0x00,0x00,0x00,0x00 }}, +/* /94 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xfc, + 0x00,0x00,0x00,0x00 }}, +/* /95 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xfe, + 0x00,0x00,0x00,0x00 }}, +/* /96 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00 }}, +/* /97 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0x80,0x00,0x00,0x00 }}, +/* /98 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xc0,0x00,0x00,0x00 }}, +/* /99 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xe0,0x00,0x00,0x00 }}, +/* /100 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00 }}, +/* /101 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xf8,0x00,0x00,0x00 }}, +/* /102 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xfc,0x00,0x00,0x00 }}, +/* /103 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xfe,0x00,0x00,0x00 }}, +/* /104 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00 }}, +/* /105 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0x80,0x00,0x00 }}, +/* /106 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xc0,0x00,0x00 }}, +/* /107 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xe0,0x00,0x00 }}, +/* /108 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xf0,0x00,0x00 }}, +/* /109 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xf8,0x00,0x00 }}, +/* /110 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xfc,0x00,0x00 }}, +/* /111 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xfe,0x00,0x00 }}, +/* /112 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00 }}, +/* /113 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0x80,0x00 }}, +/* /114 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xc0,0x00 }}, +/* /115 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xe0,0x00 }}, +/* /116 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x00 }}, +/* /117 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xf8,0x00 }}, +/* /118 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xfc,0x00 }}, +/* /119 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xfe,0x00 }}, +/* /120 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00 }}, +/* /121 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0x80 }}, +/* /122 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xc0 }}, +/* /123 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xe0 }}, +/* /124 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0 }}, +/* /125 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf8 }}, +/* /126 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xfc }}, +/* /127 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xfe }}, +/* /128 */ {{ 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff }}, +}}; + +} // folly diff --git a/folly/IPAddressV6.h b/folly/IPAddressV6.h new file mode 100644 index 00000000..134d6de9 --- /dev/null +++ b/folly/IPAddressV6.h @@ -0,0 +1,334 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include "folly/Hash.h" +#include "folly/Range.h" +#include "folly/detail/IPAddress.h" + +namespace folly { + +class IPAddress; +class IPAddressV4; +class IPAddressV6; +class MacAddress; + +/** + * Pair of IPAddressV6, netmask + */ +typedef std::pair CIDRNetworkV6; + +/** + * Specialization for IPv6 addresses + */ +typedef std::array ByteArray16; + +/** + * IPv6 variation of IPAddress. + * + * Added methods: createIPv4, getIPv4For6To4, is6To4, + * isTeredo, isIPv4Mapped, tryCreateIPv4, type + * + * @see IPAddress + */ +class IPAddressV6 : boost::totally_ordered { + public: + // V6 Address Type + enum Type { + TEREDO, T6TO4, NORMAL, + }; + // A constructor parameter to indicate that we should create a link-local + // IPAddressV6. + enum LinkLocalTag { + LINK_LOCAL, + }; + // Thrown when a type assertion fails + typedef std::runtime_error TypeError; + + // Binary prefix for teredo networks + static const uint32_t PREFIX_TEREDO; + // Binary prefix for 6to4 networks + static const uint32_t PREFIX_6TO4; + + /** + * Create a new IPAddress instance from the provided binary data. + * @throws IPAddressFormatException if the input length is not 16 bytes. + */ + static IPAddressV6 fromBinary(ByteRange bytes) { + IPAddressV6 addr; + addr.setFromBinary(bytes); + return addr; + } + + /** + * Default constructor for IPAddressV6. + * + * The address value will be ::0 + */ + IPAddressV6(); + + // Create an IPAddressV6 from a string + // @throws IPAddressFormatException + explicit IPAddressV6(StringPiece ip); + + // ByteArray16 constructor + explicit IPAddressV6(const ByteArray16& src); + + // in6_addr constructor + explicit IPAddressV6(const in6_addr& src); + + /** + * Create a link-local IPAddressV6 from the specified ethernet MAC address. + */ + IPAddressV6(LinkLocalTag tag, MacAddress mac); + + // return the mapped V4 address + // @throws IPAddressFormatException if !isIPv4Mapped + IPAddressV4 createIPv4() const; + + /** + * Return a V4 address if this is a 6To4 address. + * @throws TypeError if not a 6To4 address + */ + IPAddressV4 getIPv4For6To4() const; + + // Return true if a 6TO4 address + bool is6To4() const { + return type() == IPAddressV6::Type::T6TO4; + } + + // Return true if a TEREDO address + bool isTeredo() const { + return type() == IPAddressV6::Type::TEREDO; + } + + // return true if this is v4-to-v6-mapped + bool isIPv4Mapped() const; + + // Return the V6 address type + Type type() const; + + /** + * @see IPAddress#bitCount + * @returns 128 + */ + static size_t bitCount() { return 128; } + + /** + * @see IPAddress#toJson + */ + std::string toJson() const; + + size_t hash() const; + + // @see IPAddress#inSubnet + // @throws IPAddressFormatException if string doesn't contain a V6 address + bool inSubnet(StringPiece cidrNetwork) const; + + // return true if address is in subnet + bool inSubnet(const IPAddressV6& subnet, uint8_t cidr) const { + return inSubnetWithMask(subnet, fetchMask(cidr)); + } + bool inSubnetWithMask(const IPAddressV6& subnet, + const ByteArray16& mask) const; + + // @see IPAddress#isLoopback + bool isLoopback() const; + + // @see IPAddress#isNonroutable + bool isNonroutable() const { + return !isRoutable(); + } + + /** + * Return true if this address is routable. + */ + bool isRoutable() const; + + // @see IPAddress#isPrivate + bool isPrivate() const; + + /** + * Return true if this is a link-local IPv6 address. + * + * Note that this only returns true for addresses in the fe80::/10 range. + * It returns false for the loopback address (::1), even though this address + * is also effectively has link-local scope. It also returns false for + * link-scope and interface-scope multicast addresses. + */ + bool isLinkLocal() const; + + /** + * Return true if this is a multicast address. + */ + bool isMulticast() const; + + /** + * Return the flags for a multicast address. + * This method may only be called on multicast addresses. + */ + uint8_t getMulticastFlags() const; + + /** + * Return the scope for a multicast address. + * This method may only be called on multicast addresses. + */ + uint8_t getMulticastScope() const; + + // @see IPAddress#isZero + bool isZero() const { + return detail::Bytes::isZero(bytes(), 16); + } + + bool isLinkLocalBroadcast() const; + + // @see IPAddress#mask + IPAddressV6 mask(size_t numBits) const; + + // return underlying in6_addr structure + in6_addr toAddr() const { return addr_.in6Addr_; } + + sockaddr_in6 toSockAddr() const { + sockaddr_in6 addr; + memset(&addr, 0, sizeof(sockaddr_in6)); + addr.sin6_family = AF_INET6; + memcpy(&addr.sin6_addr, &addr_.in6Addr_, sizeof(in6_addr)); + return addr; + } + + ByteArray16 toByteArray() const { + ByteArray16 ba{{0}}; + std::memcpy(ba.data(), bytes(), 16); + return ba; + } + + // @see IPAddress#toFullyQualified + std::string toFullyQualified() const; + + // @see IPAddress#str + std::string str() const; + + // @see IPAddress#version + size_t version() const { return 6; } + + /** + * Return the solicited-node multicast address for this address. + */ + IPAddressV6 getSolicitedNodeAddress() const; + + /** + * Return the mask associated with the given number of bits. + * If for instance numBits was 24 (e.g. /24) then the V4 mask returned should + * be {0xff, 0xff, 0xff, 0x00}. + * @param [in] numBits bitmask to retrieve + * @throws abort if numBits == 0 or numBits > bitCount() + * @return mask associated with numBits + */ + static const ByteArray16 fetchMask(size_t numBits); + // Given 2 IPAddressV6,mask pairs extract the longest common IPAddress, + // mask pair + static CIDRNetworkV6 longestCommonPrefix(const CIDRNetworkV6& one, + const CIDRNetworkV6& two) { + auto prefix = detail::Bytes::longestCommonPrefix( + one.first.addr_.bytes_, one.second, + two.first.addr_.bytes_, two.second); + return {IPAddressV6(prefix.first), prefix.second}; + } + // Number of bytes in the address representation. + static constexpr size_t byteCount() { return 16; } + + //get nth most significant bit - 0 indexed + bool getNthMSBit(size_t bitIndex) const { + return detail::getNthMSBitImpl(*this, bitIndex, AF_INET6); + } + //get nth most significant byte - 0 indexed + uint8_t getNthMSByte(size_t byteIndex) const; + //get nth bit - 0 indexed + bool getNthLSBit(size_t bitIndex) const { + return getNthMSBit(bitCount() - bitIndex - 1); + } + //get nth byte - 0 indexed + uint8_t getNthLSByte(size_t byteIndex) const { + return getNthMSByte(byteCount() - byteIndex - 1); + } + + const unsigned char* bytes() const { return addr_.in6Addr_.s6_addr; } + protected: + /** + * Helper that returns true if the address is in the binary subnet specified + * by addr. + */ + bool inBinarySubnet(const std::array addr, + size_t numBits) const; + + private: + union AddressStorage { + in6_addr in6Addr_; + ByteArray16 bytes_; + AddressStorage() { + std::memset(this, 0, sizeof(AddressStorage)); + } + explicit AddressStorage(const ByteArray16& bytes): bytes_(bytes) {} + explicit AddressStorage(const in6_addr& addr): in6Addr_(addr) {} + explicit AddressStorage(MacAddress mac); + } addr_; + + static const std::array masks_; + + /** + * Set the current IPAddressV6 object to have the address specified by bytes. + * @throws IPAddressFormatException if bytes.size() is not 16. + */ + void setFromBinary(ByteRange bytes); +}; + +// boost::hash uses hash_value() so this allows boost::hash to work +// automatically for IPAddressV6 +std::size_t hash_value(const IPAddressV6& addr); +std::ostream& operator<<(std::ostream& os, const IPAddressV6& addr); +// Define toAppend() to allow IPAddressV6 to be used with to +void toAppend(IPAddressV6 addr, std::string* result); +void toAppend(IPAddressV6 addr, fbstring* result); + +/** + * Return true if two addresses are equal. + */ +inline bool operator==(const IPAddressV6& addr1, const IPAddressV6& addr2) { + return (std::memcmp(addr1.toAddr().s6_addr, addr2.toAddr().s6_addr, 16) == 0); +} +// Return true if addr1 < addr2 +inline bool operator<(const IPAddressV6& addr1, const IPAddressV6& addr2) { + return (std::memcmp(addr1.toAddr().s6_addr, addr2.toAddr().s6_addr, 16) < 0); +} + +} // folly + +namespace std { +template<> +struct hash { + size_t operator()(const folly::IPAddressV6& addr) const { + return addr.hash(); + } +}; +} // std diff --git a/folly/MacAddress.cpp b/folly/MacAddress.cpp new file mode 100644 index 00000000..6368483f --- /dev/null +++ b/folly/MacAddress.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MacAddress.h" + +#include "folly/Exception.h" +#include "folly/IPAddressV6.h" + +using std::invalid_argument; +using std::string; + +namespace folly { + +const MacAddress MacAddress::BROADCAST{Endian::big(0xffffffffffffU)}; +const MacAddress MacAddress::ZERO; + +MacAddress::MacAddress(StringPiece str) { + memset(&bytes_, 0, 8); + parse(str); +} + +MacAddress MacAddress::createMulticast(IPAddressV6 v6addr) { + // This method should only be used for multicast addresses. + DCHECK(v6addr.isMulticast()); + + uint8_t bytes[SIZE]; + bytes[0] = 0x33; + bytes[1] = 0x33; + memcpy(bytes + 2, v6addr.bytes() + 12, 4); + return fromBinary(ByteRange(bytes, SIZE)); +} + +string MacAddress::toString() const { + static const char hexValues[] = "0123456789abcdef"; + string result; + result.resize(17); + result[0] = hexValues[getByte(0) >> 4]; + result[1] = hexValues[getByte(0) & 0xf]; + result[2] = ':'; + result[3] = hexValues[getByte(1) >> 4]; + result[4] = hexValues[getByte(1) & 0xf]; + result[5] = ':'; + result[6] = hexValues[getByte(2) >> 4]; + result[7] = hexValues[getByte(2) & 0xf]; + result[8] = ':'; + result[9] = hexValues[getByte(3) >> 4]; + result[10] = hexValues[getByte(3) & 0xf]; + result[11] = ':'; + result[12] = hexValues[getByte(4) >> 4]; + result[13] = hexValues[getByte(4) & 0xf]; + result[14] = ':'; + result[15] = hexValues[getByte(5) >> 4]; + result[16] = hexValues[getByte(5) & 0xf]; + return result; +} + +void MacAddress::parse(StringPiece str) { + // Helper function to convert a single hex char into an integer + auto unhex = [](char c) -> int { + return c >= '0' && c <= '9' ? c - '0' : + c >= 'A' && c <= 'F' ? c - 'A' + 10 : + c >= 'a' && c <= 'f' ? c - 'a' + 10 : + -1; + }; + auto isSeparatorChar = [](char c) { + return c == ':' || c == '-'; + }; + + uint8_t parsed[SIZE]; + auto p = str.begin(); + for (unsigned int byteIndex = 0; byteIndex < SIZE; ++byteIndex) { + if (p == str.end()) { + throw invalid_argument(to("invalid MAC address \"", str, + "\": not enough digits")); + } + + // Skip over ':' or '-' separators between bytes + if (byteIndex != 0 && isSeparatorChar(*p)) { + ++p; + if (p == str.end()) { + throw invalid_argument(to("invalid MAC address \"", str, + "\": not enough digits")); + } + } + + // Parse the upper nibble + int upper = unhex(*p); + if (upper < 0) { + throw invalid_argument(to("invalid MAC address \"", str, + "\": contains non-hex digit")); + } + ++p; + + // Parse the lower nibble + int lower; + if (p == str.end()) { + lower = upper; + upper = 0; + } else { + lower = unhex(*p); + if (lower < 0) { + // Also accept ':', '-', or '\0', to handle the case where one + // of the bytes was represented by just a single digit. + if (isSeparatorChar(*p)) { + lower = upper; + upper = 0; + } else { + throw invalid_argument(to("invalid MAC address \"", str, + "\": contains non-hex digit")); + } + } + ++p; + } + + // Update parsed with the newly parsed byte + parsed[byteIndex] = ((upper << 4) | lower); + } + + if (p != str.end()) { + // String is too long to be a MAC address + throw invalid_argument(to("invalid MAC address \"", str, + "\": found trailing characters")); + } + + // Only update now that we have successfully parsed the entire + // string. This way we remain unchanged on error. + setFromBinary(ByteRange(parsed, SIZE)); +} + +void MacAddress::setFromBinary(ByteRange value) { + if (value.size() != SIZE) { + throw invalid_argument(to("MAC address must be 6 bytes " + "long, got ", value.size())); + } + memcpy(bytes_ + 2, value.begin(), SIZE); +} + +std::ostream& operator<<(std::ostream& os, MacAddress address) { + os << address.toString(); + return os; +} + +} // folly diff --git a/folly/MacAddress.h b/folly/MacAddress.h new file mode 100644 index 00000000..fbc9bd28 --- /dev/null +++ b/folly/MacAddress.h @@ -0,0 +1,219 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include "folly/Bits.h" +#include "folly/Conv.h" + +namespace folly { + +class IPAddressV6; + +/* + * MacAddress represents an IEEE 802 MAC address. + */ +class MacAddress : private boost::totally_ordered { + public: + static constexpr size_t SIZE = 6; + static const MacAddress BROADCAST; + static const MacAddress ZERO; + + /* + * Construct a zero-initialized MacAddress. + */ + MacAddress() { + memset(&bytes_, 0, 8); + } + + /* + * Parse a MacAddress from a human-readable string. + * The string must contain 6 one- or two-digit hexadecimal + * numbers, separated by dashes or colons. + * Examples: 00:02:C9:C8:F9:68 or 0-2-c9-c8-f9-68 + */ + explicit MacAddress(StringPiece str); + + /* + * Construct a MAC address from its 6-byte binary value + */ + static MacAddress fromBinary(ByteRange value) { + MacAddress ret; + ret.setFromBinary(value); + return ret; + } + + /* + * Construct a MacAddress from a uint64_t in network byte order. + * + * The first two bytes are ignored, and the MAC address is taken from the + * latter 6 bytes. + * + * This is a static method rather than a constructor to avoid confusion + * between host and network byte order constructors. + */ + static MacAddress fromNBO(uint64_t value) { + return MacAddress(value); + } + + /* + * Construct a MacAddress from a uint64_t in host byte order. + * + * The most significant two bytes are ignored, and the MAC address is taken + * from the least significant 6 bytes. + * + * This is a static method rather than a constructor to avoid confusion + * between host and network byte order constructors. + */ + static MacAddress fromHBO(uint64_t value) { + return MacAddress(Endian::big(value)); + } + + /* + * Construct the multicast MacAddress for the specified multicast IPv6 + * address. + */ + static MacAddress createMulticast(IPAddressV6 addr); + + /* + * Get a pointer to the MAC address' binary value. + * + * The returned value points to internal storage inside the MacAddress + * object. It is only valid as long as the MacAddress, and its contents may + * change if the MacAddress is updated. + */ + const uint8_t* bytes() const { + return bytes_ + 2; + } + + /* + * Return the address as a uint64_t, in network byte order. + * + * The first two bytes will be 0, and the subsequent 6 bytes will contain + * the address in network byte order. + */ + uint64_t u64NBO() const { + return packedBytes(); + } + + /* + * Return the address as a uint64_t, in host byte order. + * + * The two most significant bytes will be 0, and the remaining 6 bytes will + * contain the address. The most significant of these 6 bytes will contain + * the first byte that appear on the wire, and the least significant byte + * will contain the last byte. + */ + uint64_t u64HBO() const { + // Endian::big() does what we want here, even though we are converting + // from big-endian to host byte order. This swaps if and only if + // the host byte order is little endian. + return Endian::big(packedBytes()); + } + + /* + * Return a human-readable representation of the MAC address. + */ + std::string toString() const; + + /* + * Update the current MacAddress object from a human-readable string. + */ + void parse(StringPiece str); + + /* + * Update the current MacAddress object from a 6-byte binary representation. + */ + void setFromBinary(ByteRange value); + + bool isBroadcast() const { + return *this == BROADCAST; + } + bool isMulticast() const { + return getByte(0) & 0x1; + } + bool isUnicast() const { + return !isMulticast(); + } + + /* + * Return true if this MAC address is locally administered. + * + * Locally administered addresses are assigned by the local network + * administrator, and are not guaranteed to be globally unique. (It is + * similar to IPv4's private address space.) + * + * Note that isLocallyAdministered() will return true for the broadcast + * address, since it has the locally administered bit set. + */ + bool isLocallyAdministered() const { + return getByte(0) & 0x2; + } + + // Equality and less-than operators. + // boost::totally_ordered provides the other comparison operators. + + bool operator==(const MacAddress& other) const { + // All constructors and modifying methods make sure padding is 0, + // so we don't need to mask these bytes out when comparing here. + return packedBytes() == other.packedBytes(); + } + + bool operator<(const MacAddress& other) const { + return u64HBO() < other.u64HBO(); + } + + private: + explicit MacAddress(uint64_t valueNBO) { + memcpy(&bytes_, &valueNBO, 8); + // Set the pad bytes to 0. + // This allows us to easily compare two MacAddresses, + // without having to worry about differences in the padding. + bytes_[0] = 0; + bytes_[1] = 0; + } + + /* We store the 6 bytes starting at bytes_[2] (most significant) + through bytes_[7] (least). + bytes_[0] and bytes_[1] are always equal to 0 to simplify comparisons. + */ + unsigned char bytes_[8]; + + inline uint64_t getByte(size_t index) const { + return bytes_[index + 2]; + } + + uint64_t packedBytes() const { + uint64_t u64; + memcpy(&u64, bytes_, 8); + return u64; + } +}; + +/* Define toAppend() so to will work */ +template +typename std::enable_if::value>::type +toAppend(MacAddress address, Tgt* result) { + toAppend(address.toString(), result); +} + +std::ostream& operator<<(std::ostream& os, MacAddress address); + +} // folly diff --git a/folly/detail/IPAddress.h b/folly/detail/IPAddress.h new file mode 100644 index 00000000..7f1095e0 --- /dev/null +++ b/folly/detail/IPAddress.h @@ -0,0 +1,178 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +#include "folly/Conv.h" +#include "folly/Format.h" + +namespace folly { namespace detail { + +inline std::string familyNameStr(sa_family_t family) { + switch (family) { + case AF_INET: + return "AF_INET"; + case AF_INET6: + return "AF_INET6"; + case AF_UNSPEC: + return "AF_UNSPEC"; + case AF_UNIX: + return "AF_UNIX"; + default: + return folly::format("sa_family_t({})", + folly::to(family)).str(); + } +} + +template +inline bool getNthMSBitImpl(const IPAddrType& ip, uint8_t bitIndex, + sa_family_t family) { + if (bitIndex >= ip.bitCount()) { + throw std::invalid_argument(folly::to("Bit index must be < ", + ip.bitCount(), " for addresses of type :", familyNameStr(family))); + } + //Underlying bytes are in n/w byte order + return (ip.getNthMSByte(bitIndex / 8) & (0x80 >> (bitIndex % 8))) != 0; +} + +/** + * Helper for working with unsigned char* or uint8_t* ByteArray values + */ +struct Bytes : private boost::noncopyable { + // return true if all values of src are zero + static bool isZero(const uint8_t* src, std::size_t len) { + for (auto i = 0; i < len; i++) { + if (src[i] != 0x00) { + return false; + } + } + return true; + } + + // mask the values from two byte arrays, returning a new byte array + template + static std::array mask(const std::array& a, + const std::array& b) { + static_assert(N > 0, "Can't mask an empty ByteArray"); + std::size_t asize = a.size(); + std::array ba{{0}}; + for (int i = 0; i < asize; i++) { + ba[i] = a[i] & b[i]; + } + return ba; + } + + template + static std::pair, uint8_t> + longestCommonPrefix( + const std::array& one, uint8_t oneMask, + const std::array& two, uint8_t twoMask) { + static constexpr auto kBitCount = N * 8; + static constexpr std::array kMasks {{ + 0x80, // /1 + 0xc0, // /2 + 0xe0, // /3 + 0xf0, // /4 + 0xf8, // /5 + 0xfc, // /6 + 0xfe, // /7 + 0xff // /8 + }}; + if (oneMask > kBitCount || twoMask > kBitCount) { + throw std::invalid_argument(folly::to("Invalid mask " + "length: ", oneMask > twoMask ? oneMask : twoMask, + ". Mask length must be <= ", kBitCount)); + } + + auto mask = std::min(oneMask, twoMask); + uint8_t byteIndex = 0; + std::array ba{{0}}; + // Compare a byte at a time. Note - I measured compared this with + // going multiple bytes at a time (8, 4, 2 and 1). It turns out + // to be 20 - 25% slower for 4 and 16 byte arrays. + while (byteIndex * 8 <= mask && one[byteIndex] == two[byteIndex]) { + ba[byteIndex] = one[byteIndex]; + ++byteIndex; + } + auto bitIndex = std::min(mask, (uint8_t)(byteIndex * 8)); + // Compute the bit up to which the two byte arrays match in the + // unmatched byte. + // Here the check is bitIndex < mask since the 0th mask entry in + // kMasks array holds the mask for masking the MSb in this byte. + // We could instead make it hold so that no 0th entry masks no + // bits but thats a useless iteration. + while (bitIndex < mask && ((one[bitIndex / 8] & kMasks[bitIndex % 8]) == + (two[bitIndex / 8] & kMasks[bitIndex % 8]))) { + ba[bitIndex / 8] = one[bitIndex / 8] & kMasks[bitIndex % 8]; + ++bitIndex; + } + return {ba, bitIndex}; + } + + // create an in_addr from an uint8_t* + static inline in_addr mkAddress4(const uint8_t* src) { + union { + in_addr addr; + uint8_t bytes[4]; + } addr; + std::memset(&addr, 0, 4); + std::memcpy(addr.bytes, src, 4); + return addr.addr; + } + + // create an in6_addr from an uint8_t* + static inline in6_addr mkAddress6(const uint8_t* src) { + in6_addr addr; + std::memset(&addr, 0, 16); + std::memcpy(addr.s6_addr, src, 16); + return addr; + } + + // convert an uint8_t* to its hex value + static std::string toHex(const uint8_t* src, std::size_t len) { + static const char* const lut = "0123456789abcdef"; + std::stringstream ss; + for (int i = 0; i < len; i++) { + const unsigned char c = src[i]; + ss << lut[c >> 4] << lut[c & 15]; + } + return ss.str(); + } + + private: + Bytes() = delete; + ~Bytes() = delete; +}; + +}} // folly::detail diff --git a/folly/test/IPAddressTest.cpp b/folly/test/IPAddressTest.cpp new file mode 100644 index 00000000..b9cb43de --- /dev/null +++ b/folly/test/IPAddressTest.cpp @@ -0,0 +1,1056 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "IPAddressTest.h" + +#include + +#include "folly/Bits.h" +#include "folly/Format.h" +#include "folly/String.h" +#include "folly/MacAddress.h" + +using namespace folly; +using namespace std; + +// tests code example +TEST(IPAddress, CodeExample) { + EXPECT_EQ(4, sizeof(IPAddressV4)); + EXPECT_EQ(16, sizeof(IPAddressV6)); + EXPECT_EQ(20, sizeof(IPAddress)); + IPAddress v4addr("192.0.2.129"); + IPAddress v6map("::ffff:192.0.2.129"); + EXPECT_TRUE(v4addr.inSubnet("192.0.2.0/24")); + EXPECT_TRUE(v4addr.inSubnet(IPAddress("192.0.2.0"), 24)); + EXPECT_TRUE(v4addr.inSubnet("192.0.2.128/30")); + EXPECT_FALSE(v4addr.inSubnet("192.0.2.128/32")); + EXPECT_EQ(2164392128, v4addr.asV4().toLong()); + EXPECT_EQ(3221226113, v4addr.asV4().toLongHBO()); + ASSERT_TRUE(v4addr.isV4()); + ASSERT_TRUE(v6map.isV6()); + EXPECT_TRUE(v4addr == v6map); + ASSERT_TRUE(v6map.isIPv4Mapped()); + EXPECT_TRUE(v4addr.asV4() == IPAddress::createIPv4(v6map)); + EXPECT_TRUE(IPAddress::createIPv6(v4addr) == v6map.asV6()); +} + +TEST(IPAddress, Ordering) { + IPAddress a1("0.1.1.1"); + IPAddress a2("1.1.1.0"); + EXPECT_TRUE(a1 < a2); + + IPAddress b1("::ffff:0.1.1.1"); + IPAddress b2("::ffff:1.1.1.0"); + EXPECT_TRUE(b1 < b2); +} + +TEST(IPAddress, InvalidAddressFamilyExceptions) { + // asV4 + { + IPAddress addr; + EXPECT_THROW(addr.asV4(), InvalidAddressFamilyException); + } + // asV6 + { + IPAddress addr; + EXPECT_THROW(addr.asV6(), InvalidAddressFamilyException); + } + // sockaddr ctor + { + // setup + sockaddr_in addr; + addr.sin_family = AF_UNSPEC; + + EXPECT_THROW(IPAddress((sockaddr *)&addr), InvalidAddressFamilyException); + } +} + +TEST(IPAddress, CreateNetwork) { + // test valid IPv4 network + { + auto net = IPAddress::createNetwork("192.168.0.1/24"); + ASSERT_TRUE(net.first.isV4()); + EXPECT_EQ("192.168.0.0", net.first.str()); + EXPECT_EQ(24, net.second); + EXPECT_EQ("192.168.0.0/24", IPAddress::networkToString(net)); + } + // test valid IPv4 network without applying mask + { + auto net = IPAddress::createNetwork("192.168.0.1/24", -1, false); + ASSERT_TRUE(net.first.isV4()); + EXPECT_EQ("192.168.0.1", net.first.str()); + EXPECT_EQ(24, net.second); + EXPECT_EQ("192.168.0.1/24", IPAddress::networkToString(net)); + } + // test valid IPv6 network + { + auto net = IPAddress::createNetwork("1999::1/24"); + ASSERT_TRUE(net.first.isV6()); + EXPECT_EQ("1999::", net.first.str()); + EXPECT_EQ(24, net.second); + EXPECT_EQ("1999::/24", IPAddress::networkToString(net)); + } + // test valid IPv6 network without applying mask + { + auto net = IPAddress::createNetwork("1999::1/24", -1, false); + ASSERT_TRUE(net.first.isV6()); + EXPECT_EQ("1999::1", net.first.str()); + EXPECT_EQ(24, net.second); + EXPECT_EQ("1999::1/24", IPAddress::networkToString(net)); + } + // test empty string + EXPECT_THROW(IPAddress::createNetwork(""), IPAddressFormatException); + // test multi slash string + EXPECT_THROW(IPAddress::createNetwork("192.168.0.1/24/36"), + IPAddressFormatException); + // test no slash string with default IPv4 + { + auto net = IPAddress::createNetwork("192.168.0.1"); + ASSERT_TRUE(net.first.isV4()); + EXPECT_EQ("192.168.0.1", net.first.str()); + EXPECT_EQ(32, net.second); // auto-detected + net = IPAddress::createNetwork("192.168.0.1", -1, false); + ASSERT_TRUE(net.first.isV4()); + EXPECT_EQ("192.168.0.1", net.first.str()); + EXPECT_EQ(32, net.second); + } + // test no slash string with default IPv6 + { + auto net = IPAddress::createNetwork("1999::1"); + ASSERT_TRUE(net.first.isV6()); + EXPECT_EQ("1999::1", net.first.str()); + EXPECT_EQ(128, net.second); + } + // test no slash string with invalid default + EXPECT_THROW(IPAddress::createNetwork("192.168.0.1", 33), + IPAddressFormatException); + +} + +// test assignment operators +TEST(IPAddress, Assignment) { + static const string kIPv4Addr = "69.63.189.16"; + static const string kIPv6Addr = "2620:0:1cfe:face:b00c::3"; + + // Test assigning IPAddressV6 addr to IPAddress (was V4) + { + IPAddress addr(kIPv4Addr); + IPAddressV6 addrV6 = IPAddress(kIPv6Addr).asV6(); + EXPECT_TRUE(addr.isV4()); + EXPECT_EQ(kIPv4Addr, addr.str()); + addr = addrV6; + EXPECT_TRUE(addr.isV6()); + EXPECT_EQ(kIPv6Addr, addr.str()); + } + // Test assigning IPAddressV4 addr to IPAddress (was V6) + { + IPAddress addr(kIPv6Addr); + IPAddressV4 addrV4 = IPAddress(kIPv4Addr).asV4(); + EXPECT_TRUE(addr.isV6()); + EXPECT_EQ(kIPv6Addr, addr.str()); + addr = addrV4; + EXPECT_TRUE(addr.isV4()); + EXPECT_EQ(kIPv4Addr, addr.str()); + } + // Test assigning IPAddress(v6) to IPAddress (was v4) + { + IPAddress addr(kIPv4Addr); + IPAddress addrV6 = IPAddress(kIPv6Addr); + EXPECT_TRUE(addr.isV4()); + EXPECT_EQ(kIPv4Addr, addr.str()); + addr = addrV6; + EXPECT_TRUE(addr.isV6()); + EXPECT_EQ(kIPv6Addr, addr.str()); + } + // Test assigning IPAddress(v4) to IPAddress (was v6) + { + IPAddress addr(kIPv6Addr); + IPAddress addrV4 = IPAddress(kIPv4Addr); + EXPECT_TRUE(addr.isV6()); + EXPECT_EQ(kIPv6Addr, addr.str()); + addr = addrV4; + EXPECT_TRUE(addr.isV4()); + EXPECT_EQ(kIPv4Addr, addr.str()); + } +} + +// Test the default constructors +TEST(IPAddress, CtorDefault) { + IPAddressV4 v4; + EXPECT_EQ(IPAddressV4("0.0.0.0"), v4); + IPAddressV6 v6; + EXPECT_EQ(IPAddressV6("::0"), v6); +} + +// Test addresses constructed using a in[6]_addr value +TEST_P(IPAddressTest, CtorAddress) { + AddressData param = GetParam(); + IPAddress strAddr(param.address); + IPAddress address; + + if (param.version == 4) { + in_addr v4addr = detail::Bytes::mkAddress4(¶m.bytes[0]); + address = IPAddress(v4addr); + } else { + in6_addr v6addr = detail::Bytes::mkAddress6(¶m.bytes[0]); + address = IPAddress(v6addr); + } + ExpectIsValid(address); + EXPECT_EQ(strAddr, address); +} + +// Test addresses constructed using a binary address +TEST_P(IPAddressTest, CtorBinary) { + AddressData param = GetParam(); + IPAddress address; + + if (param.version == 4) { + in_addr v4addr = AddressData::parseAddress4(param.address); + address = IPAddress::fromBinary(ByteRange((unsigned char*)&v4addr, 4)); + } else { + in6_addr v6addr = AddressData::parseAddress6(param.address); + address = IPAddress::fromBinary(ByteRange((unsigned char*)&v6addr, 16)); + } + + ExpectIsValid(address); + EXPECT_EQ(IPAddress(param.address), address); +} + +// Test addresses constructed using a string +TEST_P(IPAddressTest, CtorString) { + AddressData param = GetParam(); + IPAddress address(param.address); + + ExpectIsValid(address); + + // Test the direct version-specific constructor + if (param.version == 4) { + IPAddressV4 v4(param.address); + ExpectIsValid(IPAddress(v4)); + EXPECT_THROW(IPAddressV6 v6(param.address), IPAddressFormatException); + } else if (param.version == 6) { + IPAddressV6 v6(param.address); + ExpectIsValid(IPAddress(v6)); + EXPECT_THROW(IPAddressV4 v4(param.address), IPAddressFormatException); + } +} + +TEST(IPAddress, CtorSockaddr) { + // test v4 address + { + // setup + sockaddr_in addr; + in_addr sin_addr; + sin_addr.s_addr = htonl(2122547223); + addr.sin_family = AF_INET; + addr.sin_addr = sin_addr; + + IPAddress ipAddr((sockaddr *)&addr); + EXPECT_TRUE(ipAddr.isV4()); + EXPECT_EQ("126.131.128.23", ipAddr.str()); + } + // test v6 address + { + // setup + sockaddr_in6 addr; + in6_addr sin_addr; + ByteArray16 sec{{ + // 2620:0:1cfe:face:b00c::3 + 38,32,0,0,28,254,250,206,176,12,0,0,0,0,0,3 + }}; + std::memcpy(sin_addr.s6_addr, sec.data(), 16); + addr.sin6_family = AF_INET6; + addr.sin6_addr = sin_addr; + + IPAddress ipAddr((sockaddr *)&addr); + EXPECT_TRUE(ipAddr.isV6()); + EXPECT_EQ("2620:0:1cfe:face:b00c::3", ipAddr.str()); + } + // test nullptr exception + { + sockaddr *addr = nullptr; + EXPECT_THROW(IPAddress((const sockaddr*)addr), IPAddressFormatException); + } + // test invalid family exception + { + // setup + sockaddr_in addr; + in_addr sin_addr; + sin_addr.s_addr = htonl(2122547223); + addr.sin_family = AF_UNSPEC; + addr.sin_addr = sin_addr; + + EXPECT_THROW(IPAddress((sockaddr *)&addr), IPAddressFormatException); + } +} + +TEST(IPAddress, ToSockaddrStorage) { + // test v4 address + { + string strAddr("126.131.128.23"); + IPAddress addr(strAddr); + sockaddr_storage out; + + ASSERT_TRUE(addr.isV4()); // test invariant + EXPECT_GT(addr.toSockaddrStorage(&out), 0); + + IPAddress sockAddr((sockaddr*)&out); + ASSERT_TRUE(sockAddr.isV4()); + EXPECT_EQ(strAddr, sockAddr.str()); + } + // test v6 address + { + string strAddr("2620:0:1cfe:face:b00c::3"); + IPAddress addr(strAddr); + sockaddr_storage out; + + ASSERT_TRUE(addr.isV6()); // test invariant + EXPECT_GT(addr.toSockaddrStorage(&out), 0); + + IPAddress sockAddr((sockaddr*)&out); + ASSERT_TRUE(sockAddr.isV6()); + EXPECT_EQ(strAddr, sockAddr.str()); + } + // test nullptr exception + { + sockaddr_storage *out = nullptr; + IPAddress addr("127.0.0.1"); + EXPECT_THROW(addr.toSockaddrStorage(out), IPAddressFormatException); + } + // test invalid family exception + { + IPAddress addr; + sockaddr_storage out; + ASSERT_EQ(AF_UNSPEC, addr.family()); + EXPECT_THROW(addr.toSockaddrStorage(&out), InvalidAddressFamilyException); + } +} + +TEST(IPAddress, ToString) { + // Test with IPAddressV4 + IPAddressV4 addr_10_0_0_1("10.0.0.1"); + EXPECT_EQ("10.0.0.1", folly::to(addr_10_0_0_1)); + // Test with IPAddressV6 + IPAddressV6 addr_1("::1"); + EXPECT_EQ("::1", folly::to(addr_1)); + // Test with IPAddress, both V4 and V6 + IPAddress addr_10_1_2_3("10.1.2.3"); + EXPECT_EQ("10.1.2.3", folly::to(addr_10_1_2_3)); + IPAddress addr_1_2_3("1:2::3"); + EXPECT_EQ("1:2::3", folly::to(addr_1_2_3)); + + // Test a combination of all the above arguments + EXPECT_EQ("1:2::3 - 10.0.0.1 - ::1 - 10.1.2.3", + folly::to(addr_1_2_3, " - ", addr_10_0_0_1, + " - ", addr_1, " - ", addr_10_1_2_3)); +} + +// Test that invalid string values are killed +TEST_P(IPAddressCtorTest, InvalidCreation) { + string addr = GetParam(); + EXPECT_THROW(IPAddress((const string)addr), IPAddressFormatException) + << "IPAddress(" << addr << ") " + << "should have thrown an IPAddressFormatException"; +} + +// Test that invalid binary values throw an exception +TEST_P(IPAddressCtorBinaryTest, InvalidBinary) { + auto bin = GetParam(); + EXPECT_THROW(IPAddress::fromBinary(ByteRange(&bin[0], bin.size())), + IPAddressFormatException); +} + +// Test toFullyQualified() +TEST(IPAddress, ToFullyQualifiedFb) { + IPAddress ip("2620:0:1cfe:face:b00c::3"); + EXPECT_EQ("2620:0000:1cfe:face:b00c:0000:0000:0003", ip.toFullyQualified()) + << ip; +} +TEST(IPAddress, ToFullyQualifiedLocal) { + IPAddress ip("::1"); + EXPECT_EQ("0000:0000:0000:0000:0000:0000:0000:0001", ip.toFullyQualified()) + << ip; +} + +// test v4-v6 mapped addresses +TEST_P(IPAddressMappedTest, MappedEqual) { + auto param = GetParam(); + string mappedIp = param.first; + string otherIp = param.second; + + auto mapped = IPAddress(mappedIp); + auto expected = IPAddress(otherIp); + + EXPECT_EQ(expected, mapped); + + IPAddress v6addr; + if (mapped.isV4()) { + v6addr = mapped.asV4().createIPv6(); + } else if (expected.isV4()) { + v6addr = expected.asV4().createIPv6(); + } + EXPECT_TRUE(v6addr.isV6()); + EXPECT_TRUE(mapped == v6addr); + EXPECT_TRUE(expected == v6addr); +} + +// Test subnet mask calculations +TEST_P(IPAddressMaskTest, Masks) { + auto param = GetParam(); + + IPAddress ip(param.address); + IPAddress masked = ip.mask(param.mask); + EXPECT_EQ(param.subnet, masked.str()) + << param.address << "/" << to_string(param.mask) + << " -> " << param.subnet; +} + +// Test inSubnet calculations +TEST_P(IPAddressMaskTest, InSubnet) { + auto param = GetParam(); + + IPAddress ip(param.address); + IPAddress subnet(param.subnet); + EXPECT_TRUE(ip.inSubnet(subnet, param.mask)); +} + +// Test boundary conditions for subnet calculations +TEST_P(IPAddressMaskBoundaryTest, NonMaskedSubnet) { + auto param = GetParam(); + IPAddress ip(param.address); + IPAddress subnet(param.subnet); + EXPECT_EQ(param.inSubnet, ip.inSubnet(subnet, param.mask)); +} + +// Test subnet calcs with 6to4 addresses +TEST(IPAddress, InSubnetWith6to4) { + auto ip = IPAddress("2002:c000:022a::"); // 192.0.2.42 + auto subnet = IPAddress("192.0.0.0"); + EXPECT_TRUE(ip.inSubnet(subnet, 16)); + + auto ip2 = IPAddress("192.0.0.1"); + auto subnet2 = IPAddress("2002:c000:0000::"); // 192.0.0.0 + EXPECT_TRUE(ip2.inSubnet(subnet2, 14)); + + auto ip3 = IPAddress("2002:c000:022a::"); // 192.0.2.42 + auto subnet3 = IPAddress("2002:c000:0000::"); // 192.0.0.0 + EXPECT_TRUE(ip3.inSubnet(subnet3, 16)); +} + +static vector > invalidMasks = { + {"127.0.0.1", 33}, + {"::1", 129}, +}; +TEST(IPAddress, InvalidMask) { + for (auto& tc : invalidMasks) { + uint8_t mask = tc.second; + auto ip = IPAddress(tc.first); + EXPECT_THROW(ip.mask(tc.second), IPAddressFormatException); + } +} + +static vector > v6types = { + {"::1", IPAddressV6::Type::NORMAL}, + {"2620:0:1cfe:face:b00c::3", IPAddressV6::Type::NORMAL}, + {"2001:0000:4136:e378:8000:63bf:3fff:fdd2", IPAddressV6::Type::TEREDO}, + {"2002:c000:022a::", IPAddressV6::Type::T6TO4}, +}; +TEST(IPAddress, V6Types) { + auto mkName = [&](const IPAddressV6::Type t) -> string { + switch (t) { + case IPAddressV6::Type::TEREDO: + return "teredo"; + case IPAddressV6::Type::T6TO4: + return "6to4"; + default: + return "default"; + } + }; + + for (auto& tc : v6types) { + auto ip = IPAddress(tc.first); + EXPECT_TRUE(ip.isV6()); + IPAddressV6 ipv6 = ip.asV6(); + EXPECT_EQ(tc.second, ipv6.type()) + << "expected " << mkName(tc.second) << ", got " << mkName(ipv6.type()); + switch (tc.second) { + case IPAddressV6::Type::TEREDO: + EXPECT_TRUE(ipv6.isTeredo()) << "isTeredo was false"; + EXPECT_FALSE(ipv6.is6To4()) << "is6To4 was true"; + break; + case IPAddressV6::Type::T6TO4: + EXPECT_TRUE(ipv6.is6To4()) << "is6To4 was false"; + EXPECT_FALSE(ipv6.isTeredo()) << "isTeredo was true"; + break; + case IPAddressV6::Type::NORMAL: + EXPECT_FALSE(ipv6.is6To4()) << "is6To4 was true"; + EXPECT_FALSE(ipv6.isTeredo()) << "isTeredo was true"; + break; + default: + throw std::range_error("Invalid expected type: " + + to_string(tc.second)); + } + } +} + +static vector > provideToLong = { + {"0.0.0.0", 0}, + {"10.0.0.0", 167772160}, + {"126.131.128.23", 2122547223}, + {"192.168.0.0", 3232235520}, +}; +TEST(IPAddress, ToLong) { + for (auto& tc : provideToLong) { + auto ip = IPAddress(tc.first); + EXPECT_TRUE(ip.isV4()); + IPAddressV4 ipv4 = ip.asV4(); + EXPECT_EQ(tc.second, ipv4.toLongHBO()); + + auto ip2 = IPAddress::fromLongHBO(tc.second); + EXPECT_TRUE(ip2.isV4()); + EXPECT_TRUE(tc.first.compare(ip2.str()) == 0); + EXPECT_EQ(tc.second, ip2.asV4().toLongHBO()); + + auto nla = htonl(tc.second); + auto ip3 = IPAddress::fromLong(nla); + EXPECT_TRUE(ip3.isV4()); + EXPECT_TRUE(tc.first.compare(ip3.str()) == 0); + EXPECT_EQ(nla, ip3.asV4().toLong()); + } +} + +TEST(IPAddress, fromBinaryV4) { + for (auto& tc : provideToLong) { + SCOPED_TRACE(tc.first); + union { + uint8_t u8[4]; + uint32_t u32; + } data; + data.u32 = Endian::big(tc.second); + ByteRange bytes(data.u8, 4); + + auto fromBin = IPAddressV4::fromBinary(bytes); + IPAddressV4 fromStr(tc.first); + EXPECT_EQ(fromStr, fromBin); + + IPAddressV4 addr2("0.0.0.0"); + addr2 = IPAddressV4::fromBinary(bytes); + EXPECT_EQ(fromStr, addr2); + + IPAddress genericAddr = IPAddress::fromBinary(bytes); + ASSERT_TRUE(genericAddr.isV4()); + EXPECT_EQ(fromStr, genericAddr.asV4()); + EXPECT_EQ(ByteRange(genericAddr.bytes(), genericAddr.byteCount()), bytes); + } + + uint8_t data[20]; + EXPECT_THROW(IPAddressV4::fromBinary(ByteRange(data, 3)), + IPAddressFormatException); + EXPECT_THROW(IPAddressV4::fromBinary(ByteRange(data, 16)), + IPAddressFormatException); + EXPECT_THROW(IPAddressV4::fromBinary(ByteRange(data, 20)), + IPAddressFormatException); +} + +static vector > > provideBinary16Bytes = { + {"::0", + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {"1::2", + {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}, + {"fe80::0012:34ff:fe56:78ab", + {0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x12, 0x34, 0xff, 0xfe, 0x56, 0x78, 0xab}}, + {"2001:db8:1234:5678:90ab:cdef:8765:4321", + {0x20, 0x01, 0x0d, 0xb8, 0x12, 0x34, 0x56, 0x78, + 0x90, 0xab, 0xcd, 0xef, 0x87, 0x65, 0x43, 0x21}}, + {"::ffff:0:c0a8:1", + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01}}, +}; + +TEST(IPAddress, fromBinaryV6) { + for (auto& tc : provideBinary16Bytes) { + SCOPED_TRACE(tc.first); + ByteRange bytes(&tc.second.front(), tc.second.size()); + + auto fromBin = IPAddressV6::fromBinary(bytes); + IPAddressV6 fromStr(tc.first); + EXPECT_EQ(fromStr, fromBin); + + IPAddressV6 addr2("::0"); + addr2 = IPAddressV6::fromBinary(bytes); + EXPECT_EQ(fromStr, addr2); + + IPAddress genericAddr = IPAddress::fromBinary(bytes); + ASSERT_TRUE(genericAddr.isV6()); + EXPECT_EQ(fromStr, genericAddr.asV6()); + EXPECT_EQ(ByteRange(genericAddr.bytes(), genericAddr.byteCount()), bytes); + } + + uint8_t data[20]; + EXPECT_THROW(IPAddressV6::fromBinary(ByteRange(data, 3)), + IPAddressFormatException); + EXPECT_THROW(IPAddressV6::fromBinary(ByteRange(data, 4)), + IPAddressFormatException); + EXPECT_THROW(IPAddressV6::fromBinary(ByteRange(data, 20)), + IPAddressFormatException); +} + +TEST_P(IPAddressFlagTest, IsLoopback) { + AddressFlags param = GetParam(); + IPAddress addr(param.address); + + EXPECT_EQ(param.version, addr.version()); + EXPECT_EQ(param.isLoopback(), addr.isLoopback()); +} + +TEST_P(IPAddressFlagTest, IsPrivate) { + AddressFlags param = GetParam(); + IPAddress addr(param.address); + + EXPECT_EQ(param.version, addr.version()); + EXPECT_EQ(param.isPrivate(), addr.isPrivate()) << addr; +} + +TEST_P(IPAddressFlagTest, IsNonroutable) { + AddressFlags param = GetParam(); + IPAddress addr(param.address); + + EXPECT_EQ(param.version, addr.version()); + EXPECT_EQ(param.isNonroutable(), addr.isNonroutable()) << addr; +} + +TEST_P(IPAddressFlagTest, IsZero) { + AddressFlags param = GetParam(); + IPAddress addr(param.address); + + EXPECT_EQ(param.version, addr.version()); + EXPECT_EQ(param.isZero(), addr.isZero()) << addr; +} + +TEST_P(IPAddressFlagTest, IsLinkLocal) { + AddressFlags param = GetParam(); + if (param.version != 6) { + return; + } + IPAddressV6 addr(param.address); + EXPECT_EQ(param.isLinkLocal(), addr.isLinkLocal()) << addr; +} + +TEST(IPAddress, CreateLinkLocal) { + IPAddressV6 addr(IPAddressV6::LINK_LOCAL, MacAddress("00:05:73:f9:46:fc")); + EXPECT_EQ(IPAddressV6("fe80::0205:73ff:fef9:46fc"), addr); + + addr = IPAddressV6(IPAddressV6::LINK_LOCAL, MacAddress("02:00:00:12:34:56")); + EXPECT_EQ(IPAddressV6("fe80::ff:fe12:3456"), addr); +} + +TEST_P(IPAddressFlagTest, IsLinkLocalBroadcast) { + AddressFlags param = GetParam(); + IPAddress addr(param.address); + EXPECT_EQ(param.version, addr.version()); + EXPECT_EQ(param.isLinkLocalBroadcast(), addr.isLinkLocalBroadcast()); +} + +TEST(IPAddress, SolicitedNodeAddress) { + // An example from RFC 4291 section 2.7.1 + EXPECT_EQ(IPAddressV6("ff02::1:ff0e:8c6c"), + IPAddressV6("4037::01:800:200e:8c6c").getSolicitedNodeAddress()); + + // An example from wikipedia + // (http://en.wikipedia.org/wiki/Solicited-node_multicast_address) + EXPECT_EQ(IPAddressV6("ff02::1:ff28:9c5a"), + IPAddressV6("fe80::2aa:ff:fe28:9c5a").getSolicitedNodeAddress()); +} + +TEST_P(IPAddressByteAccessorTest, CheckBytes) { + auto addrData = GetParam(); + IPAddress ip(addrData.address); + auto i = 0; + for (auto byitr = addrData.bytes.begin(); i < ip.byteCount(); ++i, ++byitr) { + EXPECT_EQ(*byitr, ip.getNthMSByte(i)); + EXPECT_EQ(*byitr, ip.isV4() ? + ip.asV4().getNthMSByte(i) : ip.asV6().getNthMSByte(i)); + } + i = 0; + for (auto byritr = addrData.bytes.rbegin(); i < ip.byteCount(); ++i, + ++byritr) { + EXPECT_EQ(*byritr, ip.getNthLSByte(i)); + EXPECT_EQ(*byritr, ip.isV4() ? + ip.asV4().getNthLSByte(i) : ip.asV6().getNthLSByte(i)); + } +} + +TEST_P(IPAddressBitAccessorTest, CheckBits) { + auto addrData = GetParam(); + auto littleEndianAddrData = addrData.bytes; + //IPAddress stores address data in n/w byte order. + reverse(littleEndianAddrData.begin(), littleEndianAddrData.end()); + //Bit iterator goes from LSBit to MSBit + //We will traverse the IPAddress bits from 0 to bitCount -1 + auto bitr = folly::makeBitIterator(littleEndianAddrData.begin()); + IPAddress ip(addrData.address); + for (auto i = 0; i < ip.bitCount(); ++i) { + auto msbIndex = ip.bitCount() - i - 1; + EXPECT_EQ(*bitr, ip.getNthMSBit(msbIndex)); + EXPECT_EQ(*bitr, ip.isV4() ? ip.asV4().getNthMSBit(msbIndex) : + ip.asV6().getNthMSBit(msbIndex)); + EXPECT_EQ(*bitr, ip.getNthLSBit(i)); + EXPECT_EQ(*bitr, ip.isV4() ? ip.asV4().getNthLSBit(i) : + ip.asV6().getNthLSBit(i)); + ++bitr; + } +} + +TEST(IPAddress, InvalidByteAccess) { + IPAddress ip4("10.10.10.10"); + //MSByte, LSByte accessors are 0 indexed + EXPECT_THROW(ip4.getNthMSByte(ip4.byteCount()), std::invalid_argument); + EXPECT_THROW(ip4.getNthLSByte(ip4.byteCount()), std::invalid_argument); + EXPECT_THROW(ip4.getNthMSByte(-1), std::invalid_argument); + EXPECT_THROW(ip4.getNthLSByte(-1), std::invalid_argument); + auto asV4 = ip4.asV4(); + EXPECT_THROW(asV4.getNthMSByte(asV4.byteCount()), std::invalid_argument); + EXPECT_THROW(asV4.getNthLSByte(asV4.byteCount()), std::invalid_argument); + EXPECT_THROW(asV4.getNthMSByte(-1), std::invalid_argument); + EXPECT_THROW(asV4.getNthLSByte(-1), std::invalid_argument); + + IPAddress ip6("2620:0:1cfe:face:b00c::3"); + EXPECT_THROW(ip6.getNthMSByte(ip6.byteCount()), std::invalid_argument); + EXPECT_THROW(ip6.getNthLSByte(ip6.byteCount()), std::invalid_argument); + EXPECT_THROW(ip6.getNthMSByte(-1), std::invalid_argument); + EXPECT_THROW(ip6.getNthLSByte(-1), std::invalid_argument); + auto asV6 = ip6.asV6(); + EXPECT_THROW(asV6.getNthMSByte(asV6.byteCount()), std::invalid_argument); + EXPECT_THROW(asV6.getNthLSByte(asV6.byteCount()), std::invalid_argument); + EXPECT_THROW(asV6.getNthMSByte(-1), std::invalid_argument); + EXPECT_THROW(asV6.getNthLSByte(-1), std::invalid_argument); + +} + +TEST(IPAddress, InvalidBBitAccess) { + IPAddress ip4("10.10.10.10"); + //MSByte, LSByte accessors are 0 indexed + EXPECT_THROW(ip4.getNthMSBit(ip4.bitCount()), std::invalid_argument); + EXPECT_THROW(ip4.getNthLSBit(ip4.bitCount()), std::invalid_argument); + EXPECT_THROW(ip4.getNthMSBit(-1), std::invalid_argument); + EXPECT_THROW(ip4.getNthLSBit(-1), std::invalid_argument); + auto asV4 = ip4.asV4(); + EXPECT_THROW(asV4.getNthMSBit(asV4.bitCount()), std::invalid_argument); + EXPECT_THROW(asV4.getNthLSBit(asV4.bitCount()), std::invalid_argument); + EXPECT_THROW(asV4.getNthMSBit(-1), std::invalid_argument); + EXPECT_THROW(asV4.getNthLSBit(-1), std::invalid_argument); + + IPAddress ip6("2620:0:1cfe:face:b00c::3"); + EXPECT_THROW(ip6.getNthMSBit(ip6.bitCount()), std::invalid_argument); + EXPECT_THROW(ip6.getNthLSBit(ip6.bitCount()), std::invalid_argument); + EXPECT_THROW(ip6.getNthMSBit(-1), std::invalid_argument); + EXPECT_THROW(ip6.getNthLSBit(-1), std::invalid_argument); + auto asV6 = ip6.asV6(); + EXPECT_THROW(asV6.getNthMSBit(asV6.bitCount()), std::invalid_argument); + EXPECT_THROW(asV6.getNthLSBit(asV6.bitCount()), std::invalid_argument); + EXPECT_THROW(asV6.getNthMSBit(-1), std::invalid_argument); + EXPECT_THROW(asV6.getNthLSBit(-1), std::invalid_argument); +} + +TEST(IPAddress, LongestCommonPrefix) { + IPAddress ip10("10.0.0.0"); + IPAddress ip11("11.0.0.0"); + IPAddress ip12("12.0.0.0"); + IPAddress ip128("128.0.0.0"); + IPAddress ip10dot10("10.10.0.0"); + auto prefix = IPAddress::longestCommonPrefix({ip10, 8}, {ip128, 8}); + auto prefix4 = IPAddressV4::longestCommonPrefix({ip10.asV4(), 8}, + {ip128.asV4(), 8}); + // No bits match b/w 128/8 and 10/8 + EXPECT_EQ(IPAddress("0.0.0.0"), prefix.first); + EXPECT_EQ(0, prefix.second); + EXPECT_EQ(IPAddressV4("0.0.0.0"), prefix4.first); + EXPECT_EQ(0, prefix4.second); + + prefix = IPAddress::longestCommonPrefix({ip10, 8}, {ip10dot10, 16}); + prefix4 = IPAddressV4::longestCommonPrefix({ip10.asV4(), 8}, + {ip10dot10.asV4(), 16}); + // Between 10/8 and 10.10/16, 10/8 is the longest common match + EXPECT_EQ(ip10, prefix.first); + EXPECT_EQ(8, prefix.second); + EXPECT_EQ(ip10.asV4(), prefix4.first); + EXPECT_EQ(8, prefix4.second); + + prefix = IPAddress::longestCommonPrefix({ip11, 8}, {ip12, 8}); + prefix4 = IPAddressV4::longestCommonPrefix({ip11.asV4(), 8}, + {ip12.asV4(), 8}); + // 12 = 1100, 11 = 1011, longest match - 1000 = 8 + EXPECT_EQ(IPAddress("8.0.0.0"), prefix.first); + EXPECT_EQ(5, prefix.second); + EXPECT_EQ(IPAddressV4("8.0.0.0"), prefix4.first); + EXPECT_EQ(5, prefix4.second); + + // Between 128/1 and 128/2, longest match 128/1 + prefix = IPAddress::longestCommonPrefix({ip128, 1}, {ip128, 2}); + prefix4 = IPAddressV4::longestCommonPrefix({ip128.asV4(), 1}, + {ip128.asV4(), 2}); + EXPECT_EQ(ip128, prefix.first); + EXPECT_EQ(1, prefix.second); + EXPECT_EQ(ip128.asV4(), prefix4.first); + EXPECT_EQ(1, prefix4.second); + + IPAddress ip6("2620:0:1cfe:face:b00c::3"); + prefix = IPAddress::longestCommonPrefix({ip6, ip6.bitCount()}, + {ip6, ip6.bitCount()}); + auto prefix6 = IPAddressV6::longestCommonPrefix( + {ip6.asV6(), IPAddressV6::bitCount()}, + {ip6.asV6(), IPAddressV6::bitCount()}); + // Longest common b/w me and myself is myself + EXPECT_EQ(ip6, prefix.first); + EXPECT_EQ(ip6.bitCount(), prefix.second); + EXPECT_EQ(ip6.asV6(), prefix6.first); + EXPECT_EQ(ip6.asV6().bitCount(), prefix6.second); + + IPAddress ip6Zero("::"); + prefix = IPAddress::longestCommonPrefix({ip6, ip6.bitCount()}, {ip6Zero, 0}); + prefix6 = IPAddressV6::longestCommonPrefix( + {ip6.asV6(), IPAddressV6::bitCount()}, + {ip6Zero.asV6(), 0}); + // Longest common b/w :: (ipv6 equivalent of 0/0) is :: + EXPECT_EQ(ip6Zero, prefix.first); + EXPECT_EQ(0, prefix.second); + + // Exceptional cases + EXPECT_THROW(IPAddress::longestCommonPrefix({ip10, 8}, {ip6, 128}), + std::invalid_argument); + EXPECT_THROW(IPAddress::longestCommonPrefix({ip10, ip10.bitCount() + 1}, + {ip10, 8}), + std::invalid_argument); + EXPECT_THROW(IPAddressV4::longestCommonPrefix( + {ip10.asV4(), IPAddressV4::bitCount() + 1}, + {ip10.asV4(), 8}), + std::invalid_argument); + EXPECT_THROW(IPAddress::longestCommonPrefix({ip6, ip6.bitCount() + 1}, + {ip6, ip6.bitCount()}), + std::invalid_argument); + EXPECT_THROW(IPAddressV6::longestCommonPrefix( + {ip6.asV6(), IPAddressV6::bitCount() + 1}, + {ip6.asV6(), IPAddressV6::bitCount()}), + std::invalid_argument); + +} + +static vector validAddressProvider = { + AddressData("127.0.0.1", {127,0,0,1}, 4), + AddressData("69.63.189.16", {69,63,189,16}, 4), + AddressData("0.0.0.0", {0,0,0,0}, 4), + AddressData("::1", + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, 6), + AddressData("2620:0:1cfe:face:b00c::3", + {38,32,0,0,28,254,250,206,176,12,0,0,0,0,0,3}, 6), +}; + +static vector invalidAddressProvider = { + "", + "foo", + "1.1.1.256", + "1", + ":1", + "127.0.0.1,127.0.0.1", + "[1234]", +}; + +static vector invalidBinaryProvider = { + {0x31, 0x32, 0x37, 0x2e, 0x30, 0x30, 0x2e, 0x30, 0x2e, 0x31}, + // foo + {0x66, 0x6f, 0x6f}, + {0x00}, + {0x00, 0x00}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00}, + {0xff}, +}; + +static const uint8_t IS_LOCAL = AddressFlags::IS_LOCAL; +static const uint8_t IS_NONROUTABLE = AddressFlags::IS_NONROUTABLE; +static const uint8_t IS_PRIVATE = AddressFlags::IS_PRIVATE; +static const uint8_t IS_ZERO = AddressFlags::IS_ZERO; +static const uint8_t IS_LINK_LOCAL = + AddressFlags::IS_LINK_LOCAL | IS_NONROUTABLE; +static const uint8_t IS_PVT_NONROUTE = IS_NONROUTABLE | IS_PRIVATE; +static const uint8_t IS_MULTICAST = AddressFlags::IS_MULTICAST; +static const uint8_t IS_LINK_LOCAL_BROADCAST = +AddressFlags::IS_LINK_LOCAL_BROADCAST; + +static vector flagProvider = { + // public v4 + AddressFlags("69.63.176.1", 4, 0), + AddressFlags("128.12.65.3", 4, 0), + AddressFlags("192.0.1.0", 4, 0), + AddressFlags("198.51.101.0", 4, 0), + AddressFlags("203.0.114.0", 4, 0), + AddressFlags("128.12.64.115", 4, 0), + + // public v6 + AddressFlags("2620:0:1cfe:face:b00c::3", 6, 0), + + // localhost + AddressFlags("127.0.0.1", 4, IS_LOCAL | IS_PVT_NONROUTE), + AddressFlags("::1", 6, IS_LOCAL | IS_PVT_NONROUTE), + + // private v4 + AddressFlags("10.0.0.0", 4, IS_PVT_NONROUTE), + AddressFlags("10.11.12.13", 4, IS_PVT_NONROUTE), + AddressFlags("10.255.255.255", 4, IS_PVT_NONROUTE), + AddressFlags("127.128.129.200", 4, IS_PVT_NONROUTE), + AddressFlags("127.255.255.255", 4, IS_PVT_NONROUTE), + AddressFlags("169.254.0.0", 4, IS_PVT_NONROUTE), + AddressFlags("192.168.0.0", 4, IS_PVT_NONROUTE), + AddressFlags("192.168.200.255", 4, IS_PVT_NONROUTE), + AddressFlags("192.168.255.255", 4, IS_PVT_NONROUTE), + + // private v6 + AddressFlags("fd01:1637:1c56:66af::", 6, IS_PVT_NONROUTE), + + // non routable v4 + AddressFlags("0.0.0.0", 4, IS_NONROUTABLE | IS_ZERO), + AddressFlags("0.255.255.255", 4, IS_NONROUTABLE), + AddressFlags("192.0.0.0", 4, IS_NONROUTABLE), + AddressFlags("192.0.2.0", 4, IS_NONROUTABLE), + AddressFlags("198.18.0.0", 4, IS_NONROUTABLE), + AddressFlags("198.19.255.255", 4, IS_NONROUTABLE), + AddressFlags("198.51.100.0", 4, IS_NONROUTABLE), + AddressFlags("198.51.100.255", 4, IS_NONROUTABLE), + AddressFlags("203.0.113.0", 4, IS_NONROUTABLE), + AddressFlags("203.0.113.255", 4, IS_NONROUTABLE), + AddressFlags("224.0.0.0", 4, IS_NONROUTABLE | IS_MULTICAST), + AddressFlags("240.0.0.0", 4, IS_NONROUTABLE), + AddressFlags("224.0.0.0", 4, IS_NONROUTABLE), + // v4 link local broadcast + AddressFlags("255.255.255.255", 4, IS_NONROUTABLE | IS_LINK_LOCAL_BROADCAST), + + // non routable v6 + AddressFlags("1999::1", 6, IS_NONROUTABLE), + AddressFlags("0::0", 6, IS_NONROUTABLE | IS_ZERO), + AddressFlags("0::0:0", 6, IS_NONROUTABLE | IS_ZERO), + AddressFlags("0:0:0::0", 6, IS_NONROUTABLE | IS_ZERO), + + // link-local v6 + AddressFlags("fe80::0205:73ff:fef9:46fc", 6, IS_LINK_LOCAL), + AddressFlags("fe80::0012:34ff:fe56:7890", 6, IS_LINK_LOCAL), + + // multicast v4 + AddressFlags("224.0.0.1", 4, IS_MULTICAST | IS_NONROUTABLE) , + AddressFlags("224.0.0.251", 4, IS_MULTICAST | IS_NONROUTABLE), + AddressFlags("239.12.34.56", 4, IS_MULTICAST | IS_NONROUTABLE), + + // multicast v6 + AddressFlags("ff00::", 6, IS_MULTICAST | IS_NONROUTABLE), + AddressFlags("ff02:ffff::1", 6, IS_MULTICAST | IS_NONROUTABLE), + AddressFlags("ff02::101", 6, IS_MULTICAST | IS_NONROUTABLE), + AddressFlags("ff0e::101", 6, IS_MULTICAST), + // v6 link local broadcast + AddressFlags("ff02::1", 6, IS_NONROUTABLE | IS_LINK_LOCAL_BROADCAST), +}; + +static vector > mapProvider = { + {"::ffff:192.0.2.128", "192.0.2.128"}, + {"192.0.2.128", "::ffff:192.0.2.128"}, + {"::FFFF:129.144.52.38", "129.144.52.38"}, + {"129.144.52.38", "::FFFF:129.144.52.38"}, + {"0:0:0:0:0:FFFF:222.1.41.90", "222.1.41.90"}, + {"::FFFF:222.1.41.90", "222.1.41.90"}, +}; + +static vector masksProvider = { + MaskData("255.255.255.255", 1, "128.0.0.0"), + MaskData("255.255.255.255", 2, "192.0.0.0"), + MaskData("192.0.2.42", 16, "192.0.0.0"), + MaskData("255.255.255.255", 24, "255.255.255.0"), + MaskData("255.255.255.255", 32, "255.255.255.255"), + MaskData("10.10.10.10", 0, "0.0.0.0"), + MaskData("::1", 64, "::"), + MaskData("2620:0:1cfe:face:b00c::3", 1, "::"), + MaskData("2620:0:1cfe:face:b00c::3", 3, "2000::"), + MaskData("2620:0:1cfe:face:b00c::3", 6, "2400::"), + MaskData("2620:0:1cfe:face:b00c::3", 7, "2600::"), + MaskData("2620:0:1cfe:face:b00c::3", 11, "2620::"), + MaskData("2620:0:1cfe:face:b00c::3", 36, "2620:0:1000::"), + MaskData("2620:0:1cfe:face:b00c::3", 37, "2620:0:1800::"), + MaskData("2620:0:1cfe:face:b00c::3", 38, "2620:0:1c00::"), + MaskData("2620:0:1cfe:face:b00c::3", 41, "2620:0:1c80::"), + MaskData("2620:0:1cfe:face:b00c::3", 42, "2620:0:1cc0::"), + MaskData("2620:0:1cfe:face:b00c::3", 43, "2620:0:1ce0::"), + MaskData("2620:0:1cfe:face:b00c::3", 44, "2620:0:1cf0::"), + MaskData("2620:0:1cfe:face:b00c::3", 45, "2620:0:1cf8::"), + MaskData("2620:0:1cfe:face:b00c::3", 46, "2620:0:1cfc::"), + MaskData("2620:0:1cfe:face:b00c::3", 47, "2620:0:1cfe::"), + MaskData("2620:0:1cfe:face:b00c::3", 49, "2620:0:1cfe:8000::"), + MaskData("2620:0:1cfe:face:b00c::3", 50, "2620:0:1cfe:c000::"), + MaskData("2620:0:1cfe:face:b00c::3", 51, "2620:0:1cfe:e000::"), + MaskData("2620:0:1cfe:face:b00c::3", 52, "2620:0:1cfe:f000::"), + MaskData("2620:0:1cfe:face:b00c::3", 53, "2620:0:1cfe:f800::"), + MaskData("2620:0:1cfe:face:b00c::3", 55, "2620:0:1cfe:fa00::"), + MaskData("2620:0:1cfe:face:b00c::3", 57, "2620:0:1cfe:fa80::"), + MaskData("2620:0:1cfe:face:b00c::3", 58, "2620:0:1cfe:fac0::"), + MaskData("2620:0:1cfe:face:b00c::3", 61, "2620:0:1cfe:fac8::"), + MaskData("2620:0:1cfe:face:b00c::3", 62, "2620:0:1cfe:facc::"), + MaskData("2620:0:1cfe:face:b00c::3", 63, "2620:0:1cfe:face::"), + MaskData("2620:0:1cfe:face:b00c::3", 65, "2620:0:1cfe:face:8000::"), + MaskData("2620:0:1cfe:face:b00c::3", 67, "2620:0:1cfe:face:a000::"), + MaskData("2620:0:1cfe:face:b00c::3", 68, "2620:0:1cfe:face:b000::"), + MaskData("2620:0:1cfe:face:b00c::3", 77, "2620:0:1cfe:face:b008::"), + MaskData("2620:0:1cfe:face:b00c::3", 78, "2620:0:1cfe:face:b00c::"), + MaskData("2620:0:1cfe:face:b00c::3", 127, "2620:0:1cfe:face:b00c::2"), + MaskData("2620:0:1cfe:face:b00c::3", 128, "2620:0:1cfe:face:b00c::3"), + MaskData("2620:0:1cfe:face:b00c::3", 0, "::") +}; + +static vector maskBoundaryProvider = { + MaskBoundaryData("10.1.1.1", 24, "10.1.1.1", true), + MaskBoundaryData("10.1.1.1", 8, "10.1.2.3", true), + MaskBoundaryData("2620:0:1cfe:face:b00c::1", 48, "2620:0:1cfe::", true), + // addresses that are NOT in the same subnet once mask is applied + MaskBoundaryData("10.1.1.1", 24, "10.1.2.1", false), + MaskBoundaryData("10.1.1.1", 16, "10.2.3.4", false), + MaskBoundaryData("2620:0:1cfe:face:b00c::1", 48, "2620:0:1cfc::", false), +}; + +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressTest, + ::testing::ValuesIn(validAddressProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressFlagTest, + ::testing::ValuesIn(flagProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressMappedTest, + ::testing::ValuesIn(mapProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressCtorTest, + ::testing::ValuesIn(invalidAddressProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressCtorBinaryTest, + ::testing::ValuesIn(invalidBinaryProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressMaskTest, + ::testing::ValuesIn(masksProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressMaskBoundaryTest, + ::testing::ValuesIn(maskBoundaryProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressByteAccessorTest, + ::testing::ValuesIn(validAddressProvider)); +INSTANTIATE_TEST_CASE_P(IPAddress, + IPAddressBitAccessorTest, + ::testing::ValuesIn(validAddressProvider)); diff --git a/folly/test/IPAddressTest.h b/folly/test/IPAddressTest.h new file mode 100644 index 00000000..a5f4d893 --- /dev/null +++ b/folly/test/IPAddressTest.h @@ -0,0 +1,166 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +extern "C" { +#include +#include +#include +} + +#include "folly/IPAddress.h" + +namespace folly { + +class IPAddress; + +typedef std::vector ByteVector; + +struct AddressData { + std::string address; + ByteVector bytes; + uint8_t version; + + AddressData(const std::string& address, const ByteVector& bytes, + uint8_t version) + : address(address), bytes(bytes), version(version) {} + AddressData(const std::string& address, uint8_t version) + : address(address), bytes(), version(version) {} + explicit AddressData(const std::string& address) + : address(address), bytes(), version(0) {} + AddressData(): address(""), bytes(), version(0) {} + + static in_addr parseAddress4(const std::string& src) { + in_addr addr; + inet_pton(AF_INET, src.c_str(), &addr); + return addr; + } + + static in6_addr parseAddress6(const std::string& src) { + in6_addr addr; + inet_pton(AF_INET6, src.c_str(), &addr); + return addr; + } +}; + +struct AddressFlags { + std::string address; + uint8_t flags; + uint8_t version; + + static const uint8_t IS_LOCAL = 1 << 0; + static const uint8_t IS_NONROUTABLE = 1 << 1; + static const uint8_t IS_PRIVATE = 1 << 2; + static const uint8_t IS_ZERO = 1 << 3; + static const uint8_t IS_LINK_LOCAL = 1 << 4; + static const uint8_t IS_MULTICAST = 1 << 5; + static const uint8_t IS_LINK_LOCAL_BROADCAST = 1 << 6; + + AddressFlags(const std::string& addr, uint8_t version, uint8_t flags) + : address(addr) + , flags(flags) + , version(version) + {} + + bool isLoopback() const { + return (flags & IS_LOCAL); + } + bool isNonroutable() const { + return (flags & IS_NONROUTABLE); + } + bool isPrivate() const { + return (flags & IS_PRIVATE); + } + bool isZero() const { + return (flags & IS_ZERO); + } + bool isLinkLocal() const { + return (flags & IS_LINK_LOCAL); + } + bool isLinkLocalBroadcast() const { + return (flags & IS_LINK_LOCAL_BROADCAST); + } +}; + +struct MaskData { + std::string address; + uint8_t mask; + std::string subnet; + MaskData(const std::string& addr, uint8_t mask, + const std::string& subnet) + : address(addr) + , mask(mask) + , subnet(subnet) + {} +}; + +struct MaskBoundaryData : MaskData { + bool inSubnet; + MaskBoundaryData(const std::string& addr, uint8_t mask, + const std::string& subnet, bool inSubnet) + : MaskData(addr, mask, subnet) + , inSubnet(inSubnet) + {} +}; + +struct SerializeData { + std::string address; + ByteVector bytes; + + SerializeData(const std::string& addr, const ByteVector& bytes) + : address(addr) + , bytes(bytes) + {} +}; + +struct IPAddressTest : public ::testing::TestWithParam { + void ExpectIsValid(const IPAddress& addr) { + AddressData param = GetParam(); + EXPECT_EQ(param.version, addr.version()); + EXPECT_EQ(param.address, addr.str()); + if (param.version == 4) { + in_addr v4addr = AddressData::parseAddress4(param.address); + EXPECT_EQ(0, memcmp(&v4addr, addr.asV4().toByteArray().data(), 4)); + EXPECT_TRUE(addr.isV4()); + EXPECT_FALSE(addr.isV6()); + } else { + in6_addr v6addr = AddressData::parseAddress6(param.address); + EXPECT_EQ(0, memcmp(&v6addr, addr.asV6().toByteArray().data(), 16)); + EXPECT_TRUE(addr.isV6()); + EXPECT_FALSE(addr.isV4()); + } + } +}; +struct IPAddressFlagTest : public ::testing::TestWithParam {}; +struct IPAddressCtorTest : public ::testing::TestWithParam {}; +struct IPAddressCtorBinaryTest : public ::testing::TestWithParam {}; +struct IPAddressMappedTest : + public ::testing::TestWithParam > {}; +struct IPAddressMaskTest : public ::testing::TestWithParam {}; +struct IPAddressMaskBoundaryTest : + public ::testing::TestWithParam {}; +struct IPAddressSerializeTest : + public ::testing::TestWithParam {}; +struct IPAddressByteAccessorTest: + public ::testing::TestWithParam {}; +struct IPAddressBitAccessorTest: + public ::testing::TestWithParam {}; +} // folly diff --git a/folly/test/MacAddressTest.cpp b/folly/test/MacAddressTest.cpp new file mode 100644 index 00000000..0cea2b82 --- /dev/null +++ b/folly/test/MacAddressTest.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "folly/IPAddressV6.h" +#include "folly/MacAddress.h" + +using folly::MacAddress; +using folly::IPAddressV6; +using folly::StringPiece; + +void testMAC(const std::string& str, uint64_t expectedHBO) { + SCOPED_TRACE(str); + MacAddress addr(str); + // Make sure parsing returned the expected value. + EXPECT_EQ(expectedHBO, addr.u64HBO()); + + // Perform additional checks on the MacAddress + + // Check using operator==() + EXPECT_EQ(MacAddress::fromHBO(expectedHBO), addr); + // Check using operator==() when passing in non-zero padding bytes + EXPECT_EQ(MacAddress::fromHBO(expectedHBO | 0xa5a5000000000000), addr); + + // Similar checks after converting to network byte order + uint64_t expectedNBO = folly::Endian::big(expectedHBO); + EXPECT_EQ(expectedNBO, addr.u64NBO()); + EXPECT_EQ(MacAddress::fromNBO(expectedNBO), addr); + uint64_t nboWithPad = folly::Endian::big(expectedHBO | 0xa5a5000000000000); + EXPECT_EQ(MacAddress::fromNBO(nboWithPad), addr); + + // Check they value returned by bytes() + uint8_t expectedBytes[8]; + memcpy(expectedBytes, &expectedNBO, 8); + for (int n = 0; n < 6; ++n) { + EXPECT_EQ(expectedBytes[n + 2], addr.bytes()[n]); + } +} + +TEST(MacAddress, parse) { + testMAC("12:34:56:78:9a:bc", 0x123456789abc); + testMAC("00-11-22-33-44-55", 0x1122334455); + testMAC("abcdef123456", 0xabcdef123456); + testMAC("1:2:3:4:5:6", 0x010203040506); + testMAC("0:0:0:0:0:0", 0); + testMAC("0:0:5e:0:1:1", 0x00005e000101); + + EXPECT_THROW(MacAddress(""), std::invalid_argument); + EXPECT_THROW(MacAddress("0"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:34"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:3"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:x4:56:78:9a:bc"), std::invalid_argument); + EXPECT_THROW(MacAddress("12x34:56:78:9a:bc"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:34:56:78:9a:bc:de"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:34:56:78:9a:bcde"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:34:56:78:9a:bc "), std::invalid_argument); + EXPECT_THROW(MacAddress(" 12:34:56:78:9a:bc"), std::invalid_argument); + EXPECT_THROW(MacAddress("12:34:56:78:-1:bc"), std::invalid_argument); +} + +void testFromBinary(const char* str, uint64_t expectedHBO) { + StringPiece bin(str, 6); + auto mac = MacAddress::fromBinary(bin); + SCOPED_TRACE(mac.toString()); + EXPECT_EQ(expectedHBO, mac.u64HBO()); +} + +TEST(MacAddress, fromBinary) { + testFromBinary("\0\0\0\0\0\0", 0); + testFromBinary("\x12\x34\x56\x78\x9a\xbc", 0x123456789abc); + testFromBinary("\x11\x22\x33\x44\x55\x66", 0x112233445566); + + StringPiece empty(""); + EXPECT_THROW(MacAddress::fromBinary(empty), std::invalid_argument); + StringPiece tooShort("\x11", 1); + EXPECT_THROW(MacAddress::fromBinary(tooShort), std::invalid_argument); + StringPiece tooLong("\x11\x22\x33\x44\x55\x66\x77", 7); + EXPECT_THROW(MacAddress::fromBinary(tooLong), std::invalid_argument); +} + +TEST(MacAddress, toString) { + EXPECT_EQ("12:34:56:78:9a:bc", + MacAddress::fromHBO(0x123456789abc).toString()); + EXPECT_EQ("12:34:56:78:9a:bc", MacAddress("12:34:56:78:9a:bc").toString()); + EXPECT_EQ("01:23:45:67:89:ab", MacAddress("01-23-45-67-89-ab").toString()); + EXPECT_EQ("01:23:45:67:89:ab", MacAddress("0123456789ab").toString()); +} + +TEST(MacAddress, attributes) { + EXPECT_TRUE(MacAddress("ff:ff:ff:ff:ff:ff").isBroadcast()); + EXPECT_FALSE(MacAddress("7f:ff:ff:ff:ff:ff").isBroadcast()); + EXPECT_FALSE(MacAddress("7f:ff:ff:ff:ff:fe").isBroadcast()); + EXPECT_FALSE(MacAddress("00:00:00:00:00:00").isBroadcast()); + EXPECT_TRUE(MacAddress::fromNBO(0xffffffffffffffffU).isBroadcast()); + + EXPECT_TRUE(MacAddress("ff:ff:ff:ff:ff:ff").isMulticast()); + EXPECT_TRUE(MacAddress("01:00:00:00:00:00").isMulticast()); + EXPECT_FALSE(MacAddress("00:00:00:00:00:00").isMulticast()); + EXPECT_FALSE(MacAddress("fe:ff:ff:ff:ff:ff").isMulticast()); + EXPECT_FALSE(MacAddress("00:00:5e:00:01:01").isMulticast()); + + EXPECT_FALSE(MacAddress("ff:ff:ff:ff:ff:ff").isUnicast()); + EXPECT_FALSE(MacAddress("01:00:00:00:00:00").isUnicast()); + EXPECT_TRUE(MacAddress("00:00:00:00:00:00").isUnicast()); + EXPECT_TRUE(MacAddress("fe:ff:ff:ff:ff:ff").isUnicast()); + EXPECT_TRUE(MacAddress("00:00:5e:00:01:01").isUnicast()); + + EXPECT_TRUE(MacAddress("ff:ff:ff:ff:ff:ff").isLocallyAdministered()); + EXPECT_TRUE(MacAddress("02:00:00:00:00:00").isLocallyAdministered()); + EXPECT_FALSE(MacAddress("01:00:00:00:00:00").isLocallyAdministered()); + EXPECT_FALSE(MacAddress("00:00:00:00:00:00").isLocallyAdministered()); + EXPECT_FALSE(MacAddress("fd:ff:ff:ff:ff:ff").isLocallyAdministered()); + EXPECT_TRUE(MacAddress("fe:ff:ff:ff:ff:ff").isLocallyAdministered()); + EXPECT_FALSE(MacAddress("00:00:5e:00:01:01").isLocallyAdministered()); + EXPECT_TRUE(MacAddress("02:12:34:56:78:9a").isLocallyAdministered()); +} + +TEST(MacAddress, createMulticast) { + EXPECT_EQ(MacAddress("33:33:00:01:00:03"), + MacAddress::createMulticast(IPAddressV6("ff02:dead:beef::1:3"))); + EXPECT_EQ(MacAddress("33:33:12:34:56:78"), + MacAddress::createMulticast(IPAddressV6("ff02::abcd:1234:5678"))); +} + +void testCmp(const char* str1, const char* str2) { + SCOPED_TRACE(folly::to(str1, " < ", str2)); + MacAddress m1(str1); + MacAddress m2(str2); + + // Test the comparison operators + EXPECT_TRUE(m1 < m2); + EXPECT_FALSE(m1 < m1); + EXPECT_TRUE(m1 <= m2); + EXPECT_TRUE(m2 > m1); + EXPECT_TRUE(m2 >= m1); + EXPECT_TRUE(m1 != m2); + EXPECT_TRUE(m1 == m1); + EXPECT_FALSE(m1 == m2); + + // Also test the copy constructor and assignment operator + MacAddress copy(m1); + EXPECT_EQ(copy, m1); + copy = m2; + EXPECT_EQ(copy, m2); +} + +TEST(MacAddress, ordering) { + testCmp("00:00:00:00:00:00", "00:00:00:00:00:01"); + testCmp("00:00:00:00:00:01", "00:00:00:00:00:02"); + testCmp("01:00:00:00:00:00", "02:00:00:00:00:00"); + testCmp("00:00:00:00:00:01", "00:00:00:00:01:00"); +} -- 2.34.1