[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