| 1 | /* |
| 2 | * Copyright 2014-present Facebook, Inc. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #pragma once |
| 18 | |
| 19 | #include <cstring> |
| 20 | |
| 21 | #include <array> |
| 22 | #include <functional> |
| 23 | #include <iosfwd> |
| 24 | #include <map> |
| 25 | #include <stdexcept> |
| 26 | |
| 27 | #include <folly/Expected.h> |
| 28 | #include <folly/FBString.h> |
| 29 | #include <folly/IPAddressException.h> |
| 30 | #include <folly/Optional.h> |
| 31 | #include <folly/Range.h> |
| 32 | #include <folly/detail/IPAddress.h> |
| 33 | #include <folly/hash/Hash.h> |
| 34 | |
| 35 | namespace folly { |
| 36 | |
| 37 | class IPAddress; |
| 38 | class IPAddressV4; |
| 39 | class IPAddressV6; |
| 40 | class MacAddress; |
| 41 | |
| 42 | /** |
| 43 | * Pair of IPAddressV6, netmask |
| 44 | */ |
| 45 | typedef std::pair<IPAddressV6, uint8_t> CIDRNetworkV6; |
| 46 | |
| 47 | /** |
| 48 | * Specialization for IPv6 addresses |
| 49 | */ |
| 50 | typedef std::array<uint8_t, 16> ByteArray16; |
| 51 | |
| 52 | /** |
| 53 | * IPv6 variation of IPAddress. |
| 54 | * |
| 55 | * Added methods: createIPv4, getIPv4For6To4, is6To4, |
| 56 | * isTeredo, isIPv4Mapped, tryCreateIPv4, type |
| 57 | * |
| 58 | * @see IPAddress |
| 59 | * |
| 60 | * Notes on scope ID parsing: |
| 61 | * |
| 62 | * getaddrinfo() uses if_nametoindex() to convert interface names |
| 63 | * into a numerical index. For instance, |
| 64 | * "fe80::202:c9ff:fec1:ee08%eth0" may return scope ID 2 on some |
| 65 | * hosts, but other numbers on other hosts. It will fail entirely on |
| 66 | * hosts without an eth0 interface. |
| 67 | * |
| 68 | * Serializing / Deserializing IPAddressB6's on different hosts |
| 69 | * that use link-local scoping probably won't work. |
| 70 | */ |
| 71 | class IPAddressV6 { |
| 72 | public: |
| 73 | // V6 Address Type |
| 74 | enum Type { |
| 75 | TEREDO, |
| 76 | T6TO4, |
| 77 | NORMAL, |
| 78 | }; |
| 79 | // A constructor parameter to indicate that we should create a link-local |
| 80 | // IPAddressV6. |
| 81 | enum LinkLocalTag { |
| 82 | LINK_LOCAL, |
| 83 | }; |
| 84 | // Thrown when a type assertion fails |
| 85 | typedef std::runtime_error TypeError; |
| 86 | |
| 87 | // Binary prefix for teredo networks |
| 88 | static const uint32_t PREFIX_TEREDO; |
| 89 | // Binary prefix for 6to4 networks |
| 90 | static const uint32_t PREFIX_6TO4; |
| 91 | |
| 92 | // Size of std::string returned by toFullyQualified. |
| 93 | static constexpr size_t kToFullyQualifiedSize = |
| 94 | 8 /*words*/ * 4 /*hex chars per word*/ + 7 /*separators*/; |
| 95 | |
| 96 | // returns true iff the input string can be parsed as an ipv6-address |
| 97 | static bool validate(StringPiece ip) noexcept; |
| 98 | |
| 99 | /** |
| 100 | * Create a new IPAddress instance from the provided binary data. |
| 101 | * @throws IPAddressFormatException if the input length is not 16 bytes. |
| 102 | */ |
| 103 | static IPAddressV6 fromBinary(ByteRange bytes); |
| 104 | |
| 105 | /** |
| 106 | * Non-throwing version of fromBinary(). |
| 107 | * On failure returns IPAddressFormatError. |
| 108 | */ |
| 109 | static Expected<IPAddressV6, IPAddressFormatError> tryFromBinary( |
| 110 | ByteRange bytes) noexcept; |
| 111 | |
| 112 | /** |
| 113 | * Tries to create a new IPAddressV6 instance from provided string and |
| 114 | * returns it on success. Returns IPAddressFormatError on failure. |
| 115 | */ |
| 116 | static Expected<IPAddressV6, IPAddressFormatError> tryFromString( |
| 117 | StringPiece str) noexcept; |
| 118 | |
| 119 | /** |
| 120 | * Create a new IPAddress instance from the ip6.arpa representation. |
| 121 | * @throws IPAddressFormatException if the input is not a valid ip6.arpa |
| 122 | * representation |
| 123 | */ |
| 124 | static IPAddressV6 fromInverseArpaName(const std::string& arpaname); |
| 125 | |
| 126 | /** |
| 127 | * Returns the address as a Range. |
| 128 | */ |
| 129 | ByteRange toBinary() const { |
| 130 | return ByteRange((const unsigned char*)&addr_.in6Addr_.s6_addr, 16); |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Default constructor for IPAddressV6. |
| 135 | * |
| 136 | * The address value will be ::0 |
| 137 | */ |
| 138 | IPAddressV6(); |
| 139 | |
| 140 | // Create an IPAddressV6 from a string |
| 141 | // @throws IPAddressFormatException |
| 142 | // |
| 143 | explicit IPAddressV6(StringPiece ip); |
| 144 | |
| 145 | // ByteArray16 constructor |
| 146 | explicit IPAddressV6(const ByteArray16& src) noexcept; |
| 147 | |
| 148 | // in6_addr constructor |
| 149 | explicit IPAddressV6(const in6_addr& src) noexcept; |
| 150 | |
| 151 | // sockaddr_in6 constructor |
| 152 | explicit IPAddressV6(const sockaddr_in6& src) noexcept; |
| 153 | |
| 154 | /** |
| 155 | * Create a link-local IPAddressV6 from the specified ethernet MAC address. |
| 156 | */ |
| 157 | IPAddressV6(LinkLocalTag tag, MacAddress mac); |
| 158 | |
| 159 | // return the mapped V4 address |
| 160 | // @throws IPAddressFormatException if !isIPv4Mapped |
| 161 | IPAddressV4 createIPv4() const; |
| 162 | |
| 163 | /** |
| 164 | * Return a V4 address if this is a 6To4 address. |
| 165 | * @throws TypeError if not a 6To4 address |
| 166 | */ |
| 167 | IPAddressV4 getIPv4For6To4() const; |
| 168 | |
| 169 | // Return true if a 6TO4 address |
| 170 | bool is6To4() const { |
| 171 | return type() == IPAddressV6::Type::T6TO4; |
| 172 | } |
| 173 | |
| 174 | // Return true if a TEREDO address |
| 175 | bool isTeredo() const { |
| 176 | return type() == IPAddressV6::Type::TEREDO; |
| 177 | } |
| 178 | |
| 179 | // return true if this is v4-to-v6-mapped |
| 180 | bool isIPv4Mapped() const; |
| 181 | |
| 182 | // Return the V6 address type |
| 183 | Type type() const; |
| 184 | |
| 185 | /** |
| 186 | * @see IPAddress#bitCount |
| 187 | * @returns 128 |
| 188 | */ |
| 189 | static constexpr size_t bitCount() { |
| 190 | return 128; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * @see IPAddress#toJson |
| 195 | */ |
| 196 | std::string toJson() const; |
| 197 | |
| 198 | size_t hash() const; |
| 199 | |
| 200 | // @see IPAddress#inSubnet |
| 201 | // @throws IPAddressFormatException if string doesn't contain a V6 address |
| 202 | bool inSubnet(StringPiece cidrNetwork) const; |
| 203 | |
| 204 | // return true if address is in subnet |
| 205 | bool inSubnet(const IPAddressV6& subnet, uint8_t cidr) const { |
| 206 | return inSubnetWithMask(subnet, fetchMask(cidr)); |
| 207 | } |
| 208 | bool inSubnetWithMask(const IPAddressV6& subnet, const ByteArray16& mask) |
| 209 | const; |
| 210 | |
| 211 | // @see IPAddress#isLoopback |
| 212 | bool isLoopback() const; |
| 213 | |
| 214 | // @see IPAddress#isNonroutable |
| 215 | bool isNonroutable() const { |
| 216 | return !isRoutable(); |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Return true if this address is routable. |
| 221 | */ |
| 222 | bool isRoutable() const; |
| 223 | |
| 224 | // @see IPAddress#isPrivate |
| 225 | bool isPrivate() const; |
| 226 | |
| 227 | /** |
| 228 | * Return true if this is a link-local IPv6 address. |
| 229 | * |
| 230 | * Note that this only returns true for addresses in the fe80::/10 range. |
| 231 | * It returns false for the loopback address (::1), even though this address |
| 232 | * is also effectively has link-local scope. It also returns false for |
| 233 | * link-scope and interface-scope multicast addresses. |
| 234 | */ |
| 235 | bool isLinkLocal() const; |
| 236 | |
| 237 | /** |
| 238 | * Return the mac address if this is a link-local IPv6 address. |
| 239 | * |
| 240 | * @return an Optional<MacAddress> union representing the mac address. |
| 241 | * |
| 242 | * If the address is not a link-local one it will return an empty Optional. |
| 243 | * You can use Optional::value() to check whether the mac address is not null. |
| 244 | */ |
| 245 | Optional<MacAddress> getMacAddressFromLinkLocal() const; |
| 246 | |
| 247 | /** |
| 248 | * Return the mac address if this is an auto-configured IPv6 address based on |
| 249 | * EUI-64 |
| 250 | * |
| 251 | * @return an Optional<MacAddress> union representing the mac address. |
| 252 | * If the address is not based on EUI-64 it will return an empty Optional. |
| 253 | * You can use Optional::value() to check whether the mac address is not null. |
| 254 | */ |
| 255 | Optional<MacAddress> getMacAddressFromEUI64() const; |
| 256 | |
| 257 | /** |
| 258 | * Return true if this is a multicast address. |
| 259 | */ |
| 260 | bool isMulticast() const; |
| 261 | |
| 262 | /** |
| 263 | * Return the flags for a multicast address. |
| 264 | * This method may only be called on multicast addresses. |
| 265 | */ |
| 266 | uint8_t getMulticastFlags() const; |
| 267 | |
| 268 | /** |
| 269 | * Return the scope for a multicast address. |
| 270 | * This method may only be called on multicast addresses. |
| 271 | */ |
| 272 | uint8_t getMulticastScope() const; |
| 273 | |
| 274 | // @see IPAddress#isZero |
| 275 | bool isZero() const { |
| 276 | constexpr auto zero = ByteArray16{{}}; |
| 277 | return 0 == std::memcmp(bytes(), zero.data(), zero.size()); |
| 278 | } |
| 279 | |
| 280 | bool isLinkLocalBroadcast() const; |
| 281 | |
| 282 | // @see IPAddress#mask |
| 283 | IPAddressV6 mask(size_t numBits) const; |
| 284 | |
| 285 | // return underlying in6_addr structure |
| 286 | in6_addr toAddr() const { |
| 287 | return addr_.in6Addr_; |
| 288 | } |
| 289 | |
| 290 | uint16_t getScopeId() const { |
| 291 | return scope_; |
| 292 | } |
| 293 | void setScopeId(uint16_t scope) { |
| 294 | scope_ = scope; |
| 295 | } |
| 296 | |
| 297 | sockaddr_in6 toSockAddr() const { |
| 298 | sockaddr_in6 addr; |
| 299 | memset(&addr, 0, sizeof(sockaddr_in6)); |
| 300 | addr.sin6_family = AF_INET6; |
| 301 | addr.sin6_scope_id = scope_; |
| 302 | memcpy(&addr.sin6_addr, &addr_.in6Addr_, sizeof(in6_addr)); |
| 303 | return addr; |
| 304 | } |
| 305 | |
| 306 | ByteArray16 toByteArray() const { |
| 307 | ByteArray16 ba{{0}}; |
| 308 | std::memcpy(ba.data(), bytes(), 16); |
| 309 | return ba; |
| 310 | } |
| 311 | |
| 312 | // @see IPAddress#toFullyQualified |
| 313 | std::string toFullyQualified() const; |
| 314 | |
| 315 | // @see IPAddress#toFullyQualifiedAppend |
| 316 | void toFullyQualifiedAppend(std::string& out) const; |
| 317 | |
| 318 | std::string toInverseArpaName() const; |
| 319 | |
| 320 | // @see IPAddress#str |
| 321 | std::string str() const; |
| 322 | |
| 323 | // @see IPAddress#version |
| 324 | uint8_t version() const { |
| 325 | return 6; |
| 326 | } |
| 327 | |
| 328 | /** |
| 329 | * Return the solicited-node multicast address for this address. |
| 330 | */ |
| 331 | IPAddressV6 getSolicitedNodeAddress() const; |
| 332 | |
| 333 | /** |
| 334 | * Return the mask associated with the given number of bits. |
| 335 | * If for instance numBits was 24 (e.g. /24) then the V4 mask returned should |
| 336 | * be {0xff, 0xff, 0xff, 0x00}. |
| 337 | * @param [in] numBits bitmask to retrieve |
| 338 | * @throws abort if numBits == 0 or numBits > bitCount() |
| 339 | * @return mask associated with numBits |
| 340 | */ |
| 341 | static const ByteArray16 fetchMask(size_t numBits); |
| 342 | // Given 2 IPAddressV6,mask pairs extract the longest common IPAddress, |
| 343 | // mask pair |
| 344 | static CIDRNetworkV6 longestCommonPrefix( |
| 345 | const CIDRNetworkV6& one, |
| 346 | const CIDRNetworkV6& two); |
| 347 | // Number of bytes in the address representation. |
| 348 | static constexpr size_t byteCount() { |
| 349 | return 16; |
| 350 | } |
| 351 | |
| 352 | // get nth most significant bit - 0 indexed |
| 353 | bool getNthMSBit(size_t bitIndex) const { |
| 354 | return detail::getNthMSBitImpl(*this, bitIndex, AF_INET6); |
| 355 | } |
| 356 | // get nth most significant byte - 0 indexed |
| 357 | uint8_t getNthMSByte(size_t byteIndex) const; |
| 358 | // get nth bit - 0 indexed |
| 359 | bool getNthLSBit(size_t bitIndex) const { |
| 360 | return getNthMSBit(bitCount() - bitIndex - 1); |
| 361 | } |
| 362 | // get nth byte - 0 indexed |
| 363 | uint8_t getNthLSByte(size_t byteIndex) const { |
| 364 | return getNthMSByte(byteCount() - byteIndex - 1); |
| 365 | } |
| 366 | |
| 367 | const unsigned char* bytes() const { |
| 368 | return addr_.in6Addr_.s6_addr; |
| 369 | } |
| 370 | |
| 371 | protected: |
| 372 | /** |
| 373 | * Helper that returns true if the address is in the binary subnet specified |
| 374 | * by addr. |
| 375 | */ |
| 376 | bool inBinarySubnet(const std::array<uint8_t, 2> addr, size_t numBits) const; |
| 377 | |
| 378 | private: |
| 379 | auto tie() const { |
| 380 | return std::tie(addr_.bytes_, scope_); |
| 381 | } |
| 382 | |
| 383 | public: |
| 384 | friend inline bool operator==(const IPAddressV6& a, const IPAddressV6& b) { |
| 385 | return a.tie() == b.tie(); |
| 386 | } |
| 387 | friend inline bool operator!=(const IPAddressV6& a, const IPAddressV6& b) { |
| 388 | return a.tie() != b.tie(); |
| 389 | } |
| 390 | friend inline bool operator<(const IPAddressV6& a, const IPAddressV6& b) { |
| 391 | return a.tie() < b.tie(); |
| 392 | } |
| 393 | friend inline bool operator>(const IPAddressV6& a, const IPAddressV6& b) { |
| 394 | return a.tie() > b.tie(); |
| 395 | } |
| 396 | friend inline bool operator<=(const IPAddressV6& a, const IPAddressV6& b) { |
| 397 | return a.tie() <= b.tie(); |
| 398 | } |
| 399 | friend inline bool operator>=(const IPAddressV6& a, const IPAddressV6& b) { |
| 400 | return a.tie() >= b.tie(); |
| 401 | } |
| 402 | |
| 403 | private: |
| 404 | union AddressStorage { |
| 405 | in6_addr in6Addr_; |
| 406 | ByteArray16 bytes_; |
| 407 | AddressStorage() { |
| 408 | std::memset(this, 0, sizeof(AddressStorage)); |
| 409 | } |
| 410 | explicit AddressStorage(const ByteArray16& bytes) : bytes_(bytes) {} |
| 411 | explicit AddressStorage(const in6_addr& addr) : in6Addr_(addr) {} |
| 412 | explicit AddressStorage(MacAddress mac); |
| 413 | } addr_; |
| 414 | |
| 415 | // Link-local scope id. This should always be 0 for IPAddresses that |
| 416 | // are *not* link-local. |
| 417 | uint16_t scope_{0}; |
| 418 | |
| 419 | /** |
| 420 | * Set the current IPAddressV6 object to have the address specified by bytes. |
| 421 | * Returns IPAddressFormatError if bytes.size() is not 16. |
| 422 | */ |
| 423 | Expected<Unit, IPAddressFormatError> trySetFromBinary( |
| 424 | ByteRange bytes) noexcept; |
| 425 | }; |
| 426 | |
| 427 | // boost::hash uses hash_value() so this allows boost::hash to work |
| 428 | // automatically for IPAddressV6 |
| 429 | std::size_t hash_value(const IPAddressV6& addr); |
| 430 | std::ostream& operator<<(std::ostream& os, const IPAddressV6& addr); |
| 431 | // Define toAppend() to allow IPAddressV6 to be used with to<string> |
| 432 | void toAppend(IPAddressV6 addr, std::string* result); |
| 433 | void toAppend(IPAddressV6 addr, fbstring* result); |
| 434 | |
| 435 | } // namespace folly |
| 436 | |
| 437 | namespace std { |
| 438 | template <> |
| 439 | struct hash<folly::IPAddressV6> { |
| 440 | size_t operator()(const folly::IPAddressV6& addr) const { |
| 441 | return addr.hash(); |
| 442 | } |
| 443 | }; |
| 444 | } // namespace std |
| 445 | |