common/helpers: rename PrefixTo16 to PrefixTo6

Move it to common/helpers/ipv6.go and use it where needed.
This commit is contained in:
Vincent Bernat
2025-11-04 08:05:06 +01:00
parent ffe696e0e1
commit 217c484061
10 changed files with 73 additions and 122 deletions

View File

@@ -13,9 +13,10 @@ var (
z6noz = *(*uint64)(unsafe.Add(unsafe.Pointer(&someIPv6), 16))
)
// AddrTo6 maps an IPv4 to an IPv4-mapped IPv6 and returns an IPv6 unmodified.
// This is unsafe, but there is a test to ensure netip.Addr is like we expect.
// Copying a unique.Handle bypass reference count, but z6noz is "static".
// AddrTo6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified. This is unsafe, but there is a test to ensure
// netip.Addr is like we expect. Copying a unique.Handle bypass reference count,
// but z6noz is "static".
//
// This would be trivial to implement inside netip:
//
@@ -32,3 +33,22 @@ func AddrTo6(ip netip.Addr) netip.Addr {
}
return ip
}
// PrefixTo6 maps an IPv4 prefix to an IPv4-mapped IPv6 prefix. It returns an
// IPv6 prefix unmodified.
func PrefixTo6(prefix netip.Prefix) netip.Prefix {
if prefix.Addr().Is4() {
return netip.PrefixFrom(AddrTo6(prefix.Addr()), prefix.Bits()+96)
}
return prefix
}
// UnmapPrefix unmaps a IPv4-mapped IPv6 prefix to IPv4 if it is one. Otherwise,
// it returns the provided prefix unmodified.
func UnmapPrefix(prefix netip.Prefix) netip.Prefix {
if prefix.Addr().Is4In6() && prefix.Bits() >= 96 {
ipv4Addr := prefix.Addr().Unmap()
return netip.PrefixFrom(ipv4Addr, prefix.Bits()-96)
}
return prefix
}

View File

