[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
Wed Sep 30 22:50:06 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 1/4] [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;
 

>From 3b2ba208e6e483984171a8b1a40a07748e40c3b8 Mon Sep 17 00:00:00 2001
From: Aman Maurya <amanmaurya9209 at gmail.com>
Date: Tue, 29 Sep 2026 00:19:06 +0530
Subject: [PATCH 2/4] [libc] Refactor str_to_ipv6 in inet_pton to use
 strtointeger and FixedVector

- Use internal::strtointeger<uint16_t> for parsing 16-bit hex components.
- Use FixedVector buffers to cleanly expand zero compression (::) without in-place shifting.
- Store components as big-endian uint16_t words directly into s6_addr16.
- Simplify test helper check_ipv6 using s6_addr16 and inline_memcmp.
- Add test coverage for signs, hex prefixes, and trailing non-hex characters.
---
 libc/src/__support/net/CMakeLists.txt      |   2 +
 libc/src/__support/net/address.cpp         | 138 +++++++++++----------
 libc/test/src/arpa/inet/CMakeLists.txt     |   1 +
 libc/test/src/arpa/inet/inet_pton_test.cpp |  22 ++--
 4 files changed, 87 insertions(+), 76 deletions(-)

diff --git a/libc/src/__support/net/CMakeLists.txt b/libc/src/__support/net/CMakeLists.txt
index effc8b90c775a..39a34528199ec 100644
--- a/libc/src/__support/net/CMakeLists.txt
+++ b/libc/src/__support/net/CMakeLists.txt
@@ -15,7 +15,9 @@ add_object_library(
     libc.src.__support.CPP.optional
     libc.src.__support.CPP.span
     libc.src.__support.CPP.string_view
+    libc.src.__support.fixedvector
     libc.src.__support.libc_assert
     libc.src.__support.str_to_integer
+    libc.src.string.memory_utils.inline_bzero
     libc.src.string.memory_utils.inline_memcpy
 )
diff --git a/libc/src/__support/net/address.cpp b/libc/src/__support/net/address.cpp
index 61c6c5bc52762..cd0c26ab235b5 100644
--- a/libc/src/__support/net/address.cpp
+++ b/libc/src/__support/net/address.cpp
@@ -22,8 +22,10 @@
 #include "src/__support/common.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/endian_internal.h"
+#include "src/__support/fixedvector.h"
 #include "src/__support/libc_assert.h"
 #include "src/__support/str_to_integer.h"
+#include "src/string/memory_utils/inline_bzero.h"
 #include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
@@ -67,104 +69,106 @@ 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;
+  bool has_double_colon = false;
+  FixedVector<uint16_t, 8> parts[2];
+  size_t part_idx = 0;
 
   if (src.starts_with("::")) {
-    double_colon_byte = 0;
+    has_double_colon = true;
+    part_idx = 1;
     src.remove_prefix(2);
     if (src.empty()) {
-      inline_memcpy(&dst.s6_addr, bytes, 16);
+      inline_bzero(&dst.s6_addr, 16);
       return true;
     }
-  } else if (src[0] == ':') {
+    if (src.starts_with(':'))
+      return false;
+  } else if (src.starts_with(':')) {
     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)
+  while (!src.empty()) {
+    if (src.starts_with("::")) {
+      if (has_double_colon)
         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)
+      has_double_colon = true;
+      part_idx = 1;
+      src.remove_prefix(2);
+      if (src.empty())
+        break;
+      if (src.starts_with(':'))
         return false;
+    } else if (src.starts_with(':')) {
+      src.remove_prefix(1);
+      if (src.empty() || src.starts_with(':'))
+        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)
+    // Check if the current component is an embedded IPv4 address.
+    // In IPv6, an embedded IPv4 address can only appear at the very end.
+    size_t colon_pos = src.find_first_of(':');
+    cpp::string_view token =
+        (colon_pos == cpp::string_view::npos) ? src : src.substr(0, colon_pos);
+
+    if (token.find_first_of('.') != cpp::string_view::npos) {
+      if (colon_pos != cpp::string_view::npos)
         return false;
 
-      cpp::string_view ipv4_str = src.substr(token_start);
       struct in_addr in4;
-      if (!str_to_ipv4(ipv4_str, in4))
+      if (!str_to_ipv4(src, in4))
         return false;
 
-      inline_memcpy(&bytes[cur_byte], &in4.s_addr, 4);
-      cur_byte += 4;
-      num_digits = 0;
+      uint16_t v4_words[2];
+      inline_memcpy(v4_words, &in4.s_addr, sizeof(v4_words));
+      if (!parts[part_idx].push_back(v4_words[0]) ||
+          !parts[part_idx].push_back(v4_words[1]))
+        return false;
+
+      src = cpp::string_view();
       break;
-    } else {
-      return false;
     }
-  }
 
