Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F147392851
D53488.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
D53488.id.diff
View Options
diff --git a/lib/libc/inet/inet_cidr_ntop.c b/lib/libc/inet/inet_cidr_ntop.c
--- a/lib/libc/inet/inet_cidr_ntop.c
+++ b/lib/libc/inet/inet_cidr_ntop.c
@@ -221,13 +221,7 @@
(best.len == 5 && words[5] == 0xffff))) {
int n;
- if (src[15] || bits == -1 || bits > 120)
- n = 4;
- else if (src[14] || bits > 112)
- n = 3;
- else
- n = 2;
- n = decoct(src+12, n, tp, sizeof tmp - (tp - tmp));
+ n = decoct(src+12, 4, tp, sizeof tmp - (tp - tmp));
if (n == 0) {
errno = EMSGSIZE;
return (NULL);
diff --git a/lib/libc/tests/net/Makefile b/lib/libc/tests/net/Makefile
--- a/lib/libc/tests/net/Makefile
+++ b/lib/libc/tests/net/Makefile
@@ -5,9 +5,11 @@
ATF_TESTS_C+= eui64_ntoa_test
ATF_TESTS_CXX+= link_addr_test
ATF_TESTS_CXX+= inet_net_test
+ATF_TESTS_CXX+= inet_cidr_test
CXXSTD.link_addr_test= c++20
CXXSTD.inet_net_test= c++20
+CXXSTD.inet_cidr_test= c++20
CFLAGS+= -I${.CURDIR}
diff --git a/lib/libc/tests/net/inet_cidr_test.cc b/lib/libc/tests/net/inet_cidr_test.cc
new file mode 100644
--- /dev/null
+++ b/lib/libc/tests/net/inet_cidr_test.cc
@@ -0,0 +1,373 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2025 Lexi Winter <ivy@FreeBSD.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Tests for inet_cidr_pton() and inet_cidr_ntop().
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+using namespace std::literals;
+
+/*
+ * inet_cidr_ntop() and inet_cidr_pton() for IPv4.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_inet4)
+ATF_TEST_CASE_BODY(inet_cidr_inet4)
+{
+ /*
+ * Define a list of addresses we want to check. Each address is passed
+ * to inet_cidr_pton() to convert it to an in_addr, then we convert the
+ * in_addr back to a string and compare it with the expected value. We
+ * want to test over-long prefixes here (such as 10.0.0.1/8), so we also
+ * specify what the result is expected to be.
+ */
+
+ struct test_addr {
+ std::string input;
+ int bits;
+ std::string output;
+ };
+
+ auto test_addrs = std::vector<test_addr>{
+ // Simple prefixes that fall on octet boundaries.
+ { "10.0.0.0/8", 8, "10/8" },
+ { "10.1.0.0/16", 16, "10.1/16" },
+ { "10.1.2.0/24", 24, "10.1.2/24" },
+ { "10.1.2.3/32", 32, "10.1.2.3/32" },
+
+ // Simple prefixes with the short-form address.
+ { "10/8", 8, "10/8" },
+ { "10.1/16", 16, "10.1/16" },
+ { "10.1.2/24", 24, "10.1.2/24" },
+
+ // A prefix that doesn't fall on an octet boundary.
+ { "10.1.64/18", 18, "10.1.64/18" },
+
+ // An overlong prefix with bits that aren't part of the prefix.
+ { "10.0.0.1/8", 8, "10.0.0.1/8" },
+
+ // An address with no mask is treated as a /32.
+ { "10.1.2.3", 32, "10.1.2.3/32" },
+ };
+
+ for (auto const &addr: test_addrs) {
+ /*
+ * Convert the input string to an in_addr + bits, and make
+ * sure the result produces the number of bits we expected.
+ */
+
+ auto in = in_addr{};
+ auto bits = int{};
+ auto ret = inet_cidr_pton(AF_INET, addr.input.c_str(),
+ &in, &bits);
+ ATF_REQUIRE(ret != -1);
+ ATF_REQUIRE_EQ(bits, addr.bits);
+
+ /*
+ * Convert the in_addr back to a string
+ */
+
+ /*
+ * XXX: Should there be a constant for the size of the result
+ * buffer? For now, use ADDRSTRLEN + 3 ("/32") + 1 (NUL).
+ *
+ * Fill the buffer with 'Z', so we can check the result was
+ * properly terminated.
+ */
+ auto strbuf = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto *pret = inet_cidr_ntop(AF_INET, &in, bits,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(pret != nullptr);
+ ATF_REQUIRE_EQ(pret, strbuf.data());
+
+ /* Make sure the result was NUL-terminated and find the NUL */
+ ATF_REQUIRE(strbuf.size() >= 1);
+ auto end = std::ranges::find(strbuf, '\0');
+ ATF_REQUIRE(end != strbuf.end());
+
+ /*
+ * Check the result matches what we expect. Use a temporary
+ * string here instead of std::ranges::equal because this
+ * means ATF can print the mismatch.
+ */
+ auto str = std::string(std::ranges::begin(strbuf), end);
+ ATF_REQUIRE_EQ(str, addr.output);
+
+ /*
+ * Call inet_cidr_ntop() again, but pass bits as -1. This
+ * skips printing the prefix length, which makes the function
+ * identical to inet_ntop() (so what is the point of this?).
+ */
+ auto buf1 = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto *ret1 = inet_cidr_ntop(AF_INET, &in, -1,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret1 != NULL);
+
+ auto buf2 = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto *ret2 = inet_ntop(AF_INET, &in,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret2 != NULL);
+
+ auto str1 = std::string(buf1.begin(), buf1.end());
+ auto str2 = std::string(buf2.begin(), buf2.end());
+ ATF_REQUIRE_EQ(str1, str2);
+ }
+}
+
+/*
+ * inet_cidr_ntop() and inet_cidr_pton() for IPv6.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_inet6)
+ATF_TEST_CASE_BODY(inet_cidr_inet6)
+{
+ /*
+ * Define a list of addresses we want to check. Each address is
+ * passed to inet_cidr_pton() to convert it to an in6_addr, then we
+ * convert the in6_addr back to a string and compare it with the
+ * expected value. We want to test over-long prefixes here (such
+ * as 2001:db8::1/32), so we also specify what the result is
+ * expected to be.
+ */
+
+ struct test_addr {
+ std::string input;
+ int bits;
+ std::string output;
+ };
+
+ auto test_addrs = std::vector<test_addr>{
+ // An address with no prefix length
+ { "::1", -1, "::1" },
+ { "2001:db8::1", -1, "2001:db8::1" },
+
+ // A prefix with a trailing ::
+ { "2001:db8::/32", 32, "2001:db8::/32" },
+
+ // A prefix with a leading ::. Note that the output is
+ // different from the input because inet_ntop() renders
+ // this prefix with an IPv4 suffix for legacy reasons.
+ { "::ffff:0:0/96", 96, "::ffff:0.0.0.0/96" },
+ { "::ffff:0:1/96", 96, "::ffff:0.0.0.1/96" },
+ { "::ffff:1:0/96", 96, "::ffff:0.1.0.0/96" },
+
+ // The same prefix but with the IPv4 legacy form as input.
+ { "::ffff:0.0.0.0/96", 96, "::ffff:0.0.0.0/96" },
+ { "::ffff:0.0.3.0/96", 96, "::ffff:0.0.3.0/96" },
+
+ // A prefix with an infix ::.
+ { "2001:db8::1/128", 128, "2001:db8::1/128" },
+
+ // A prefix with bits set which are outside the prefix;
+ // these should be silently ignored.
+ { "2001:db8:1:1:1:1:1:1/32", 32, "2001:db8:1:1:1:1:1:1/32" },
+
+ // As above but with infix ::.
+ { "2001:db8::1/32", 32, "2001:db8::1/32" },
+
+ // A prefix with only ::, commonly used to represent the
+ // entire address space.
+ { "::/0", 0, "::/0" },
+
+ // A single address with no ::.
+ { "2001:db8:1:1:1:1:1:1/128", 128, "2001:db8:1:1:1:1:1:1/128" },
+
+ // A prefix with no ::.
+ { "2001:db8:1:1:0:0:0:0/64", 64, "2001:db8:1:1::/64" },
+
+ // A prefix which isn't on a 16-bit boundary.
+ { "2001:db8:c000::/56", 56, "2001:db8:c000::/56" },
+
+ // A prefix which isn't on a nibble boundary.
+ { "2001:db8:c100::/57", 57, "2001:db8:c100::/57" },
+
+ // Test vectors provided in PR bin/289198.
+ { "fe80::1/64", 64, "fe80::1/64" },
+ { "fe80::f000:74ff:fe54:bed2/64",
+ 64, "fe80::f000:74ff:fe54:bed2/64" },
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64",
+ 64,
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64" },
+ };
+
+ for (auto const &addr: test_addrs) {
+ /*
+ * Convert the input string to an in6_addr + bits, and make
+ * sure the result produces the number of bits we expected.
+ */
+
+ auto in6 = in6_addr{};
+ auto bits = int{};
+ errno = 0;
+
+ auto ret = inet_cidr_pton(AF_INET6, addr.input.c_str(),
+ &in6, &bits);
+ ATF_REQUIRE_EQ(ret, 0);
+ ATF_REQUIRE_EQ(bits, addr.bits);
+
+ /*
+ * Convert the in6_addr back to a string
+ */
+
+ /*
+ * XXX: Should there be a constant for the size of the result
+ * buffer? For now, use ADDRSTRLEN + 4 ("/128") + 1 (NUL).
+ *
+ * Fill the buffer with 'Z', so we can check the result was
+ * properly terminated.
+ */
+ auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto *pret = inet_cidr_ntop(AF_INET6, &in6, bits,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(pret != NULL);
+ ATF_REQUIRE_EQ(pret, strbuf.data());
+
+ /* Make sure the result was NUL-terminated and find the NUL */
+ ATF_REQUIRE(strbuf.size() >= 1);
+ auto end = std::ranges::find(strbuf, '\0');
+ ATF_REQUIRE(end != strbuf.end());
+
+ /*
+ * Check the result matches what we expect. Use a temporary
+ * string here instead of std::ranges::equal because this
+ * means ATF can print the mismatch.
+ */
+ auto str = std::string(std::ranges::begin(strbuf), end);
+ ATF_REQUIRE_EQ(str, addr.output);
+
+ /*
+ * Call inet_cidr_ntop() again, but pass bits as -1. This
+ * skips printing the prefix length, which makes the function
+ * identical to inet_ntop() (so what is the point of this?).
+ */
+ auto buf1 = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto *ret1 = inet_cidr_ntop(AF_INET6, &in6, -1,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret1 != NULL);
+
+ auto buf2 = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto *ret2 = inet_ntop(AF_INET6, &in6,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret2 != NULL);
+
+ auto str1 = std::string(buf1.begin(), buf1.end());
+ auto str2 = std::string(buf2.begin(), buf2.end());
+ ATF_REQUIRE_EQ(str1, str2);
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_pton_invalid)
+ATF_TEST_CASE_BODY(inet_cidr_pton_invalid)
+{
+ auto bits = int{};
+ auto addr4 = in_addr{};
+ auto str4 = "10.0.0.0"s;
+ auto addr6 = in6_addr{};
+ auto str6 = "2001:db8::"s;
+
+ /* Test some generally invalid addresses. */
+ auto invalid4 = std::vector<std::string>{
+ // Partial address with no prefix length
+ "10",
+ "10.0",
+ "10.0.0",
+ // Prefix length too big
+ "10.0.0.0/33",
+ // Prefix length is negative
+ "10.0.0.0/-1",
+ // Prefix length is not a number
+ "10.0.0.0/foo",
+ // Input is not a network prefix
+ "this is not an IP address",
+ };
+
+ for (auto const &addr: invalid4) {
+ auto ret = inet_cidr_pton(AF_INET, addr.c_str(), &addr4, &bits);
+ ATF_REQUIRE_EQ(ret, -1);
+ }
+
+ auto invalid6 = std::vector<std::string>{
+ // Prefix length too big
+ "2001:db8::/129",
+ // Prefix length is negative
+ "2001:db8::/-1",
+ // Prefix length is not a number
+ "2001:db8::/foo",
+ // Input is not a network prefix
+ "this is not an IP address",
+ };
+
+ for (auto const &addr: invalid6) {
+ auto ret = inet_cidr_pton(AF_INET6, addr.c_str(), &addr6, &bits);
+ ATF_REQUIRE_EQ(ret, -1);
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_ntop_invalid)
+ATF_TEST_CASE_BODY(inet_cidr_ntop_invalid)
+{
+ auto addr4 = in_addr{};
+ auto addr6 = in6_addr{};
+ auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1);
+
+ /*
+ * Passing a buffer which is too small should not overrun the buffer.
+ * Test this by initialising the buffer to 'Z', and only providing
+ * part of it to the function.
+ */
+
+ std::ranges::fill(strbuf, 'Z');
+ auto ret = inet_cidr_ntop(AF_INET6, &addr6, 128, strbuf.data(), 1);
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ATF_REQUIRE_EQ(strbuf[1], 'Z');
+
+ std::ranges::fill(strbuf, 'Z');
+ ret = inet_cidr_ntop(AF_INET, &addr4, 32, strbuf.data(), 1);
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ATF_REQUIRE_EQ(strbuf[1], 'Z');
+
+ /* Check that invalid prefix lengths return an error */
+
+ ret = inet_cidr_ntop(AF_INET6, &addr6, 129, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ret = inet_cidr_ntop(AF_INET6, &addr6, -2, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+
+ ret = inet_cidr_ntop(AF_INET, &addr4, 33, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ret = inet_cidr_ntop(AF_INET, &addr4, -2, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_inet4);
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_inet6);
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_pton_invalid);
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_ntop_invalid);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 11, 2:47 PM (6 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29524065
Default Alt Text
D53488.id.diff (12 KB)
Attached To
Mode
D53488: inet_cidr_ntop: Produce valid IPv4 mapped addresses
Attached
Detach File
Event Timeline
Log In to Comment