xref: /freebsd/lib/libc/tests/net/inet_net_test.cc (revision df21a004be237a1dccd03c7b47254625eea62fa9)
1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2025 Lexi Winter <ivy@FreeBSD.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * Tests for inet_net_pton() and inet_net_ntop().
21  */
22 
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 
26 #include <netinet/in.h>
27 #include <arpa/inet.h>
28 
29 #include <ranges>
30 #include <string>
31 #include <vector>
32 
33 #include <atf-c++.hpp>
34 
35 using namespace std::literals;
36 
37 /*
38  * inet_net_ntop() and inet_net_pton() for IPv4.
39  */
40 ATF_TEST_CASE_WITHOUT_HEAD(inet_net_inet4)
41 ATF_TEST_CASE_BODY(inet_net_inet4)
42 {
43 	/*
44 	 * Define a list of addresses we want to check.  Each address is passed
45 	 * to inet_net_pton() to convert it to an in_addr, then we convert the
46 	 * in_addr back to a string and compare it with the expected value.  We
47 	 * want to test over-long prefixes here (such as 10.0.0.1/8), so we also
48 	 * specify what the result is expected to be.
49 	 */
50 
51 	struct test_addr {
52 		std::string input;
53 		int bits;
54 		std::string output;
55 	};
56 
57 	auto test_addrs = std::vector<test_addr>{
58 		// Simple prefixes that fall on octet boundaries.
59 		{ "10.0.0.0/8",		8,	"10/8" },
60 		{ "10.1.0.0/16",	16,	"10.1/16" },
61 		{ "10.1.2.0/24",	24,	"10.1.2/24" },
62 		{ "10.1.2.3/32",	32,	"10.1.2.3/32" },
63 
64 		// Simple prefixes with the short-form address.
65 		{ "10/8",		8,	"10/8" },
66 		{ "10.1/16",		16,	"10.1/16" },
67 		{ "10.1.2/24",		24,	"10.1.2/24" },
68 
69 		// A prefix that doesn't fall on an octet boundary.
70 		{ "10.1.64/18",		18,	"10.1.64/18" },
71 
72 		// An overlong prefix with bits that aren't part of the prefix.
73 		{ "10.0.0.1/8",		8,	"10/8" },
74 	};
75 
76 	for (auto const &addr: test_addrs) {
77 		/*
78 		 * Convert the input string to an in_addr + bits, and make
79 		 * sure the result produces the number of bits we expected.
80 		 */
81 
82 		auto in = in_addr{};
83 		auto bits = inet_net_pton(AF_INET, addr.input.c_str(),
84 		    &in, sizeof(in));
85 		ATF_REQUIRE(bits != -1);
86 		ATF_REQUIRE_EQ(bits, addr.bits);
87 
88 		/*
89 		 * Convert the in_addr back to a string
90 		 */
91 
92 		/*
93 		 * XXX: Should there be a constant for the size of the result
94 		 * buffer?  For now, use ADDRSTRLEN + 3 ("/32") + 1 (NUL).
95 		 *
96 		 * Fill the buffer with 'Z', so we can check the result was
97 		 * properly terminated.
98 		 */
99 		auto strbuf = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
100 		auto ret = inet_net_ntop(AF_INET, &in, bits,
101 		    strbuf.data(), strbuf.size());
102 		ATF_REQUIRE(ret != NULL);
103 		ATF_REQUIRE_EQ(ret, strbuf.data());
104 
105 		/* Make sure the result was NUL-terminated and find the NUL */
106 		ATF_REQUIRE(strbuf.size() >= 1);
107 		auto end = std::ranges::find(strbuf, '\0');
108 		ATF_REQUIRE(end != strbuf.end());
109 
110 		/*
111 		 * Check the result matches what we expect.  Use a temporary
112 		 * string here instead of std::ranges::equal because this
113 		 * means ATF can print the mismatch.
114 		 */
115 		auto str = std::string(std::ranges::begin(strbuf), end);
116 		ATF_REQUIRE_EQ(str, addr.output);
117 	}
118 }
119 
120 /*
121  * inet_net_ntop() and inet_net_pton() for IPv6.
122  */
123 ATF_TEST_CASE_WITHOUT_HEAD(inet_net_inet6)
124 ATF_TEST_CASE_BODY(inet_net_inet6)
125 {
126 	/*
127 	 * Define a list of addresses we want to check.  Each address is
128 	 * passed to inet_net_pton() to convert it to an in6_addr, then we
129 	 * convert the in6_addr back to a string and compare it with the
130 	 * expected value.  We want to test over-long prefixes here (such
131 	 * as 2001:db8::1/32), so we also specify what the result is
132 	 * expected to be.
133 	 */
134 
135 	struct test_addr {
136 		std::string input;
137 		int bits;
138 		std::string output;
139 	};
140 
141 	auto test_addrs = std::vector<test_addr>{
142 		// A prefix with a trailing ::
143 		{ "2001:db8::/32",	32,	"2001:db8::/32" },
144 
145 		// A prefix with a leading ::.  Note that the output is
146 		// different from the input because inet_ntop() renders
147 		// this prefix with an IPv4 suffix for legacy reasons.
148 		{ "::ffff:0:0/96",	96,	"::ffff:0.0.0.0/96" },
149 
150 		// The same prefix but with the IPv4 legacy form as input.
151 		{ "::ffff:0.0.0.0/96",	96,	"::ffff:0.0.0.0/96" },
152 
153 		// A prefix with an infix ::.
154 		{ "2001:db8::1/128",	128,	"2001:db8::1/128" },
155 
156 		// A prefix with bits set which are outside the prefix;
157 		// these should be silently ignored.
158 		{ "2001:db8:1:1:1:1:1:1/32", 32, "2001:db8::/32" },
159 
160 		// As above but with infix ::.
161 		{ "2001:db8::1/32",	32,	"2001:db8::/32" },
162 
163 		// A prefix with only ::, commonly used to represent the
164 		// entire address space.
165 		{ "::/0",		0,	"::/0" },
166 
167 		// A single address with no ::.
168 		{ "2001:db8:1:1:1:1:1:1/128", 128, "2001:db8:1:1:1:1:1:1/128" },
169 
170 		// A prefix with no ::.
171 		{ "2001:db8:1:1:0:0:0:0/64", 64, "2001:db8:1:1::/64" },
172 
173 		// A prefix which isn't on a 16-bit boundary.
174 		{ "2001:db8:c000::/56",	56,	"2001:db8:c000::/56" },
175 
176 		// A prefix which isn't on a nibble boundary.
177 		{ "2001:db8:c100::/57",	57,	"2001:db8:c100::/57" },
178 
179 		// An address without a prefix length, which should be treated
180 		// as a /128.
181 		{ "2001:db8::",		128,	"2001:db8::/128" },
182 		{ "2001:db8::1",	128,	"2001:db8::1/128" },
183 
184 		// Test vectors provided in PR bin/289198.
185 		{ "fe80::1/64",		64,	"fe80::/64" },
186 		{ "fe80::f000:74ff:fe54:bed2/64",
187 					64,	"fe80::/64" },
188 		{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64",
189 					64,	"ffff:ffff:ffff:ffff::/64" },
190 		{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128,
191 		    "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" },
192 	};
193 
194 	for (auto const &addr: test_addrs) {
195 		/*
196 		 * Convert the input string to an in6_addr + bits, and make
197 		 * sure the result produces the number of bits we expected.
198 		 */
199 
200 		auto in6 = in6_addr{};
201 		errno = 0;
202 		auto bits = inet_net_pton(AF_INET6, addr.input.c_str(),
203 		    &in6, sizeof(in6));
204 		ATF_REQUIRE(bits != -1);
205 		ATF_REQUIRE_EQ(bits, addr.bits);
206 
207 		/*
208 		 * Convert the in6_addr back to a string
209 		 */
210 
211 		/*
212 		 * XXX: Should there be a constant for the size of the result
213 		 * buffer?  For now, use ADDRSTRLEN + 4 ("/128") + 1 (NUL).
214 		 *
215 		 * Fill the buffer with 'Z', so we can check the result was
216 		 * properly terminated.
217 		 */
218 		auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
219 		auto ret = inet_net_ntop(AF_INET6, &in6, bits,
220 		    strbuf.data(), strbuf.size());
221 		ATF_REQUIRE(ret != NULL);
222 		ATF_REQUIRE_EQ(ret, strbuf.data());
223 
224 		/* Make sure the result was NUL-terminated and find the NUL */
225 		ATF_REQUIRE(strbuf.size() >= 1);
226 		auto end = std::ranges::find(strbuf, '\0');
227 		ATF_REQUIRE(end != strbuf.end());
228 
229 		/*
230 		 * Check the result matches what we expect.  Use a temporary
231 		 * string here instead of std::ranges::equal because this
232 		 * means ATF can print the mismatch.
233 		 */
234 		auto str = std::string(std::ranges::begin(strbuf), end);
235 		ATF_REQUIRE_EQ(str, addr.output);
236 	}
237 }
238 
239 ATF_TEST_CASE_WITHOUT_HEAD(inet_net_pton_invalid)
240 ATF_TEST_CASE_BODY(inet_net_pton_invalid)
241 {
242 	auto ret = int{};
243 	auto addr4 = in_addr{};
244 	auto str4 = "10.0.0.0"s;
245 	auto addr6 = in6_addr{};
246 	auto str6 = "2001:db8::"s;
247 
248 	/* Passing an address which is too short should be an error */
249 	ret = inet_net_pton(AF_INET6, str6.c_str(), &addr6, sizeof(addr6) - 1);
250 	ATF_REQUIRE_EQ(ret, -1);
251 
252 	ret = inet_net_pton(AF_INET, str4.c_str(), &addr4, sizeof(addr4) - 1);
253 	ATF_REQUIRE_EQ(ret, -1);
254 
255 	/* Test some generally invalid addresses. */
256 	auto invalid4 = std::vector<std::string>{
257 		// Prefix length too big
258 		"10.0.0.0/33",
259 		// Prefix length is negative
260 		"10.0.0.0/-1",
261 		// Prefix length is not a number
262 		"10.0.0.0/foo",
263 		// Input is not a network prefix
264 		"this is not an IP address",
265 	};
266 
267 	for (auto const &addr: invalid4) {
268 		auto ret = inet_net_pton(AF_INET, addr.c_str(), &addr4,
269 		    sizeof(addr4));
270 		ATF_REQUIRE_EQ(ret, -1);
271 	}
272 
273 	auto invalid6 = std::vector<std::string>{
274 		// Prefix length too big
275 		"2001:db8::/129",
276 		// Prefix length is negative
277 		"2001:db8::/-1",
278 		// Prefix length is not a number
279 		"2001:db8::/foo",
280 		// Input is not a network prefix
281 		"this is not an IP address",
282 	};
283 
284 	for (auto const &addr: invalid6) {
285 		auto ret = inet_net_pton(AF_INET6, addr.c_str(), &addr6,
286 		    sizeof(addr6));
287 		ATF_REQUIRE_EQ(ret, -1);
288 	}
289 }
290 
291 ATF_TEST_CASE_WITHOUT_HEAD(inet_net_ntop_invalid)
292 ATF_TEST_CASE_BODY(inet_net_ntop_invalid)
293 {
294 	auto addr4 = in_addr{};
295 	auto addr6 = in6_addr{};
296 	auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1);
297 
298 	/*
299 	 * Passing a buffer which is too small should not overrun the buffer.
300 	 * Test this by initialising the buffer to 'Z', and only providing
301 	 * part of it to the function.
302 	 */
303 
304 	std::ranges::fill(strbuf, 'Z');
305 	auto ret = inet_net_ntop(AF_INET6, &addr6, 128, strbuf.data(), 1);
306 	ATF_REQUIRE_EQ(ret, nullptr);
307 	ATF_REQUIRE_EQ(strbuf[1], 'Z');
308 
309 	std::ranges::fill(strbuf, 'Z');
310 	ret = inet_net_ntop(AF_INET, &addr4, 32, strbuf.data(), 1);
311 	ATF_REQUIRE_EQ(ret, nullptr);
312 	ATF_REQUIRE_EQ(strbuf[1], 'Z');
313 
314 	/* Check that invalid prefix lengths return an error */
315 
316 	ret = inet_net_ntop(AF_INET6, &addr6, 129, strbuf.data(), strbuf.size());
317 	ATF_REQUIRE_EQ(ret, nullptr);
318 	ret = inet_net_ntop(AF_INET6, &addr6, -1, strbuf.data(), strbuf.size());
319 	ATF_REQUIRE_EQ(ret, nullptr);
320 
321 	ret = inet_net_ntop(AF_INET, &addr4, 33, strbuf.data(), strbuf.size());
322 	ATF_REQUIRE_EQ(ret, nullptr);
323 	ret = inet_net_ntop(AF_INET, &addr4, -1, strbuf.data(), strbuf.size());
324 	ATF_REQUIRE_EQ(ret, nullptr);
325 }
326 
327 ATF_INIT_TEST_CASES(tcs)
328 {
329 	ATF_ADD_TEST_CASE(tcs, inet_net_inet4);
330 	ATF_ADD_TEST_CASE(tcs, inet_net_inet6);
331 	ATF_ADD_TEST_CASE(tcs, inet_net_pton_invalid);
332 	ATF_ADD_TEST_CASE(tcs, inet_net_ntop_invalid);
333 }
334