| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/inet/ip_address.h" |
| |
| #include <arpa/inet.h> |
| #include <endian.h> |
| #include <netdb.h> |
| #include <sys/socket.h> |
| |
| #include <sstream> |
| |
| namespace inet { |
| namespace { |
| |
| // Parses a string. Match functions either return true and update the position |
| // of the parser or return false and leave the position unchanged. |
| class Parser { |
| public: |
| Parser(const std::string& str) : str_(str), pos_(0) {} |
| |
| // Matches end-of-string. |
| bool MatchEnd() { return pos_ == str_.length(); } |
| |
| // Matches a specified character. |
| bool Match(char to_match) { |
| if (pos_ == str_.length() || str_[pos_] != to_match) { |
| return false; |
| } |
| |
| ++pos_; |
| |
| return true; |
| } |
| |
| // Matches a single decimal digit. |
| bool MatchDecDigit(uint8_t* out) { |
| FXL_DCHECK(out); |
| |
| if (pos_ == str_.length() || !std::isdigit(static_cast<unsigned char>(str_[pos_]))) { |
| return false; |
| } |
| |
| *out = str_[pos_] - '0'; |
| ++pos_; |
| |
| return true; |
| } |
| |
| // Matches a single lowercase hexadecimal digit. |
| bool MatchLowerHexDigit(uint8_t* out) { |
| FXL_DCHECK(out); |
| |
| if (pos_ == str_.length() || !std::isxdigit(static_cast<unsigned char>(str_[pos_]))) { |
| return false; |
| } |
| |
| if (std::isdigit(static_cast<unsigned char>(str_[pos_]))) { |
| *out = str_[pos_] - '0'; |
| } else if (std::islower(static_cast<unsigned char>(str_[pos_]))) { |
| *out = 10 + (str_[pos_] - 'a'); |
| } else { |
| // Uppercase hexadecimal is not permitted. |
| return false; |
| } |
| |
| ++pos_; |
| |
| return true; |
| } |
| |
| // Matches a decimal byte of at most 3 digits. The match will succeed even |
| // if the decimal byte is followed immediately by a digit. If matching three |
| // digits would produce a value greater than 255, only two digits are matched. |
| bool MatchMax3DigitDecByte(uint8_t* byte_out) { |
| FXL_DCHECK(byte_out); |
| |
| uint8_t digit = 0; |
| if (!MatchDecDigit(&digit)) { |
| return false; |
| } |
| |
| uint16_t accum = digit; |
| if (MatchDecDigit(&digit)) { |
| accum = accum * 10 + digit; |
| if (accum <= 25 && MatchDecDigit(&digit)) { |
| if (accum < 25 || digit < 6) { |
| accum = accum * 10 + digit; |
| } else { |
| // Including that last digit would produce a value > 255. |
| --pos_; |
| } |
| } |
| } |
| |
| FXL_DCHECK(accum <= 255); |
| *byte_out = static_cast<uint8_t>(accum); |
| return true; |
| } |
| |
| // Matches a lowercase hexadecimal word of at most 4 digits. The match will |
| // succeed even if the hexadecimal word is followed immediately by a |
| // hexadecimal digit. |
| bool MatchMax4DigitLowerHexWord(uint16_t* word_out) { |
| FXL_DCHECK(word_out); |
| |
| uint8_t digit = 0; |
| if (!MatchLowerHexDigit(&digit)) { |
| return false; |
| } |
| |
| uint16_t accum = digit; |
| if (MatchLowerHexDigit(&digit)) { |
| accum = accum * 16 + digit; |
| if (MatchLowerHexDigit(&digit)) { |
| accum = accum * 16 + digit; |
| if (MatchLowerHexDigit(&digit)) { |
| accum = accum * 16 + digit; |
| } |
| } |
| } |
| |
| *word_out = accum; |
| return true; |
| } |
| |
| // Matches an IPV4 address. |
| bool MatchIpV4Address(IpAddress* address_out) { |
| FXL_DCHECK(address_out); |
| |
| size_t old_pos = pos_; |
| uint8_t b0, b1, b2, b3; |
| if (MatchMax3DigitDecByte(&b0) && Match('.') && MatchMax3DigitDecByte(&b1) && Match('.') && |
| MatchMax3DigitDecByte(&b2) && Match('.') && MatchMax3DigitDecByte(&b3)) { |
| *address_out = IpAddress(b0, b1, b2, b3); |
| return true; |
| } |
| |
| pos_ = old_pos; |
| return false; |
| } |
| |
| // Matches an IPV6 address. |
| bool MatchIpV6Address(IpAddress* address_out) { |
| FXL_DCHECK(address_out); |
| |
| size_t old_pos = pos_; |
| uint16_t words[8]; |
| size_t word_index = 0; |
| size_t ellipsis_word_index = 0; |
| |
| if (MatchMax4DigitLowerHexWord(&words[word_index]) && Match(':')) { |
| while (true) { |
| // At this point, we've matched at least one word, we've just matched a |
| // colon, and |word_index| indexes the last word we matched. |
| ++word_index; |
| |
| // Check for "::" ellipsis. |
| if (Match(':')) { |
| if (ellipsis_word_index != 0) { |
| // More than one "::" ellipsis. |
| break; |
| } |
| |
| ellipsis_word_index = word_index; |
| } |
| |
| if (MatchMax4DigitLowerHexWord(&words[word_index])) { |
| if (word_index < 7 && Match(':')) { |
| // More words to read. |
| continue; |
| } |
| } else if (word_index == 1) { |
| // Need at least two words. |
| break; |
| } else { |
| // We've read a ':' past the end. |
| --pos_; |
| } |
| |
| if (word_index == 7) { |
| if (ellipsis_word_index != 0) { |
| // We parsed 8 words, and there's an ellipsis. |
| break; |
| } |
| } else { |
| if (ellipsis_word_index == 0) { |
| // We parsed less than 8 words, and there's no ellipsis. |
| break; |
| } |
| |
| // Insert zeros for the ellipsis. |
| size_t to = 7; |
| for (size_t from = word_index; from >= ellipsis_word_index; --from) { |
| words[to] = words[from]; |
| --to; |
| } |
| |
| for (; to >= ellipsis_word_index; --to) { |
| words[to] = 0; |
| } |
| } |
| |
| *address_out = IpAddress(words[0], words[1], words[2], words[3], words[4], words[5], |
| words[6], words[7]); |
| return true; |
| } |
| } |
| |
| pos_ = old_pos; |
| return false; |
| } |
| |
| private: |
| const std::string& str_; |
| size_t pos_; |
| }; |
| |
| } // namespace |
| |
| // static |
| const IpAddress IpAddress::kInvalid; |
| // static |
| const IpAddress IpAddress::kV4Loopback(127, 0, 0, 1); |
| // static |
| const IpAddress IpAddress::kV6Loopback(0, 0, 0, 0, 0, 0, 0, 1); |
| |
| // static |
| IpAddress IpAddress::FromString(const std::string address_string, sa_family_t family) { |
| FXL_DCHECK(family == AF_UNSPEC || family == AF_INET || family == AF_INET6); |
| |
| Parser parser(address_string); |
| IpAddress address; |
| if ((parser.MatchIpV4Address(&address) || parser.MatchIpV6Address(&address)) && |
| parser.MatchEnd()) { |
| return address; |
| } |
| |
| return kInvalid; |
| } |
| |
| IpAddress::IpAddress() { |
| family_ = AF_UNSPEC; |
| std::memset(&v6_, 0, sizeof(v6_)); |
| } |
| |
| IpAddress::IpAddress(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) { |
| family_ = AF_INET; |
| uint8_t* bytes = reinterpret_cast<uint8_t*>(&v4_.s_addr); |
| bytes[0] = b0; |
| bytes[1] = b1; |
| bytes[2] = b2; |
| bytes[3] = b3; |
| } |
| |
| IpAddress::IpAddress(in_addr_t addr) { |
| family_ = AF_INET; |
| v4_.s_addr = addr; |
| } |
| |
| IpAddress::IpAddress(const in_addr& addr) { |
| family_ = AF_INET; |
| v4_ = addr; |
| } |
| |
| IpAddress::IpAddress(uint16_t w0, uint16_t w1, uint16_t w2, uint16_t w3, uint16_t w4, uint16_t w5, |
| uint16_t w6, uint16_t w7) { |
| family_ = AF_INET6; |
| uint16_t* words = v6_.s6_addr16; |
| words[0] = htobe16(w0); |
| words[1] = htobe16(w1); |
| words[2] = htobe16(w2); |
| words[3] = htobe16(w3); |
| words[4] = htobe16(w4); |
| words[5] = htobe16(w5); |
| words[6] = htobe16(w6); |
| words[7] = htobe16(w7); |
| } |
| |
| IpAddress::IpAddress(uint16_t w0, uint16_t w7) { |
| family_ = AF_INET6; |
| std::memset(&v6_, 0, sizeof(v6_)); |
| uint16_t* words = v6_.s6_addr16; |
| words[0] = htobe16(w0); |
| words[7] = htobe16(w7); |
| } |
| |
| IpAddress::IpAddress(const in6_addr& addr) { |
| family_ = AF_INET6; |
| v6_ = addr; |
| } |
| |
| IpAddress::IpAddress(const sockaddr* addr) { |
| FXL_DCHECK(addr != nullptr); |
| switch (addr->sa_family) { |
| case AF_INET: |
| family_ = AF_INET; |
| v4_ = *reinterpret_cast<const in_addr*>(addr->sa_data); |
| break; |
| case AF_INET6: |
| family_ = AF_INET6; |
| v6_ = *reinterpret_cast<const in6_addr*>(addr->sa_data); |
| break; |
| default: |
| family_ = AF_UNSPEC; |
| std::memset(&v6_, 0, sizeof(v6_)); |
| break; |
| } |
| } |
| |
| IpAddress::IpAddress(const sockaddr_storage& addr) { |
| switch (addr.ss_family) { |
| case AF_INET: |
| family_ = AF_INET; |
| v4_ = *reinterpret_cast<const in_addr*>(reinterpret_cast<const uint8_t*>(&addr) + |
| sizeof(sa_family_t)); |
| break; |
| case AF_INET6: |
| family_ = AF_INET6; |
| v6_ = *reinterpret_cast<const in6_addr*>(reinterpret_cast<const uint8_t*>(&addr) + |
| sizeof(sa_family_t)); |
| break; |
| default: |
| family_ = AF_UNSPEC; |
| std::memset(&v6_, 0, sizeof(v6_)); |
| break; |
| } |
| } |
| |
| IpAddress::IpAddress(const fuchsia::net::Ipv4Address* addr) { |
| FXL_DCHECK(addr != nullptr); |
| family_ = AF_INET; |
| memcpy(&v4_, addr->addr.data(), 4); |
| } |
| |
| IpAddress::IpAddress(const fuchsia::net::Ipv6Address* addr) { |
| FXL_DCHECK(addr != nullptr); |
| family_ = AF_INET6; |
| memcpy(&v6_, addr->addr.data(), 16); |
| } |
| |
| IpAddress::IpAddress(const fuchsia::net::IpAddress* addr) { |
| FXL_DCHECK(addr != nullptr); |
| switch (addr->Which()) { |
| case fuchsia::net::IpAddress::Tag::kIpv4: |
| family_ = AF_INET; |
| memcpy(&v4_, addr->ipv4().addr.data(), 4); |
| break; |
| case fuchsia::net::IpAddress::Tag::kIpv6: |
| family_ = AF_INET6; |
| memcpy(&v6_, addr->ipv6().addr.data(), 16); |
| break; |
| default: |
| FXL_DCHECK(false); |
| break; |
| } |
| } |
| |
| bool IpAddress::is_mapped_from_v4() const { |
| // A V6 address mapped from a V4 address takes the form 0::ffff:xxxx:xxxx, where the x's make |
| // up the V4 address. |
| return is_v6() && v6_.s6_addr16[0] == 0 && v6_.s6_addr16[1] == 0 && v6_.s6_addr16[2] == 0 && |
| v6_.s6_addr16[3] == 0 && v6_.s6_addr16[4] == 0 && v6_.s6_addr16[5] == 0xffff; |
| } |
| |
| IpAddress IpAddress::mapped_v4_address() const { |
| FXL_DCHECK(is_mapped_from_v4()); |
| auto bytes = as_bytes(); |
| return IpAddress(bytes[12], bytes[13], bytes[14], bytes[15]); |
| } |
| |
| IpAddress IpAddress::mapped_as_v6() const { |
| FXL_DCHECK(is_v4()); |
| auto bytes = as_bytes(); |
| // The words passed in to this constructor are stored in big-endian order. |
| return IpAddress(0, 0, 0, 0, 0, 0xffff, static_cast<uint16_t>(bytes[0]) << 8 | bytes[1], |
| static_cast<uint16_t>(bytes[2]) << 8 | bytes[3]); |
| } |
| |
| bool IpAddress::is_loopback() const { |
| switch (family_) { |
| case AF_INET: |
| return *this == kV4Loopback; |
| case AF_INET6: |
| return *this == kV6Loopback; |
| default: |
| return false; |
| } |
| } |
| |
| std::string IpAddress::ToString() const { |
| std::ostringstream os; |
| os << *this; |
| return os.str(); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const IpAddress& value) { |
| if (!value.is_valid()) { |
| return os << "<invalid>"; |
| } |
| |
| if (value.is_v4()) { |
| const uint8_t* bytes = value.as_bytes(); |
| return os << static_cast<int>(bytes[0]) << '.' << static_cast<int>(bytes[1]) << '.' |
| << static_cast<int>(bytes[2]) << '.' << static_cast<int>(bytes[3]); |
| } else { |
| // IPV6 text representation per RFC 5952: |
| // 1) Suppress leading zeros in hex representation of words. |
| // 2) Don't use '::' to shorten a just single zero word. |
| // 3) Shorten the longest sequence of zero words preferring the leftmost |
| // sequence if there's a tie. |
| // 4) Use lower-case hexadecimal. |
| |
| const uint16_t* words = value.as_words(); |
| |
| // Figure out where the longest span of zeros is. |
| uint8_t start_of_zeros; |
| uint8_t zeros_seen = 0; |
| uint8_t start_of_best_zeros = 255; |
| // Don't bother if the longest sequence is length 1. |
| uint8_t best_zeros_seen = 1; |
| |
| for (uint8_t i = 0; i < 8; ++i) { |
| if (words[i] == 0) { |
| if (zeros_seen == 0) { |
| start_of_zeros = i; |
| } |
| ++zeros_seen; |
| } else if (zeros_seen != 0) { |
| if (zeros_seen > best_zeros_seen) { |
| start_of_best_zeros = start_of_zeros; |
| best_zeros_seen = zeros_seen; |
| } |
| zeros_seen = 0; |
| } |
| } |
| |
| if (zeros_seen > best_zeros_seen) { |
| start_of_best_zeros = start_of_zeros; |
| best_zeros_seen = zeros_seen; |
| } |
| |
| os << std::hex; |
| for (uint8_t i = 0; i < 8; ++i) { |
| if (i < start_of_best_zeros || i >= start_of_best_zeros + best_zeros_seen) { |
| os << betoh16(words[i]); |
| if (i != 7) { |
| os << ":"; |
| } |
| } else if (i == start_of_best_zeros) { |
| if (i == 0) { |
| os << "::"; |
| } else { |
| os << ":"; // We just wrote a ':', so we only need one more. |
| } |
| } |
| } |
| return os << std::dec; |
| } |
| } |
| |
| } // namespace inet |