| // 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) { | |
| FX_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) { | |
| FX_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) { | |
| FX_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_; | |
| } | |
| } | |
| } | |
| FX_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) { | |
| FX_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) { | |
| FX_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) { | |
| FX_DCHECK(address_out); | |
| size_t old_pos = pos_; | |
| uint16_t words[8]; | |
| size_t word_index = 0; | |
| ssize_t ellipsis_word_index = -1; | |
| if (Match(':')) { | |
| ellipsis_word_index = 0; | |
| words[0] = 0; | |
| } else if (!MatchMax4DigitLowerHexWord(&words[word_index])) { | |
| pos_ = old_pos; | |
| return false; | |
| } | |
| if (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 != -1) { | |
| // 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 { | |
| // We didn't match that last word. | |
| --word_index; | |
| if (word_index == 0) { | |
| if (ellipsis_word_index != 1) { | |
| // Need at least two words, if we don't have a trailing pair of colons. | |
| break; | |
| } | |
| } else { | |
| // We've read a ':' past the end. | |
| --pos_; | |
| } | |
| } | |
| if (word_index == 7) { | |
| if (ellipsis_word_index != -1) { | |
| // We parsed 8 words, and there's an ellipsis. | |
| break; | |
| } | |
| } else { | |
| if (ellipsis_word_index == -1) { | |
| // We parsed less than 8 words, and there's no ellipsis. | |
| break; | |
| } | |
| // Insert zeros for the ellipsis. | |
| ssize_t to = 7; | |
| for (ssize_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) { | |
| FX_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) { | |
| FX_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) { | |
| FX_DCHECK(addr != nullptr); | |
| family_ = AF_INET; | |
| memcpy(&v4_, addr->addr.data(), 4); | |
| } | |
| IpAddress::IpAddress(const fuchsia::net::Ipv6Address* addr) { | |
| FX_DCHECK(addr != nullptr); | |
| family_ = AF_INET6; | |
| memcpy(&v6_, addr->addr.data(), 16); | |
| } | |
| IpAddress::IpAddress(const fuchsia::net::IpAddress* addr) { | |
| FX_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: | |
| FX_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 { | |
| FX_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 { | |
| FX_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 |