@@ -30,6 +30,44 @@ func TestAddrTo6(t *testing.T) {
}
}
func TestPrefixTo6(t *testing.T) {
cases := []struct {
input netip.Prefix
output netip.Prefix
}{
{netip.Prefix{}, netip.Prefix{}},
{netip.MustParsePrefix("192.168.1.0/24"), netip.MustParsePrefix("::ffff:192.168.1.0/120")},
{netip.MustParsePrefix("2a01:db8::/64"), netip.MustParsePrefix("2a01:db8::/64")},
}
for _, tc := range cases {
got := helpers.PrefixTo6(tc.input)
if diff := helpers.Diff(got, tc.output); diff != "" {
t.Errorf("PrefixTo6(%s) (-got, +want):\n%s", tc.input, diff)
}
}
}
func TestUnmapPrefix(t *testing.T) {
for _, tc := range []struct {
input string
output string
}{
{"0.0.0.0/0", "0.0.0.0/0"},
{"::/0", "::/0"},
{"192.168.12.0/24", "192.168.12.0/24"},
{"2001:db8::/52", "2001:db8::/52"},
{"::ffff:192.168.12.0/120", "192.168.12.0/24"},
{"::ffff:0.0.0.0/0", "::ffff:0.0.0.0/0"},
{"::ffff:0.0.0.0/96", "0.0.0.0/0"},
} {
prefix := netip.MustParsePrefix(tc.input)
got := helpers.UnmapPrefix(prefix).String()
if diff := helpers.Diff(got, tc.output); diff != "" {
t.Errorf("UnmapPrefix(%q) (-got, +want):\n%s", tc.input, diff)
}
}
}
func TestNetIPAddrStructure(t *testing.T) {
var addr netip.Addr
addrType := reflect.TypeOf(addr)

View File

@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2025 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package helpers
import "net/netip"
// UnmapPrefix unmaps a IPv4-mapped IPv6 prefix to IPv4 if it is one. Otherwise,
// it returns the provided prefix unmodified.
func UnmapPrefix(prefix netip.Prefix) netip.Prefix {
if prefix.Addr().Is4In6() && prefix.Bits() >= 96 {
ipv4Addr := prefix.Addr().Unmap()
return netip.PrefixFrom(ipv4Addr, prefix.Bits()-96)
}
return prefix
}

View File

@@ -1,32 +0,0 @@
// SPDX-FileCopyrightText: 2025 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package helpers_test
import (
"net/netip"
"testing"
"akvorado/common/helpers"
)
func TestUnmapPrefix(t *testing.T) {
for _, tc := range []struct {
input string
output string
}{
{"0.0.0.0/0", "0.0.0.0/0"},
{"::/0", "::/0"},
{"192.168.12.0/24", "192.168.12.0/24"},
{"2001:db8::/52", "2001:db8::/52"},
{"::ffff:192.168.12.0/120", "192.168.12.0/24"},
{"::ffff:0.0.0.0/0", "::ffff:0.0.0.0/0"},
{"::ffff:0.0.0.0/96", "0.0.0.0/0"},
} {
prefix := netip.MustParsePrefix(tc.input)
got := helpers.UnmapPrefix(prefix).String()
if diff := helpers.Diff(got, tc.output); diff != "" {
t.Errorf("UnmapPrefix(%q) (-got, +want):\n%s", tc.input, diff)
}
}
}

View File

@@ -221,16 +221,6 @@ func SubnetMapUnmarshallerHook[V any]() mapstructure.DecodeHookFunc {
}
}
// PrefixTo16 converts an IPv4 prefix to an IPv4-mapped IPv6 prefix.
// IPv6 prefixes are returned as-is.
func PrefixTo16(prefix netip.Prefix) netip.Prefix {
if prefix.Addr().Is6() {
return prefix
}
// Convert IPv4 to IPv4-mapped IPv6
return netip.PrefixFrom(AddrTo6(prefix.Addr()), prefix.Bits()+96)
}
// SubnetMapParseKey parses a prefix or an IP address into a netip.Prefix that
// can be used in a map.
func SubnetMapParseKey(k string) (netip.Prefix, error) {
@@ -240,7 +230,7 @@ func SubnetMapParseKey(k string) (netip.Prefix, error) {
if err != nil {
return netip.Prefix{}, err
}
return PrefixTo16(key), nil
return PrefixTo6(key), nil
}
// IP address
key, err := netip.ParseAddr(k)
@@ -248,7 +238,7 @@ func SubnetMapParseKey(k string) (netip.Prefix, error) {
return netip.Prefix{}, err
}
if key.Is4() {
return PrefixTo16(netip.PrefixFrom(key, 32)), nil
return PrefixTo6(netip.PrefixFrom(key, 32)), nil
}
return netip.PrefixFrom(key, 128), nil
}

View File

@@ -549,50 +549,6 @@ func TestSubnetMapString(t *testing.T) {
})
}
func TestPrefixTo16(t *testing.T) {
cases := []struct {
name string
input string
expected string
}{
{
name: "IPv4 prefix",
input: "192.0.2.0/24",
expected: "::ffff:192.0.2.0/120",
},
{
name: "IPv4 host",
input: "192.0.2.1/32",
expected: "::ffff:192.0.2.1/128",
},
{
name: "IPv6 prefix unchanged",
input: "2001:db8::/64",
expected: "2001:db8::/64",
},
{
name: "IPv6 host unchanged",
input: "2001:db8::1/128",
expected: "2001:db8::1/128",
},
{
name: "IPv4-mapped IPv6 unchanged",
input: "::ffff:192.0.2.0/120",
expected: "::ffff:192.0.2.0/120",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
prefix := netip.MustParsePrefix(tc.input)
result := helpers.PrefixTo16(prefix)
if result.String() != tc.expected {
t.Fatalf("PrefixTo16(%s) = %s, want %s", tc.input, result, tc.expected)
}
})
}
}
func TestSubnetMapSupernets(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var sm *helpers.SubnetMap[string]