Skip to content

Commit 2f0e3da

Browse files
tomutalinusg
authored andcommitted
AK: Add IPv6Address class
This is the IPv6 counter part to the IPv4Address class and implements parsing strings into a in6_addr and formatting one as a string. It supports the address compression scheme as well as IPv4 mapped addresses.
1 parent f235f08 commit 2f0e3da

File tree

3 files changed

+421
-0
lines changed

3 files changed

+421
-0
lines changed

AK/IPv6Address.h

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/*
2+
* Copyright (c) 2022, the SerenityOS developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/Endian.h>
10+
#include <AK/Format.h>
11+
#include <AK/Optional.h>
12+
#include <AK/StringView.h>
13+
#include <AK/Vector.h>
14+
15+
#ifdef KERNEL
16+
# include <AK/Error.h>
17+
# include <Kernel/KString.h>
18+
#else
19+
# include <AK/String.h>
20+
#endif
21+
#include <AK/IPv4Address.h>
22+
#include <AK/StringBuilder.h>
23+
24+
namespace AK {
25+
26+
class [[gnu::packed]] IPv6Address {
27+
public:
28+
using in6_addr_t = u8[16];
29+
30+
constexpr IPv6Address() = default;
31+
32+
constexpr IPv6Address(in6_addr_t const& data)
33+
{
34+
for (size_t i = 0; i < 16; i++)
35+
m_data[i] = data[i];
36+
}
37+
38+
constexpr IPv6Address(IPv4Address const& ipv4_address)
39+
{
40+
// IPv4 mapped IPv6 address
41+
m_data[10] = 0xff;
42+
m_data[11] = 0xff;
43+
m_data[12] = ipv4_address[0];
44+
m_data[13] = ipv4_address[1];
45+
m_data[14] = ipv4_address[2];
46+
m_data[15] = ipv4_address[3];
47+
}
48+
49+
constexpr u16 operator[](int i) const { return group(i); }
50+
51+
#ifdef KERNEL
52+
ErrorOr<NonnullOwnPtr<Kernel::KString>> to_string() const
53+
#else
54+
String to_string() const
55+
#endif
56+
{
57+
if (is_zero()) {
58+
#ifdef KERNEL
59+
return KString::try_create("::"sv);
60+
#else
61+
return "::"sv;
62+
#endif
63+
}
64+
65+
// TODO: Error propagation
66+
StringBuilder builder;
67+
68+
if (is_ipv4_mapped()) {
69+
#ifdef KERNEL
70+
return KString::formatted("::ffff:{}.{}.{}.{}", m_data[12], m_data[13], m_data[14], m_data[15]);
71+
#else
72+
return String::formatted("::ffff:{}.{}.{}.{}", m_data[12], m_data[13], m_data[14], m_data[15]);
73+
#endif
74+
}
75+
76+
// Find the start of the longest span of 0 values
77+
Optional<int> longest_zero_span_start;
78+
int zero_span_length = 0;
79+
for (int i = 0; i < 8;) {
80+
if (group(i) != 0) {
81+
i++;
82+
continue;
83+
}
84+
int contiguous_zeros = 1;
85+
for (int j = i + 1; j < 8; j++) {
86+
if (group(j) != 0)
87+
break;
88+
contiguous_zeros++;
89+
}
90+
91+
if (!longest_zero_span_start.has_value() || longest_zero_span_start.value() < contiguous_zeros) {
92+
longest_zero_span_start = i;
93+
zero_span_length = contiguous_zeros;
94+
}
95+
96+
i += contiguous_zeros;
97+
}
98+
99+
for (int i = 0; i < 8;) {
100+
if (longest_zero_span_start.has_value() && longest_zero_span_start.value() == i) {
101+
if (longest_zero_span_start.value() + zero_span_length >= 8)
102+
builder.append("::"sv);
103+
else
104+
builder.append(':');
105+
i += zero_span_length;
106+
continue;
107+
}
108+
109+
if (i == 0)
110+
builder.appendff("{:x}", group(i));
111+
else
112+
builder.appendff(":{:x}", group(i));
113+
114+
i++;
115+
}
116+
#ifdef KERNEL
117+
return KString::try_create(builder.string_view());
118+
#else
119+
return builder.string_view();
120+
#endif
121+
}
122+
123+
static Optional<IPv6Address> from_string(StringView string)
124+
{
125+
if (string.is_null())
126+
return {};
127+
128+
auto const parts = string.split_view(':', true);
129+
if (parts.is_empty())
130+
return {};
131+
if (parts.size() > 9) {
132+
// We may have 9 parts if the address is compressed
133+
// at the beginning or end, e.g. by substituting the
134+
// leading or trailing 0 with a : character. Otherwise,
135+
// the maximum number of parts is 8, which we validate
136+
// when expanding the compression.
137+
return {};
138+
}
139+
if (parts.size() >= 4 && parts[parts.size() - 1].contains('.')) {
140+
// Check if this may be an ipv4 mapped address
141+
auto is_ipv4_prefix = [&]() {
142+
auto separator_part = parts[parts.size() - 2].trim_whitespace();
143+
if (separator_part.is_empty())
144+
return false;
145+
auto separator_value = StringUtils::convert_to_uint_from_hex(separator_part);
146+
if (!separator_value.has_value() || separator_value.value() != 0xffff)
147+
return false;
148+
// TODO: this allows multiple compression tags "::" in the prefix, which is technically not legal
149+
for (size_t i = 0; i < parts.size() - 2; i++) {
150+
auto part = parts[i].trim_whitespace();
151+
if (part.is_empty())
152+
continue;
153+
auto value = StringUtils::convert_to_uint_from_hex(part);
154+
if (!value.has_value() || value.value() != 0)
155+
return false;
156+
}
157+
return true;
158+
};
159+
160+
if (is_ipv4_prefix()) {
161+
auto ipv4_address = IPv4Address::from_string(parts[parts.size() - 1]);
162+
if (ipv4_address.has_value())
163+
return IPv6Address(ipv4_address.value());
164+
return {};
165+
}
166+
}
167+
168+
in6_addr_t addr {};
169+
int group = 0;
170+
int have_groups = 0;
171+
bool found_compressed = false;
172+
for (size_t i = 0; i < parts.size();) {
173+
auto trimmed_part = parts[i].trim_whitespace();
174+
if (trimmed_part.is_empty()) {
175+
if (found_compressed)
176+
return {};
177+
int empty_parts = 1;
178+
bool is_leading = (i == 0);
179+
bool is_trailing = false;
180+
for (size_t j = i + 1; j < parts.size(); j++) {
181+
if (!parts[j].trim_whitespace().is_empty())
182+
break;
183+
empty_parts++;
184+
if (j == parts.size() - 1)
185+
is_trailing = true;
186+
}
187+
if (is_leading && is_trailing) {
188+
if (empty_parts > 3)
189+
return {};
190+
return IPv6Address();
191+
}
192+
if (is_leading || is_trailing) {
193+
if (empty_parts > 2)
194+
return {};
195+
} else if (empty_parts > 1) {
196+
return {};
197+
}
198+
199+
int remaining_parts = parts.size() - empty_parts - have_groups;
200+
found_compressed = true;
201+
group = 8 - remaining_parts;
202+
VERIFY(group >= 0);
203+
i += empty_parts;
204+
continue;
205+
} else {
206+
i++;
207+
}
208+
auto part = StringUtils::convert_to_uint_from_hex(trimmed_part);
209+
if (!part.has_value() || part.value() > 0xffff)
210+
return {};
211+
212+
if (++have_groups > 8)
213+
return {};
214+
215+
VERIFY(group < 8);
216+
addr[group * sizeof(u16)] = (u8)(part.value() >> 8);
217+
addr[group * sizeof(u16) + 1] = (u8)part.value();
218+
group++;
219+
}
220+
221+
return IPv6Address(addr);
222+
}
223+
224+
constexpr in6_addr_t const& to_in6_addr_t() const { return m_data; }
225+
226+
constexpr bool operator==(IPv6Address const& other) const = default;
227+
constexpr bool operator!=(IPv6Address const& other) const = default;
228+
229+
constexpr bool is_zero() const
230+
{
231+
for (auto& d : m_data) {
232+
if (d != 0)
233+
return false;
234+
}
235+
return true;
236+
}
237+
238+
constexpr bool is_ipv4_mapped() const
239+
{
240+
if (m_data[0] || m_data[1] || m_data[2] || m_data[3] || m_data[4] || m_data[5] || m_data[6] || m_data[7] || m_data[8] || m_data[9])
241+
return false;
242+
if (m_data[10] != 0xff || m_data[11] != 0xff)
243+
return false;
244+
return true;
245+
}
246+
247+
Optional<IPv4Address> ipv4_mapped_address() const
248+
{
249+
if (is_ipv4_mapped())
250+
return IPv4Address(m_data[12], m_data[13], m_data[14], m_data[15]);
251+
return {};
252+
}
253+
254+
private:
255+
constexpr u16 group(unsigned i) const
256+
{
257+
VERIFY(i < 8);
258+
return ((u16)m_data[i * sizeof(u16)] << 8) | m_data[i * sizeof(u16) + 1];
259+
}
260+
261+
in6_addr_t m_data {};
262+
};
263+
264+
static_assert(sizeof(IPv6Address) == 16);
265+
266+
template<>
267+
struct Traits<IPv6Address> : public GenericTraits<IPv6Address> {
268+
static constexpr unsigned hash(IPv6Address const& address)
269+
{
270+
unsigned h = 0;
271+
for (int group = 0; group < 8; group += 2) {
272+
u32 two_groups = ((u32)address[group] << 16) | (u32)address[group + 1];
273+
if (group == 0)
274+
h = int_hash(two_groups);
275+
else
276+
h = pair_int_hash(h, two_groups);
277+
}
278+
return h;
279+
}
280+
};
281+
282+
#ifdef KERNEL
283+
template<>
284+
struct Formatter<IPv6Address> : Formatter<ErrorOr<NonnullOwnPtr<Kernel::KString>>> {
285+
ErrorOr<void> format(FormatBuilder& builder, IPv6Address const& value)
286+
{
287+
return Formatter<ErrorOr<NonnullOwnPtr<Kernel::KString>>>::format(builder, value.to_string());
288+
}
289+
};
290+
#else
291+
template<>
292+
struct Formatter<IPv6Address> : Formatter<String> {
293+
ErrorOr<void> format(FormatBuilder& builder, IPv6Address const& value)
294+
{
295+
return Formatter<String>::format(builder, value.to_string());
296+
}
297+
};
298+
#endif
299+
300+
}
301+
302+
using AK::IPv6Address;

Tests/AK/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ set(AK_TEST_SOURCES
3232
TestHashTable.cpp
3333
TestHex.cpp
3434
TestIPv4Address.cpp
35+
TestIPv6Address.cpp
3536
TestIndexSequence.cpp
3637
TestIntegerMath.cpp
3738
TestIntrusiveList.cpp

0 commit comments

Comments
 (0)