-  if (num_digits > 0) {
-    if (cur_byte + 2 > 16)
+    if (internal::isspace(src[0]) || src[0] == '+' || src[0] == '-' ||
+        src.starts_with("0x") || src.starts_with("0X"))
       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)
+    auto result = internal::strtointeger<uint16_t>(src.data(), 16, src.size());
+    if (result.has_error() || result.parsed_len == 0 || result.parsed_len > 4)
       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)];
+    if (!parts[part_idx].push_back(Endian::to_big_endian(result.value)))
+      return false;
 
-    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;
+    src.remove_prefix(static_cast<size_t>(result.parsed_len));
+    if (!src.empty() && src[0] != ':')
+      return false;
+  }
+
+  if (has_double_colon) {
+    if (parts[0].size() + parts[1].size() >= 8)
+      return false;
+
+    size_t num_zeroes = 8 - parts[0].size() - parts[1].size();
+    uint16_t *ptr = dst.s6_addr16;
+    if (!parts[0].empty()) {
+      inline_memcpy(ptr, parts[0].begin(), parts[0].size() * sizeof(uint16_t));
+      ptr += parts[0].size();
+    }
+    inline_bzero(ptr, num_zeroes * sizeof(uint16_t));
+    ptr += num_zeroes;
+    if (!parts[1].empty()) {
+      inline_memcpy(ptr, parts[1].begin(), parts[1].size() * sizeof(uint16_t));
+    }
+  } else {
+    if (parts[0].size() != 8)
+      return false;
+    inline_memcpy(dst.s6_addr16, parts[0].begin(), 8 * sizeof(uint16_t));
   }
 
-  inline_memcpy(&dst.s6_addr, bytes, 16);
   return true;
 }
 
diff --git a/libc/test/src/arpa/inet/CMakeLists.txt b/libc/test/src/arpa/inet/CMakeLists.txt
index 564434803f66e..9959935db1dbb 100644
--- a/libc/test/src/arpa/inet/CMakeLists.txt
+++ b/libc/test/src/arpa/inet/CMakeLists.txt
@@ -85,6 +85,7 @@ add_libc_test(
     libc.src.arpa.inet.inet_pton
     libc.src.__support.libc_errno
     libc.hdr.sys_socket_macros
+    libc.src.string.memory_utils.inline_memcmp
     libc.test.UnitTest.ErrnoCheckingTest
 )
 
diff --git a/libc/test/src/arpa/inet/inet_pton_test.cpp b/libc/test/src/arpa/inet/inet_pton_test.cpp
index 524b4a72d37fc..0f716211c994d 100644
--- a/libc/test/src/arpa/inet/inet_pton_test.cpp
+++ b/libc/test/src/arpa/inet/inet_pton_test.cpp
@@ -18,6 +18,7 @@
 #include "src/__support/endian_internal.h"
 #include "src/__support/libc_errno.h"
 #include "src/arpa/inet/inet_pton.h"
+#include "src/string/memory_utils/inline_memcmp.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/Test.h"
 
@@ -94,15 +95,12 @@ TEST_F(LlvmLibcInetPtonTest, StrictPosixLeadingZeros) {
 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;
+  uint16_t words[8] = {a, b, c, d, e, f, g, h};
+  struct in6_addr expected;
+  for (size_t i = 0; i < 8; ++i)
+    expected.s6_addr16[i] = LIBC_NAMESPACE::Endian::to_big_endian(words[i]);
+  return LIBC_NAMESPACE::inline_memcmp(&addr, &expected,
+                                       sizeof(struct in6_addr)) == 0;
 }
 
 TEST_F(LlvmLibcInetPtonTest, ValidIPv6Addresses) {
@@ -230,6 +228,12 @@ TEST_F(LlvmLibcInetPtonTest, InvalidIPv6Formats) {
   EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6::192.168.1.1",
                                          &addr));
 
