[libc-commits] [libc] [libc] Implement IPv6 (AF_INET6) support in inet_pton (PR #226550)

Aman Maurya via libc-commits libc-commits at lists.llvm.org
Fri Sep 25 19:34:47 PDT 2026


https://github.com/amanmaurya92 updated https://github.com/llvm/llvm-project/pull/226550

>From a7d7e7cdcf676c85df3256534f1b7a2286ec73f1 Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Fri, 25 Sep 2026 21:06:55 +0530
Subject: [PATCH] [libc] Implement IPv6 (AF_INET6) support in inet_pton

Add RFC 4291 / POSIX.1-2024 compliant IPv6 text parsing support (str_to_ipv6) to libc/src/__support/net/address.cpp, enabling AF_INET6 handling in inet_pton.

Also promotes inet_pton from experimental to standard entrypoints across x86_64, aarch64, and riscv now that IPv6 support is complete.

Fixes #226460
---
 libc/config/linux/aarch64/entrypoints.txt  |   4 +-
 libc/config/linux/riscv/entrypoints.txt    |   4 +-
 libc/config/linux/x86_64/entrypoints.txt   |   4 +-
 libc/src/__support/net/address.cpp         | 101 ++++++++++++++
 libc/src/__support/net/address.h           |   1 +
 libc/src/arpa/inet/inet_pton.cpp           |  11 +-
 libc/test/src/arpa/inet/CMakeLists.txt     |   1 +
 libc/test/src/arpa/inet/inet_pton_test.cpp | 146 +++++++++++++++++++++
 8 files changed, 258 insertions(+), 14 deletions(-)

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 15a5367f5fc95..4239a3b808acd 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1134,6 +1134,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.arpa.inet.inet_aton
     libc.src.arpa.inet.inet_ntoa
     libc.src.arpa.inet.inet_ntop
+    libc.src.arpa.inet.inet_pton
     libc.src.arpa.inet.ntohl
     libc.src.arpa.inet.ntohs
 
@@ -1458,9 +1459,6 @@ endif()
 if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
   if(LLVM_LIBC_FULL_BUILD)
     list(APPEND TARGET_LIBC_ENTRYPOINTS
-      # arpa/inet.h entrypoints
-      libc.src.arpa.inet.inet_pton
-
       # fnmatch.h entrypoints
       libc.src.fnmatch.fnmatch
 
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 8f4dded27de68..d03f2fef11584 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -6,6 +6,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.arpa.inet.inet_aton
     libc.src.arpa.inet.inet_ntoa
     libc.src.arpa.inet.inet_ntop
+    libc.src.arpa.inet.inet_pton
     libc.src.arpa.inet.ntohl
     libc.src.arpa.inet.ntohs
 
@@ -1670,9 +1671,6 @@ endif()
 if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
   if(LLVM_LIBC_FULL_BUILD)
     list(APPEND TARGET_LIBC_ENTRYPOINTS
-      # arpa/inet.h entrypoints
-      libc.src.arpa.inet.inet_pton
-
       # fnmatch.h entrypoints
       libc.src.fnmatch.fnmatch
 
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 8ca73cc82cc2d..2d0d60e3bf13c 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -6,6 +6,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.arpa.inet.inet_aton
     libc.src.arpa.inet.inet_ntoa
     libc.src.arpa.inet.inet_ntop
+    libc.src.arpa.inet.inet_pton
     libc.src.arpa.inet.ntohl
     libc.src.arpa.inet.ntohs
 
@@ -1696,9 +1697,6 @@ endif()
 if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
   if(LLVM_LIBC_FULL_BUILD)
     list(APPEND TARGET_LIBC_ENTRYPOINTS
-      # arpa/inet.h entrypoints
-      libc.src.arpa.inet.inet_pton
-
       # fnmatch.h entrypoints
       libc.src.fnmatch.fnmatch
 
diff --git a/libc/src/__support/net/address.cpp b/libc/src/__support/net/address.cpp
index c4bf5432bc4ca..61c6c5bc52762 100644
--- a/libc/src/__support/net/address.cpp
+++ b/libc/src/__support/net/address.cpp
@@ -67,6 +67,107 @@ namespace net {
   return true;
 }
 
+namespace {
+
+LIBC_INLINE constexpr bool is_hex_char(char c) {
+  return internal::isalnum(c) && internal::b36_char_to_int(c) < 16;
+}
+
+} // anonymous namespace
+
+[[nodiscard]] bool str_to_ipv6(cpp::string_view src, struct in6_addr &dst) {
+  if (src.empty())
+    return false;
+
+  uint8_t bytes[16] = {0};
+  size_t cur_byte = 0;
+  int double_colon_byte = -1;
+
+  if (src.starts_with("::")) {
+    double_colon_byte = 0;
+    src.remove_prefix(2);
+    if (src.empty()) {
+      inline_memcpy(&dst.s6_addr, bytes, 16);
+      return true;
+    }
+  } else if (src[0] == ':') {
+    return false;
+  }
+
+  uint32_t val = 0;
+  size_t num_digits = 0;
+  size_t token_start = 0;
+
+  for (size_t i = 0; i < src.size(); ++i) {
+    char c = src[i];
+    if (is_hex_char(c)) {
+      if (++num_digits > 4)
+        return false;
+      if (num_digits == 1)
+        token_start = i;
+      val = (val << 4) | static_cast<uint32_t>(internal::b36_char_to_int(c));
+    } else if (c == ':') {
+      if (num_digits == 0) {
+        if (double_colon_byte != -1)
+          return false;
+        double_colon_byte = static_cast<int>(cur_byte);
+        continue;
+      }
+      if (i + 1 == src.size())
+        return false; // Trailing single colon
+
+      if (cur_byte + 2 > 16)
+        return false;
+
+      bytes[cur_byte++] = static_cast<uint8_t>(val >> 8);
+      bytes[cur_byte++] = static_cast<uint8_t>(val & 0xff);
+      val = 0;
+      num_digits = 0;
+    } else if (c == '.') {
+      if (num_digits == 0 || cur_byte + 4 > 16)
+        return false;
+
+      cpp::string_view ipv4_str = src.substr(token_start);
+      struct in_addr in4;
+      if (!str_to_ipv4(ipv4_str, in4))
+        return false;
+
+      inline_memcpy(&bytes[cur_byte], &in4.s_addr, 4);
+      cur_byte += 4;
+      num_digits = 0;
+      break;
+    } else {
+      return false;
+    }
+  }
+
+  if (num_digits > 0) {
+    if (cur_byte + 2 > 16)
+      return false;
+    bytes[cur_byte++] = static_cast<uint8_t>(val >> 8);
+    bytes[cur_byte++] = static_cast<uint8_t>(val & 0xff);
+  }
+
+  if (double_colon_byte != -1) {
+    if (cur_byte >= 16)
+      return false;
+
+    size_t bytes_after = cur_byte - static_cast<size_t>(double_colon_byte);
+    for (size_t k = bytes_after; k > 0; --k)
+      bytes[16 - bytes_after + (k - 1)] =
+          bytes[static_cast<size_t>(double_colon_byte) + (k - 1)];
+
+    size_t gap = 16 - cur_byte;
+    for (size_t k = 0; k < gap; ++k)
+      bytes[static_cast<size_t>(double_colon_byte) + k] = 0;
+  } else if (cur_byte != 16) {
+    return false;
+  }
+
+  inline_memcpy(&dst.s6_addr, bytes, 16);
+  return true;
+}
+
 cpp::optional<in_addr_t> inet_addr(cpp::string_view src) {
   constexpr int IPV4_MAX_DOT_NUM = 3;
   in_addr_t parts[IPV4_MAX_DOT_NUM + 1] = {0};
diff --git a/libc/src/__support/net/address.h b/libc/src/__support/net/address.h
index 1ad3ab04415be..065dd4f30fae9 100644
--- a/libc/src/__support/net/address.h
+++ b/libc/src/__support/net/address.h
@@ -37,6 +37,7 @@ cpp::optional<in_addr_t> inet_addr(cpp::string_view src);
 /// provided address into the destination buffer. In case of error, returns
 /// false and does not modify the buffer.
 [[nodiscard]] bool ipv6_to_str(const struct in6_addr &src, cpp::span<char> dst);
+[[nodiscard]] bool str_to_ipv6(cpp::string_view src, struct in6_addr &dst);
 
 } // namespace net
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/arpa/inet/inet_pton.cpp b/libc/src/arpa/inet/inet_pton.cpp
index 9e933de108af4..16597db5acd5a 100644
--- a/libc/src/arpa/inet/inet_pton.cpp
+++ b/libc/src/arpa/inet/inet_pton.cpp
@@ -29,11 +29,12 @@ LLVM_LIBC_FUNCTION(int, inet_pton,
                    (int af, const char *__restrict src, void *__restrict dst)) {
   LIBC_CRASH_ON_NULLPTR(src);
   LIBC_CRASH_ON_NULLPTR(dst);
-  if (af != AF_INET) {
-    libc_errno = EAFNOSUPPORT;
-    return -1;
-  }
-  return net::str_to_ipv4(src, *reinterpret_cast<struct in_addr *>(dst));
+  if (af == AF_INET)
+    return net::str_to_ipv4(src, *reinterpret_cast<struct in_addr *>(dst));
+  if (af == AF_INET6)
+    return net::str_to_ipv6(src, *reinterpret_cast<struct in6_addr *>(dst));
+  libc_errno = EAFNOSUPPORT;
+  return -1;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/arpa/inet/CMakeLists.txt b/libc/test/src/arpa/inet/CMakeLists.txt
index 4fba96417bc03..564434803f66e 100644
--- a/libc/test/src/arpa/inet/CMakeLists.txt
+++ b/libc/test/src/arpa/inet/CMakeLists.txt
@@ -81,6 +81,7 @@ add_libc_test(
   DEPENDS
     libc.hdr.errno_macros
     libc.hdr.types.struct_in_addr
+    libc.hdr.types.struct_in6_addr
     libc.src.arpa.inet.inet_pton
     libc.src.__support.libc_errno
     libc.hdr.sys_socket_macros
diff --git a/libc/test/src/arpa/inet/inet_pton_test.cpp b/libc/test/src/arpa/inet/inet_pton_test.cpp
index 99b78a23a3479..524b4a72d37fc 100644
--- a/libc/test/src/arpa/inet/inet_pton_test.cpp
+++ b/libc/test/src/arpa/inet/inet_pton_test.cpp
@@ -13,6 +13,7 @@
 
 #include "hdr/errno_macros.h"
 #include "hdr/sys_socket_macros.h"
+#include "hdr/types/struct_in6_addr.h"
 #include "hdr/types/struct_in_addr.h"
 #include "src/__support/endian_internal.h"
 #include "src/__support/libc_errno.h"
@@ -90,6 +91,151 @@ TEST_F(LlvmLibcInetPtonTest, StrictPosixLeadingZeros) {
   ASSERT_ERRNO_SUCCESS();
 }
 
+static bool check_ipv6(const struct in6_addr &addr, uint16_t a, uint16_t b,
+                       uint16_t c, uint16_t d, uint16_t e, uint16_t f,
+                       uint16_t g, uint16_t h) {
+  uint16_t expected[8] = {a, b, c, d, e, f, g, h};
+  for (size_t i = 0; i < 8; ++i) {
+    uint16_t actual = static_cast<uint16_t>(
+        (static_cast<uint16_t>(addr.s6_addr[2 * i]) << 8) |
+        static_cast<uint16_t>(addr.s6_addr[2 * i + 1]));
+    if (actual != expected[i])
+      return false;
+  }
+  return true;
+}
+
+TEST_F(LlvmLibcInetPtonTest, ValidIPv6Addresses) {
+  struct in6_addr addr;
+
+  // Unspecified address
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "::", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0, 0, 0, 0, 0, 0, 0, 0));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Loopback address
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "::1", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0, 0, 0, 0, 0, 0, 0, 1));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Full 8 groups without compression
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(
+                   AF_INET6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", &addr));
+  EXPECT_TRUE(
+      check_ipv6(addr, 0x2001, 0x0db8, 0x85a3, 0, 0, 0x8a2e, 0x0370, 0x7334));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Full 8 groups with varying digit lengths
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(
+                   AF_INET6, "2001:db8:85a3:0:0:8a2e:370:7334", &addr));
+  EXPECT_TRUE(
+      check_ipv6(addr, 0x2001, 0x0db8, 0x85a3, 0, 0, 0x8a2e, 0x0370, 0x7334));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Compression at beginning
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "::1234", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0, 0, 0, 0, 0, 0, 0, 0x1234));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Compression at end
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "fe80::", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0xfe80, 0, 0, 0, 0, 0, 0, 0));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Compression in middle
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "2001:db8::1", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0x2001, 0x0db8, 0, 0, 0, 0, 0, 1));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Multiple words before and after compression
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(
+                   AF_INET6, "2001:db8:85a3::8a2e:370:7334", &addr));
+  EXPECT_TRUE(
+      check_ipv6(addr, 0x2001, 0x0db8, 0x85a3, 0, 0, 0x8a2e, 0x0370, 0x7334));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Case insensitivity
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "2001:Db8::AbCd", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xabcd));
+  ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcInetPtonTest, ValidIPv4MappedIPv6Addresses) {
+  struct in6_addr addr;
+
+  // Standard IPv4-mapped address ::ffff:192.168.1.1
+  EXPECT_EQ(1,
+            LIBC_NAMESPACE::inet_pton(AF_INET6, "::ffff:192.168.1.1", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0101));
+  ASSERT_ERRNO_SUCCESS();
+
+  // IPv4-compatible address ::192.168.1.1
+  EXPECT_EQ(1, LIBC_NAMESPACE::inet_pton(AF_INET6, "::192.168.1.1", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Mixed prefix with compression: 2001:db8::192.168.1.1
+  EXPECT_EQ(
+      1, LIBC_NAMESPACE::inet_pton(AF_INET6, "2001:db8::192.168.1.1", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0x2001, 0x0db8, 0, 0, 0, 0, 0xc0a8, 0x0101));
+  ASSERT_ERRNO_SUCCESS();
+
+  // Full 6 words without compression followed by IPv4
+  EXPECT_EQ(
+      1, LIBC_NAMESPACE::inet_pton(AF_INET6, "0:0:0:0:0:0:192.168.1.1", &addr));
+  EXPECT_TRUE(check_ipv6(addr, 0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101));
+  ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcInetPtonTest, InvalidIPv6Formats) {
+  struct in6_addr addr;
+
+  // Empty string
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "", &addr));
+
+  // Colon misuse
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, ":", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, ":::", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, ":1:2:3:4:5:6:7:8", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6:7:8:", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1::2:", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1::2::3", &addr));
+
+  // Word count errors
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6:7:8:9", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4::5:6:7:8", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6:7", &addr));
+
+  // Field overflow (> 4 hex digits)
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "12345::1", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "00000::1", &addr));
+
+  // Non-hex characters
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "2001:xyz::1", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "fe80::1%eth0", &addr));
+
+  // Plain IPv4 without ::
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "192.168.1.1", &addr));
+
+  // Embedded IPv4 errors
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "::ffff:192.168.1", &addr));
+  EXPECT_EQ(0,
+            LIBC_NAMESPACE::inet_pton(AF_INET6, "::ffff:192.168.1.256", &addr));
+  EXPECT_EQ(0,
+            LIBC_NAMESPACE::inet_pton(AF_INET6, "::ffff:192.168.1.1:", &addr));
+  EXPECT_EQ(0,
+            LIBC_NAMESPACE::inet_pton(AF_INET6, "::ffff:192.168.01.1", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6:7:192.168.1.1",
+                                         &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6::192.168.1.1",
+                                         &addr));
+
+  // Whitespace
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, " ::1", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "::1 ", &addr));
+  ASSERT_ERRNO_SUCCESS();
+}
+
 TEST_F(LlvmLibcInetPtonTest, InvalidAddressFamily) {
   struct in_addr addr;
 



More information about the libc-commits mailing list