From 5696dc8da95388d0c3906688619582df87628097 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 17:27:03 +0200 Subject: [PATCH] net: pure-V RFC 5952 IPv6 canonicalization, fixes Ip6.str() emitting deprecated ::a.b.c.d on libc (fix #27095) (#27096) --- vlib/net/address.c.v | 16 +- vlib/net/ipv6.v | 256 +++++++++++++++++++++++++ vlib/net/ipv6_test.v | 292 +++++++++++++++++++++++++++++ vlib/net/testdata/ipv6_rfc5952.tsv | 113 +++++++++++ 4 files changed, 667 insertions(+), 10 deletions(-) create mode 100644 vlib/net/ipv6.v create mode 100644 vlib/net/ipv6_test.v create mode 100644 vlib/net/testdata/ipv6_rfc5952.tsv diff --git a/vlib/net/address.c.v b/vlib/net/address.c.v index 3502946e8..39999afbe 100644 --- a/vlib/net/address.c.v +++ b/vlib/net/address.c.v @@ -128,17 +128,13 @@ pub fn (a Ip) str() string { return '${saddr}:${port}' } -// str returns a string representation of `a` +// str returns a string representation of `a`. The IPv6 portion is +// rendered per RFC 5952 by canonical_ipv6_from_bytes (pure V), instead +// of libc's inet_ntop, which historically emits the deprecated +// IPv4-compatible mixed form (`::a.b.c.d`) for any address with the +// upper 96 bits zero. pub fn (a Ip6) str() string { - buf := [max_ip6_len]char{} - - res := &char(C.inet_ntop(i32(AddrFamily.ip6), &a.addr, &buf[0], buf.len)) - - if res == 0 { - return '' - } - - saddr := unsafe { cstring_to_vstring(res) } + saddr := canonical_ipv6_from_bytes(a.addr[..]) or { return '' } port := conv.ntoh16(a.port) return '[${saddr}]:${port}' } diff --git a/vlib/net/ipv6.v b/vlib/net/ipv6.v new file mode 100644 index 000000000..7db555b10 --- /dev/null +++ b/vlib/net/ipv6.v @@ -0,0 +1,256 @@ +module net + +// IPv6 text representation per RFC 5952 (August 2010). +// +// Pure-V replacement for libc's inet_ntop on the IPv6 path. inet_ntop +// historically follows the older RFC 1884 / RFC 2373 rules and emits +// the deprecated IPv4-compatible mixed form (`::a.b.c.d`) for any +// address whose upper 96 bits are zero — non-conformant per RFC 5952 +// §5, which restricts the mixed form to recognized IPv4-in-IPv6 +// formats. +// +// Reference: https://www.rfc-editor.org/rfc/rfc5952 +// +// Conformance summary: +// §4.1 Leading zeros MUST be suppressed. ✓ +// §4.2.1 "::" MUST be used to its maximum capability. ✓ +// §4.2.2 "::" MUST NOT shorten just one 16-bit 0 field. ✓ +// §4.2.3 Longest run of zeros wins; ties: first run wins. ✓ +// §4.3 Hex digits a-f MUST be lowercase. ✓ +// §5 IPv4-mapped (::ffff:0:0/96) uses dotted-quad ✓ +// tail; the leading zeros and 0xffff are still +// formatted per §4. Other IPv4-in-IPv6 formats +// (ISATAP, IPv4-translatable) are not detectable +// from the address bits alone and stay in hex. + +// canonical_ipv6_from_bytes formats a 16-byte IPv6 address as the +// canonical text representation defined by RFC 5952. Errors only when +// the input slice is not exactly 16 octets long. +pub fn canonical_ipv6_from_bytes(b []u8) !string { + if b.len != 16 { + return error('canonical_ipv6_from_bytes: need 16 bytes, got ${b.len}') + } + mut groups := [8]u16{} + for i in 0 .. 8 { + groups[i] = (u16(b[2 * i]) << 8) | u16(b[2 * i + 1]) + } + return format_ipv6_groups(groups) +} + +// canonical_ipv6 takes any RFC 4291 legitimate text form (full, +// "::"-compressed, or with an embedded IPv4 dotted-quad tail) and +// returns the RFC 5952 canonical form. +pub fn canonical_ipv6(addr string) !string { + bytes := parse_ipv6_to_bytes(addr)! + return canonical_ipv6_from_bytes(bytes) +} + +// format_ipv6_groups turns 8 u16 groups into the canonical RFC 5952 +// string. Only the IPv4-mapped prefix (::ffff:0:0/96, RFC 4291) +// triggers mixed notation here — other RFC 5952 §5 prefixes +// (IPv4-translatable RFC 6052, ISATAP) require out-of-band knowledge +// and are not assumed. +fn format_ipv6_groups(g [8]u16) string { + if is_ipv4_mapped(g) { + a := g[6] >> 8 + b := g[6] & 0xff + c := g[7] >> 8 + d := g[7] & 0xff + return '::ffff:${a}.${b}.${c}.${d}' + } + + start, length := longest_zero_run(g) + + mut parts := []string{cap: 8} + for v in g { + parts << v.hex() + } + + if length < 2 { + return parts.join(':') + } + + mut out := '' + if start > 0 { + out += parts[..start].join(':') + } + out += '::' + end := start + length + if end < 8 { + out += parts[end..].join(':') + } + return out +} + +// is_ipv4_mapped tests for the ::ffff:0:0/96 prefix (RFC 4291 §2.5.5.2). +fn is_ipv4_mapped(g [8]u16) bool { + return g[0] == 0 && g[1] == 0 && g[2] == 0 && g[3] == 0 && g[4] == 0 && g[5] == 0xffff +} + +// longest_zero_run scans the 8 groups for the longest contiguous run of +// 0x0000 fields and returns (start_index, length). On ties the FIRST +// run wins (RFC 5952 §4.2.3). When no zero is present, length is 0. +fn longest_zero_run(g [8]u16) (int, int) { + mut best_start := 0 + mut best_len := 0 + mut cur_start := 0 + mut cur_len := 0 + for i in 0 .. 8 { + if g[i] == 0 { + if cur_len == 0 { + cur_start = i + } + cur_len++ + if cur_len > best_len { + best_len = cur_len + best_start = cur_start + } + } else { + cur_len = 0 + } + } + return best_start, best_len +} + +// parse_ipv6_to_bytes accepts the legitimate RFC 4291 forms: +// - 8 groups of 1..4 hex digits separated by ':' +// - the same with one "::" run replacing one or more all-zero groups +// - the same with the trailing 32 bits given as dotted-quad IPv4 +// Returns 16 octets in network order. +fn parse_ipv6_to_bytes(s string) ![]u8 { + if s == '' { + return error('parse_ipv6: empty') + } + double_idx := s.index('::') or { -1 } + if s.count('::') > 1 { + return error('parse_ipv6: multiple "::" runs') + } + + head, tail := if double_idx >= 0 { + s[..double_idx], s[double_idx + 2..] + } else { + s, '' + } + + mut head_g := if head == '' { []string{} } else { head.split(':') } + mut tail_g := if tail == '' { []string{} } else { tail.split(':') } + mut v4_bytes := []u8{} + + // Detect dotted-quad tail in the LAST group (of tail when "::" present, + // of head otherwise). + tail_owns_last := double_idx >= 0 + last_group := if tail_owns_last && tail_g.len > 0 { + tail_g[tail_g.len - 1] + } else if !tail_owns_last && head_g.len > 0 { + head_g[head_g.len - 1] + } else { + '' + } + if last_group.contains('.') { + v4_bytes = parse_dotted_quad(last_group)! + if tail_owns_last { + tail_g = tail_g[..tail_g.len - 1].clone() + } else { + head_g = head_g[..head_g.len - 1].clone() + } + } + + mut head_words := []u16{} + for h in head_g { + head_words << parse_hex_group(h)! + } + mut tail_words := []u16{} + for t in tail_g { + tail_words << parse_hex_group(t)! + } + + v4_word_len := if v4_bytes.len == 4 { 2 } else { 0 } + total := head_words.len + tail_words.len + v4_word_len + if double_idx < 0 { + if total != 8 { + return error('parse_ipv6: ${s} must have exactly 8 groups (got ${total})') + } + } else { + if total >= 8 { + return error('parse_ipv6: "::" present but address already has ${total} groups') + } + } + + mut words := []u16{cap: 8} + for w in head_words { + words << w + } + if double_idx >= 0 { + for _ in 0 .. (8 - total) { + words << u16(0) + } + } + for w in tail_words { + words << w + } + if v4_bytes.len == 4 { + words << (u16(v4_bytes[0]) << 8) | u16(v4_bytes[1]) + words << (u16(v4_bytes[2]) << 8) | u16(v4_bytes[3]) + } + + if words.len != 8 { + return error('parse_ipv6: internal: expanded to ${words.len} groups') + } + + mut out := []u8{cap: 16} + for w in words { + out << u8(w >> 8) + out << u8(w & 0xff) + } + return out +} + +fn parse_hex_group(s string) !u16 { + if s == '' || s.len > 4 { + return error('parse_ipv6: bad hex group "${s}"') + } + mut v := u32(0) + for c in s { + d := hex_digit(c) or { return error('parse_ipv6: non-hex char in "${s}"') } + v = (v << 4) | u32(d) + } + return u16(v) +} + +fn hex_digit(c u8) ?u8 { + if c >= `0` && c <= `9` { + return u8(c - `0`) + } + if c >= `a` && c <= `f` { + return u8(c - `a` + 10) + } + if c >= `A` && c <= `F` { + return u8(c - `A` + 10) + } + return none +} + +fn parse_dotted_quad(s string) ![]u8 { + parts := s.split('.') + if parts.len != 4 { + return error('parse_ipv6: bad dotted-quad "${s}"') + } + mut out := []u8{cap: 4} + for p in parts { + if p.len == 0 || p.len > 3 { + return error('parse_ipv6: bad octet "${p}"') + } + mut v := u32(0) + for c in p { + if c < `0` || c > `9` { + return error('parse_ipv6: non-digit in octet "${p}"') + } + v = v * 10 + u32(c - `0`) + } + if v > 255 { + return error('parse_ipv6: octet "${p}" > 255') + } + out << u8(v) + } + return out +} diff --git a/vlib/net/ipv6_test.v b/vlib/net/ipv6_test.v new file mode 100644 index 000000000..159c89d32 --- /dev/null +++ b/vlib/net/ipv6_test.v @@ -0,0 +1,292 @@ +module net + +import os + +// Conformance tests for RFC 5952 (IPv6 text representation). +// +// Each vector is `(input_form, expected_canonical)`. Inputs include +// non-canonical legitimate forms (uppercase, leading zeros, partial +// "::" placement) so that canonical_ipv6() also exercises the parser. + +struct V6Vec { + input string + expected string +} + +const rfc5952_vectors = [ + // -------- §4.1 Leading zeros MUST be suppressed -------- + V6Vec{'2001:0db8:0000:0000:0000:0000:0000:0001', '2001:db8::1'}, + V6Vec{'0000:0000:0000:0000:0000:0000:0000:0000', '::'}, + V6Vec{'0000:0000:0000:0000:0000:0000:0000:0001', '::1'}, + // "A single 16-bit 0000 field MUST be represented as 0." + V6Vec{'2001:0db8:0000:0001:0001:0001:0001:0001', '2001:db8:0:1:1:1:1:1'}, + // -------- §4.2.1 Shorten as much as possible -------- + V6Vec{'2001:db8:0:0:0:0:2:1', '2001:db8::2:1'}, + // Counter-example from the RFC: "2001:db8::0:1 is not acceptable". + V6Vec{'2001:db8:0:0:0:0:0:1', '2001:db8::1'}, + // -------- §4.2.2 "::" MUST NOT shorten just one 16-bit 0 field -------- + // "2001:db8::1:1:1:1:1 is not correct" — single 0, leave as 0. + V6Vec{'2001:db8:0:1:1:1:1:1', '2001:db8:0:1:1:1:1:1'}, + // Several singletons, none shortened: + V6Vec{'1:0:1:0:1:0:1:0', '1:0:1:0:1:0:1:0'}, + V6Vec{'0:1:0:1:0:1:0:1', '0:1:0:1:0:1:0:1'}, + // -------- §4.2.3 Longest run wins; ties → first run -------- + // Three zeros (0,0,0) at positions 1..3 outweighs two zeros at 4..5. + V6Vec{'2001:0:0:0:1:0:0:1', '2001::1:0:0:1'}, + // Tie: two equal pairs (0,0) at positions 2..3 and 5..6 — first wins. + V6Vec{'2001:db8:0:0:1:0:0:1', '2001:db8::1:0:0:1'}, + // Earlier example from §4.2.3 with three trailing zero fields: + V6Vec{'2001:0:0:1:0:0:0:1', '2001:0:0:1::1'}, + // -------- §4.3 Lowercase -------- + V6Vec{'2001:0DB8:AC10:FE01:0000:0000:0000:0000', '2001:db8:ac10:fe01::'}, + V6Vec{'FE80::1', 'fe80::1'}, + V6Vec{'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'}, + // -------- §5 IPv4-mapped (::ffff:0:0/96) → mixed notation -------- + V6Vec{'0:0:0:0:0:ffff:c000:0201', '::ffff:192.0.2.1'}, + V6Vec{'::ffff:192.0.2.1', '::ffff:192.0.2.1'}, + V6Vec{'0000:0000:0000:0000:0000:FFFF:0A00:0001', '::ffff:10.0.0.1'}, + V6Vec{'::ffff:0.0.0.0', '::ffff:0.0.0.0'}, + V6Vec{'::ffff:255.255.255.255', '::ffff:255.255.255.255'}, + // -------- Run at start (>=2) -------- + V6Vec{'0:0:0:0:0:0:0:2', '::2'}, + V6Vec{'0:0:0:0:0:0:1:2', '::1:2'}, + V6Vec{'0:0:c:d:e:f:1:2', '::c:d:e:f:1:2'}, + // -------- Run at end (>=2) -------- + V6Vec{'1::', '1::'}, + V6Vec{'1:0:0:0:0:0:0:0', '1::'}, + V6Vec{'a:b:c:d:e:f:0:0', 'a:b:c:d:e:f::'}, + V6Vec{'2001:db8:ac10:fe01::', '2001:db8:ac10:fe01::'}, + // -------- Run in the middle -------- + V6Vec{'2001:db8::1', '2001:db8::1'}, + V6Vec{'fe80:0:0:0:0:0:0:1', 'fe80::1'}, + // -------- Loopback / unspecified -------- + V6Vec{'::1', '::1'}, + V6Vec{'::', '::'}, + // -------- No zero field at all -------- + V6Vec{'1:2:3:4:5:6:7:8', '1:2:3:4:5:6:7:8'}, + V6Vec{'2001:db8:1:2:3:4:5:6', '2001:db8:1:2:3:4:5:6'}, + // -------- Mixed leading-zero suppression with longer/shorter values -------- + V6Vec{'0001:0023:0456:7890:abcd:00ef:0:1', '1:23:456:7890:abcd:ef:0:1'}, + // -------- Two equal-length non-trivial runs (§4.2.3 tie) -------- + V6Vec{'0:0:1:2:3:4:0:0', '::1:2:3:4:0:0'}, + // -------- Run of length exactly 2 still triggers compression -------- + V6Vec{'1:2:0:0:3:4:5:6', '1:2::3:4:5:6'}, + // -------- Adjacent zero singletons: NEITHER must be compressed when + // a longer run exists -------- + V6Vec{'0:1:0:0:0:1:0:1', '0:1::1:0:1'}, + // -------- Parser robustness -------- + V6Vec{'2001:DB8::AB:CD', '2001:db8::ab:cd'}, + V6Vec{'::ffff:192.000.002.001', '::ffff:192.0.2.1'}, +]! + +fn test_rfc5952_vectors() { + mut failures := []string{} + for v in rfc5952_vectors { + got := canonical_ipv6(v.input) or { + failures << '${v.input} -> error: ${err}' + continue + } + if got != v.expected { + failures << '${v.input} -> got ${got}, want ${v.expected}' + } + } + if failures.len > 0 { + for f in failures { + eprintln(' FAIL: ${f}') + } + assert false, '${failures.len}/${rfc5952_vectors.len} RFC 5952 vectors failed' + } +} + +fn test_from_bytes_basic() { + zeros := []u8{len: 16} + assert canonical_ipv6_from_bytes(zeros)! == '::' + + mut loop := []u8{len: 16} + loop[15] = 1 + assert canonical_ipv6_from_bytes(loop)! == '::1' + + bytes := [u8(0x20), 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] + assert canonical_ipv6_from_bytes(bytes)! == '2001:db8::1' + + mapped := [u8(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 0, 2, 1] + assert canonical_ipv6_from_bytes(mapped)! == '::ffff:192.0.2.1' +} + +fn test_from_bytes_rejects_wrong_length() { + if _ := canonical_ipv6_from_bytes([u8(0)]) { + assert false, 'must reject 1-byte input' + } + if _ := canonical_ipv6_from_bytes([]u8{len: 15}) { + assert false, 'must reject 15-byte input' + } + if _ := canonical_ipv6_from_bytes([]u8{len: 17}) { + assert false, 'must reject 17-byte input' + } +} + +fn test_canonical_is_idempotent() { + for v in rfc5952_vectors { + once := canonical_ipv6(v.input) or { + assert false, 'first pass failed on ${v.input}: ${err}' + continue + } + twice := canonical_ipv6(once) or { + assert false, 'second pass failed on ${once}: ${err}' + continue + } + assert once == twice, 'idempotence broken: ${v.input} -> ${once} -> ${twice}' + } +} + +fn test_parser_rejects_malformed() { + bad := [ + '', + 'gggg::1', // non-hex + '1::2::3', // two "::" + '1:2:3:4:5:6:7:8:9', // too many groups, no "::" + '1:2:3:4:5:6:7', // too few groups, no "::" + '1:2:3:4:5:6:7:8::', // "::" with already 8 groups + '12345::1', // group too long + '1:2:3:4:5:6:1.2.3', // bad dotted quad (3 octets) + '1:2:3:4:5:6:1.2.3.4.5', // bad dotted quad (5 octets) + '::ffff:1.2.3.999', // octet > 255 + '::ffff:1.2.3.', // trailing dot in v4 + ] + for s in bad { + if _ := canonical_ipv6(s) { + assert false, 'parser must reject "${s}"' + } + } +} + +// test_inet_ntop_corpus loads testdata/ipv6_rfc5952.tsv (113 vectors +// generated by Python's ipaddress.IPv6Address.compressed) and confirms +// our pure-V implementation produces byte-identical output. +fn test_inet_ntop_corpus() { + path := os.join_path(os.dir(@FILE), 'testdata', 'ipv6_rfc5952.tsv') + body := os.read_file(path) or { + assert false, 'cannot read fixture ${path}: ${err}' + return + } + mut total := 0 + mut failed := []string{} + for raw in body.split('\n') { + line := raw.trim_space() + if line.len == 0 { + continue + } + fields := line.split('\t') + if fields.len != 2 { + assert false, 'bad fixture line: ${line}' + continue + } + hex_addr := fields[0] + want := fields[1] + bytes := hex_to_bytes(hex_addr) or { + assert false, 'bad hex: ${hex_addr}' + continue + } + got := canonical_ipv6_from_bytes(bytes) or { + failed << '${hex_addr}: ${err}' + continue + } + if got != want { + failed << '${hex_addr}: got "${got}", want "${want}"' + } + total++ + } + if failed.len > 0 { + for f in failed { + eprintln(' FAIL: ${f}') + } + assert false, '${failed.len}/${total} cross-check vectors failed' + } + assert total >= 100, 'expected >= 100 vectors, got ${total}' +} + +fn hex_to_bytes(s string) ![]u8 { + if s.len % 2 != 0 { + return error('hex_to_bytes: odd length') + } + mut out := []u8{cap: s.len / 2} + for i := 0; i < s.len; i += 2 { + hi := hex_digit(s[i]) or { return error('bad hex at ${i}') } + lo := hex_digit(s[i + 1]) or { return error('bad hex at ${i + 1}') } + out << (hi << 4) | lo + } + return out +} + +// Property: bytes -> canonical -> parse -> bytes round-trip + idempotence. +fn test_property_roundtrip_and_idempotence() { + mut state := u64(0xCAFEBABEDEADBEEF) + for iter in 0 .. 1000 { + mut bytes := []u8{cap: 16} + for _ in 0 .. 16 { + state = state * 6364136223846793005 + 1442695040888963407 + bytes << u8(state >> 56) + } + if iter % 5 == 0 { + run_start := int((state >> 8) & 0x7) + run_len := int((state >> 16) & 0x7) + 1 + for k in 0 .. run_len { + idx := 2 * (run_start + k) + if idx + 1 < bytes.len { + bytes[idx] = 0 + bytes[idx + 1] = 0 + } + } + } + canon := canonical_ipv6_from_bytes(bytes) or { + assert false, 'canonical_ipv6_from_bytes failed on iter ${iter}: ${err}' + continue + } + parsed := parse_ipv6_to_bytes(canon) or { + assert false, 'parse_ipv6_to_bytes failed on canonical "${canon}" (iter ${iter}): ${err}' + continue + } + assert parsed == bytes, 'round-trip mismatch on iter ${iter}: bytes=${bytes.hex()}, canon="${canon}", parsed=${parsed.hex()}' + + canon2 := canonical_ipv6(canon) or { + assert false, 'canonical_ipv6 failed on its own output "${canon}" (iter ${iter}): ${err}' + continue + } + assert canon == canon2, 'idempotence broken on iter ${iter}: "${canon}" -> "${canon2}"' + } +} + +fn test_alternative_forms_canonicalize_identically() { + cases := [ + ['2001:db8:0:0:0:0:0:1', '2001:db8::1'], + ['2001:DB8:0000:0000:0000:0000:0000:0001', '2001:db8::1'], + ['2001:0db8::0001', '2001:db8::1'], + ['2001:DB8::1', '2001:db8::1'], + ['0:0:0:0:0:0:0:0', '::'], + ['0000:0000:0000:0000:0000:0000:0000:0000', '::'], + ['0:0:0:0:0:ffff:c0a8:0001', '::ffff:192.168.0.1'], + ['::FFFF:192.168.0.1', '::ffff:192.168.0.1'], + ['::ffff:c0a8:1', '::ffff:192.168.0.1'], + ['fe80:0:0:0:0:0:0:1', 'fe80::1'], + ['FE80::0:0:0:1', 'fe80::1'], + ] + for c in cases { + got := canonical_ipv6(c[0]) or { + assert false, 'canonical_ipv6("${c[0]}") errored: ${err}' + continue + } + assert got == c[1], '"${c[0]}" -> got "${got}", want "${c[1]}"' + } +} + +fn test_well_known_corpus() { + assert canonical_ipv6('::')! == '::' + assert canonical_ipv6('::1')! == '::1' + assert canonical_ipv6('1::')! == '1::' + assert canonical_ipv6('fe80::1')! == 'fe80::1' + assert canonical_ipv6('2001:db8::')! == '2001:db8::' + assert canonical_ipv6('2001:db8:0:0:0:0:0:1')! == '2001:db8::1' + assert canonical_ipv6('2001:db8:0:0:1:0:0:1')! == '2001:db8::1:0:0:1' + // §5 is opt-in for non-::ffff:0:0/96 prefixes; we keep them as hex. + assert canonical_ipv6('64:ff9b::192.0.2.33')! == '64:ff9b::c000:221' +} diff --git a/vlib/net/testdata/ipv6_rfc5952.tsv b/vlib/net/testdata/ipv6_rfc5952.tsv new file mode 100644 index 000000000..440f8a3e0 --- /dev/null +++ b/vlib/net/testdata/ipv6_rfc5952.tsv @@ -0,0 +1,113 @@ +c286ec2db679dca7925a1d5995be183d c286:ec2d:b679:dca7:925a:1d59:95be:183d +7464dca3bcf3e6ca29ae867d887980b0 7464:dca3:bcf3:e6ca:29ae:867d:8879:80b0 +80ea00b5a5f812276a6c820587fe4067 80ea:b5:a5f8:1227:6a6c:8205:87fe:4067 +62c9b502c5ac9f1a27224fdcab179cec 62c9:b502:c5ac:9f1a:2722:4fdc:ab17:9cec +495956d36032330554fda95cb2c4f836 4959:56d3:6032:3305:54fd:a95c:b2c4:f836 +f7a4fbb3340d4279f58f553eed9b9dac f7a4:fbb3:340d:4279:f58f:553e:ed9b:9dac +7de6306f4e3da79ae6e045df2e0321e1 7de6:306f:4e3d:a79a:e6e0:45df:2e03:21e1 +83907aa581bd79f0ee8efcc8dd825f29 8390:7aa5:81bd:79f0:ee8e:fcc8:dd82:5f29 +1d4c9c97d2248e7e2c330d56a4522702 1d4c:9c97:d224:8e7e:2c33:d56:a452:2702 +7a01b9bbd0c4b2cbe917c6e4124ccceb 7a01:b9bb:d0c4:b2cb:e917:c6e4:124c:cceb +8da1258510afc2aa82f61948707a849d 8da1:2585:10af:c2aa:82f6:1948:707a:849d +0169b27f22d16fc68c2defd97e58f220 169:b27f:22d1:6fc6:8c2d:efd9:7e58:f220 +f0b01e4d9a6d7a753fa6907cb5304158 f0b0:1e4d:9a6d:7a75:3fa6:907c:b530:4158 +5762e27dc0c838e21e3d403ecf2c70a9 5762:e27d:c0c8:38e2:1e3d:403e:cf2c:70a9 +3f607c6f995458d1e27137dbd6a5f5fc 3f60:7c6f:9954:58d1:e271:37db:d6a5:f5fc +c9d637e5201a67014ea987e6e44cf3e1 c9d6:37e5:201a:6701:4ea9:87e6:e44c:f3e1 +509ac38a305bdf498950819f472739d4 509a:c38a:305b:df49:8950:819f:4727:39d4 +84a6ff94ce969dac78f9d4bede655652 84a6:ff94:ce96:9dac:78f9:d4be:de65:5652 +8788864631b49e0f112ae3dc0819f31b 8788:8646:31b4:9e0f:112a:e3dc:819:f31b +9ce0a493dcb3d73007c2e628048e8740 9ce0:a493:dcb3:d730:7c2:e628:48e:8740 +e69eeed27c5f8ea5df3afa50a61b50dd e69e:eed2:7c5f:8ea5:df3a:fa50:a61b:50dd +e7e3993b4fc9acef2e76d50278ef6ced e7e3:993b:4fc9:acef:2e76:d502:78ef:6ced +9fdf54d7dd2b1da22b91396d8188c460 9fdf:54d7:dd2b:1da2:2b91:396d:8188:c460 +eba0a35b0618969f62711118e5758857 eba0:a35b:618:969f:6271:1118:e575:8857 +4a28c51da0ba655149511190e1fc076e 4a28:c51d:a0ba:6551:4951:1190:e1fc:76e +537c6c4ecc7bd6fcc0d96920d4071392 537c:6c4e:cc7b:d6fc:c0d9:6920:d407:1392 +563f8c8583cb7025e3b35e86db128342 563f:8c85:83cb:7025:e3b3:5e86:db12:8342 +2a39fb4110bae496ecaefdbc53b4f917 2a39:fb41:10ba:e496:ecae:fdbc:53b4:f917 +df1e1f652e037717f507d287dfdb4dd3 df1e:1f65:2e03:7717:f507:d287:dfdb:4dd3 +87ff1ff1ae0f9f40d1be18a796720fbc 87ff:1ff1:ae0f:9f40:d1be:18a7:9672:fbc +19073c75447ffb66b16ca72057999f31 1907:3c75:447f:fb66:b16c:a720:5799:9f31 +65ad6dab78d10325d450350fdf9ffac5 65ad:6dab:78d1:325:d450:350f:df9f:fac5 +927fb3eb55772efa520a53e4169dafae 927f:b3eb:5577:2efa:520a:53e4:169d:afae +bebf9d1c74e363350d2d1ae57687c84a bebf:9d1c:74e3:6335:d2d:1ae5:7687:c84a +a334a42f7614f06f00d875831b34af5a a334:a42f:7614:f06f:d8:7583:1b34:af5a +73dba827b9bc3f63d767191e6ad605ac 73db:a827:b9bc:3f63:d767:191e:6ad6:5ac +46aaa7fe350f54cb67db7266f8ff0cde 46aa:a7fe:350f:54cb:67db:7266:f8ff:cde +b69f13c5c77341dfecc20c89a331d5ca b69f:13c5:c773:41df:ecc2:c89:a331:d5ca +6eb1c45d0287cc5edc2477e04885dbc3 6eb1:c45d:287:cc5e:dc24:77e0:4885:dbc3 +dc273f4732ac24e9c037efcdb025331d dc27:3f47:32ac:24e9:c037:efcd:b025:331d +107766d5aaf15604eac0ea3a292811db 1077:66d5:aaf1:5604:eac0:ea3a:2928:11db +68f4ef6853d6a2dacff4ecf36c61e3f2 68f4:ef68:53d6:a2da:cff4:ecf3:6c61:e3f2 +87b5cea2258332677e16454977aad829 87b5:cea2:2583:3267:7e16:4549:77aa:d829 +76095dc492dcebfe5538365da6d5dd46 7609:5dc4:92dc:ebfe:5538:365d:a6d5:dd46 +1a0624aed1ee87d0116272f4b6d005b1 1a06:24ae:d1ee:87d0:1162:72f4:b6d0:5b1 +48356710bf7c501a2ca214bc9e03f18f 4835:6710:bf7c:501a:2ca2:14bc:9e03:f18f +3f4ec5eefffe4d5550248c54841f3942 3f4e:c5ee:fffe:4d55:5024:8c54:841f:3942 +db5e160f347a6f3b6de6d53cd33526b7 db5e:160f:347a:6f3b:6de6:d53c:d335:26b7 +66a5c66897ffe6d6d179f97e79af71c0 66a5:c668:97ff:e6d6:d179:f97e:79af:71c0 +2efdae2963da99ce181bf2186914b631 2efd:ae29:63da:99ce:181b:f218:6914:b631 +00000000000000000000000000000000 :: +00000000000000000000000000000001 ::1 +20010db8000000000000000000000001 2001:db8::1 +20010db8ac10fe010000000000000000 2001:db8:ac10:fe01:: +00000000000000000000ffffc0000201 ::ffff:192.0.2.1 +00000000000000000000ffff00000000 ::ffff:0.0.0.0 +00000000000000000000ffffffffffff ::ffff:255.255.255.255 +20010000000000010000000000000001 2001:0:0:1::1 +20010db8000000000001000000000001 2001:db8::1:0:0:1 +00010000000100000001000000010000 1:0:1:0:1:0:1:0 +fe800000000000000000000000000001 fe80::1 +00000000000000000000000000000002 ::2 +00010000000000000000000000000000 1:: +000a000b000c000d000e000f00000000 a:b:c:d:e:f:: +00000000000c000d000e000f00010002 ::c:d:e:f:1:2 +ffffffffffffffffffffffffffffffff ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff +00000000000100020003000400000000 ::1:2:3:4:0:0 +00010002000000000003000400050006 1:2::3:4:5:6 +000100230456789000abcdef00000001 1:23:456:7890:ab:cdef:0:1 +00000000000300040005000600070008 ::3:4:5:6:7:8 +00000000000000040005000600070008 ::4:5:6:7:8 +00000000000000000005000600070008 ::5:6:7:8 +00000000000000000000000600070008 ::6:7:8 +00000000000000000000000000070008 ::7:8 +00000000000000000000000000000008 ::8 +00010000000000040005000600070008 1::4:5:6:7:8 +00010000000000000005000600070008 1::5:6:7:8 +00010000000000000000000600070008 1::6:7:8 +00010000000000000000000000070008 1::7:8 +00010000000000000000000000000008 1::8 +00010002000000000005000600070008 1:2::5:6:7:8 +00010002000000000000000600070008 1:2::6:7:8 +00010002000000000000000000070008 1:2::7:8 +00010002000000000000000000000008 1:2::8 +00010002000000000000000000000000 1:2:: +00010002000300000000000600070008 1:2:3::6:7:8 +00010002000300000000000000070008 1:2:3::7:8 +00010002000300000000000000000008 1:2:3::8 +00010002000300000000000000000000 1:2:3:: +00010002000300040000000000070008 1:2:3:4::7:8 +00010002000300040000000000000008 1:2:3:4::8 +00010002000300040000000000000000 1:2:3:4:: +00010002000300040005000000000008 1:2:3:4:5::8 +00010002000300040005000000000000 1:2:3:4:5:: +00010002000300040005000600000000 1:2:3:4:5:6:: +00000000000100000000000100010001 ::1:0:0:1:1:1 +00000000000100010000000000010001 ::1:1:0:0:1:1 +00000000000100010001000000000001 ::1:1:1:0:0:1 +00000000000100010001000100000000 ::1:1:1:1:0:0 +00010000000000010000000000010001 1::1:0:0:1:1 +00010000000000010001000000000001 1::1:1:0:0:1 +00010000000000010001000100000000 1::1:1:1:0:0 +00010001000000000001000000000001 1:1::1:0:0:1 +00010001000000000001000100000000 1:1::1:1:0:0 +00010001000100000000000100000000 1:1:1::1:0:0 +00000000000000010000000000000001 ::1:0:0:0:1 +00000000000000010001000000000000 ::1:1:0:0:0 +00010000000000000001000000000000 1::1:0:0:0 +00000001000000010000000100000001 0:1:0:1:0:1:0:1 +00000000000000000000ffff0a000001 ::ffff:10.0.0.1 +00000000000000000000ffff7f000001 ::ffff:127.0.0.1 +00000000000000000000ffff08080808 ::ffff:8.8.8.8 +00000000000000000000ffffff0000ff ::ffff:255.0.0.255 -- 2.39.5