+  // Hex prefixes and signs
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "0x1234::", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "+1234::", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "-1234::", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1234g::", &addr));
+
   // Whitespace
   EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, " ::1", &addr));
   EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "::1 ", &addr));

>From 4c53f86887ba367f75c6b113545faaa21c7e072d Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Tue, 29 Sep 2026 21:35:57 +0530
Subject: [PATCH 3/4] [libc] Address review feedback on inet_pton IPv6 support

---
 libc/src/__support/net/address.cpp         | 74 +++++++++-------------
 libc/test/src/arpa/inet/inet_pton_test.cpp |  2 +
 2 files changed, 33 insertions(+), 43 deletions(-)

diff --git a/libc/src/__support/net/address.cpp b/libc/src/__support/net/address.cpp
index cd0c26ab235b5..ac9bd1f1c4931 100644
--- a/libc/src/__support/net/address.cpp
+++ b/libc/src/__support/net/address.cpp
@@ -70,54 +70,44 @@ namespace net {
 }
 
 [[nodiscard]] bool str_to_ipv6(cpp::string_view src, struct in6_addr &dst) {
-  if (src.empty())
-    return false;
-
-  bool has_double_colon = false;
-  FixedVector<uint16_t, 8> parts[2];
+  constexpr size_t NUM_COMPONENTS = 8;
+  FixedVector<uint16_t, NUM_COMPONENTS> parts[2];
   size_t part_idx = 0;
 
-  if (src.starts_with("::")) {
-    has_double_colon = true;
-    part_idx = 1;
-    src.remove_prefix(2);
-    if (src.empty()) {
-      inline_bzero(&dst.s6_addr, 16);
-      return true;
-    }
-    if (src.starts_with(':'))
+  while (!src.empty()) {
+    size_t non_colon = src.find_first_not_of(':');
+    if (non_colon == cpp::string_view::npos) {
+      if (src.size() == 2 && part_idx == 0) {
+        part_idx = 1;
+        break;
+      }
       return false;
-  } else if (src.starts_with(':')) {
-    return false;
-  }
+    }
 
-  while (!src.empty()) {
-    if (src.starts_with("::")) {
-      if (has_double_colon)
+    switch (non_colon) {
+    case 0:
+      if (part_idx > 0 || !parts[0].empty())
         return false;
-      has_double_colon = true;
-      part_idx = 1;
-      src.remove_prefix(2);
-      if (src.empty())
-        break;
-      if (src.starts_with(':'))
+      break;
+    case 1:
+      if (part_idx == 0 && parts[0].empty())
         return false;
-    } else if (src.starts_with(':')) {
       src.remove_prefix(1);
-      if (src.empty() || src.starts_with(':'))
+      break;
+    case 2:
+      if (part_idx > 0)
         return false;
+      part_idx = 1;
+      src.remove_prefix(2);
+      break;
+    default:
+      return false;
     }
 
-    // Check if the current component is an embedded IPv4 address.
-    // In IPv6, an embedded IPv4 address can only appear at the very end.
     size_t colon_pos = src.find_first_of(':');
-    cpp::string_view token =
-        (colon_pos == cpp::string_view::npos) ? src : src.substr(0, colon_pos);
-
-    if (token.find_first_of('.') != cpp::string_view::npos) {
-      if (colon_pos != cpp::string_view::npos)
-        return false;
+    cpp::string_view token = src.substr(0, colon_pos);
 
+    if (colon_pos == cpp::string_view::npos && token.contains('.')) {
       struct in_addr in4;
       if (!str_to_ipv4(src, in4))
         return false;
@@ -144,15 +134,13 @@ namespace net {
       return false;
 
     src.remove_prefix(static_cast<size_t>(result.parsed_len));
-    if (!src.empty() && src[0] != ':')
-      return false;
   }
 
-  if (has_double_colon) {
-    if (parts[0].size() + parts[1].size() >= 8)
+  if (part_idx > 0) {
+    if (parts[0].size() + parts[1].size() >= NUM_COMPONENTS)
       return false;
 
-    size_t num_zeroes = 8 - parts[0].size() - parts[1].size();
+    size_t num_zeroes = NUM_COMPONENTS - parts[0].size() - parts[1].size();
     uint16_t *ptr = dst.s6_addr16;
     if (!parts[0].empty()) {
       inline_memcpy(ptr, parts[0].begin(), parts[0].size() * sizeof(uint16_t));
@@ -164,9 +152,9 @@ namespace net {
       inline_memcpy(ptr, parts[1].begin(), parts[1].size() * sizeof(uint16_t));
     }
   } else {
-    if (parts[0].size() != 8)
+    if (parts[0].size() != NUM_COMPONENTS)
       return false;
-    inline_memcpy(dst.s6_addr16, parts[0].begin(), 8 * sizeof(uint16_t));
+    inline_memcpy(dst.s6_addr16, parts[0].begin(), sizeof(dst));
   }
 
   return true;
diff --git a/libc/test/src/arpa/inet/inet_pton_test.cpp b/libc/test/src/arpa/inet/inet_pton_test.cpp
index 0f716211c994d..34f5ba9e7ec42 100644
--- a/libc/test/src/arpa/inet/inet_pton_test.cpp
+++ b/libc/test/src/arpa/inet/inet_pton_test.cpp
@@ -227,6 +227,8 @@ TEST_F(LlvmLibcInetPtonTest, InvalidIPv6Formats) {
                                          &addr));
   EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2:3:4:5:6::192.168.1.1",
                                          &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1::2.3.4.5:6", &addr));
+  EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "1:2.3.4.5:6", &addr));
 
   // Hex prefixes and signs
   EXPECT_EQ(0, LIBC_NAMESPACE::inet_pton(AF_INET6, "0x1234::", &addr));

>From a56b98146b69711a1c397c3f8403e60e32800e71 Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Wed, 30 Sep 2026 20:57:55 +0530
Subject: [PATCH 4/4] [libc] Address review nits on inet_pton IPv6 parsing

---
 libc/src/__support/net/address.cpp | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/libc/src/__support/net/address.cpp b/libc/src/__support/net/address.cpp
index ac9bd1f1c4931..bc18d927d7cd8 100644
--- a/libc/src/__support/net/address.cpp
+++ b/libc/src/__support/net/address.cpp
@@ -71,6 +71,9 @@ namespace net {
 
 [[nodiscard]] bool str_to_ipv6(cpp::string_view src, struct in6_addr &dst) {
   constexpr size_t NUM_COMPONENTS = 8;
+  // `parts[0]` collects 16-bit groups preceding "::", while `parts[1]` collects
+  // groups following "::". When "::" is encountered, `part_idx` switches to 1.
+  // After parsing, any omitted zero groups are filled between the two parts.
   FixedVector<uint16_t, NUM_COMPONENTS> parts[2];
   size_t part_idx = 0;
 
@@ -105,9 +108,7 @@ namespace net {
     }
 
     size_t colon_pos = src.find_first_of(':');
-    cpp::string_view token = src.substr(0, colon_pos);
-
-    if (colon_pos == cpp::string_view::npos && token.contains('.')) {
+    if (colon_pos == cpp::string_view::npos && src.contains('.')) {
       struct in_addr in4;
       if (!str_to_ipv4(src, in4))
         return false;
@@ -118,7 +119,6 @@ namespace net {
           !parts[part_idx].push_back(v4_words[1]))
         return false;
 
-      src = cpp::string_view();
       break;
     }
 
@@ -148,9 +148,8 @@ namespace net {
     }
     inline_bzero(ptr, num_zeroes * sizeof(uint16_t));
     ptr += num_zeroes;
-    if (!parts[1].empty()) {
+    if (!parts[1].empty())
       inline_memcpy(ptr, parts[1].begin(), parts[1].size() * sizeof(uint16_t));
-    }
   } else {
     if (parts[0].size() != NUM_COMPONENTS)
       return false;



More information about the libc-commits mailing list