mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
common/helpers: add a NetIPTo6() function
Some checks failed
CI / 🤖 Check dependabot status (push) Has been cancelled
CI / 🐧 Test on Linux (${{ github.ref_type == 'tag' }}, misc) (push) Has been cancelled
CI / 🐧 Test on Linux (coverage) (push) Has been cancelled
CI / 🐧 Test on Linux (regular) (push) Has been cancelled
CI / ❄️ Build on Nix (push) Has been cancelled
CI / 🍏 Build and test on macOS (push) Has been cancelled
CI / 🧪 End-to-end testing (push) Has been cancelled
CI / 🔍 Upload code coverage (push) Has been cancelled
CI / 🔬 Test only Go (push) Has been cancelled
CI / 🔬 Test only JS (${{ needs.dependabot.outputs.package-ecosystem }}, 20) (push) Has been cancelled
CI / 🔬 Test only JS (${{ needs.dependabot.outputs.package-ecosystem }}, 22) (push) Has been cancelled
CI / 🔬 Test only JS (${{ needs.dependabot.outputs.package-ecosystem }}, 24) (push) Has been cancelled
CI / ⚖️ Check licenses (push) Has been cancelled
CI / 🐋 Build Docker images (push) Has been cancelled
CI / 🐋 Tag Docker images (push) Has been cancelled
CI / 🚀 Publish release (push) Has been cancelled
Update Nix dependency hashes / Update dependency hashes (push) Has been cancelled
Some checks failed
CI / 🤖 Check dependabot status (push) Has been cancelled
CI / 🐧 Test on Linux (${{ github.ref_type == 'tag' }}, misc) (push) Has been cancelled
CI / 🐧 Test on Linux (coverage) (push) Has been cancelled
CI / 🐧 Test on Linux (regular) (push) Has been cancelled
CI / ❄️ Build on Nix (push) Has been cancelled
CI / 🍏 Build and test on macOS (push) Has been cancelled
CI / 🧪 End-to-end testing (push) Has been cancelled
CI / 🔍 Upload code coverage (push) Has been cancelled
CI / 🔬 Test only Go (push) Has been cancelled
CI / 🔬 Test only JS (${{ needs.dependabot.outputs.package-ecosystem }}, 20) (push) Has been cancelled
CI / 🔬 Test only JS (${{ needs.dependabot.outputs.package-ecosystem }}, 22) (push) Has been cancelled
CI / 🔬 Test only JS (${{ needs.dependabot.outputs.package-ecosystem }}, 24) (push) Has been cancelled
CI / ⚖️ Check licenses (push) Has been cancelled
CI / 🐋 Build Docker images (push) Has been cancelled
CI / 🐋 Tag Docker images (push) Has been cancelled
CI / 🚀 Publish release (push) Has been cancelled
Update Nix dependency hashes / Update dependency hashes (push) Has been cancelled
This should be netip.To6() but it does not exist and it was rejected. There is a benchmark showing the improvment of such optimisation: BenchmarkNetIPTo6/safe_v4-12 170152954 7.054 ns/op BenchmarkNetIPTo6/unsafe_v4-12 764772190 1.553 ns/op See https://github.com/golang/go/issues/54365.
This commit is contained in:
2
common/helpers/cache/cache_test.go
vendored
2
common/helpers/cache/cache_test.go
vendored
@@ -15,7 +15,7 @@ import (
|
||||
func expectCacheGet(t *testing.T, c *cache.Cache[netip.Addr, string], key string, expectedResult string, expectedOk bool) {
|
||||
t.Helper()
|
||||
ip := netip.MustParseAddr(key)
|
||||
ip = netip.AddrFrom16(ip.As16())
|
||||
ip = helpers.NetIPTo6(ip)
|
||||
result, ok := c.Get(time.Time{}, ip)
|
||||
got := struct {
|
||||
Result string
|
||||
|
||||
34
common/helpers/ipv6.go
Normal file
34
common/helpers/ipv6.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2025 Free Mobile
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
someIPv6 = netip.MustParseAddr("2001:db8::1")
|
||||
z6noz = *(*uint64)(unsafe.Add(unsafe.Pointer(&someIPv6), 16))
|
||||
)
|
||||
|
||||
// NetIPTo6 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".
|
||||
//
|
||||
// This would be trivial to implement inside netip:
|
||||
//
|
||||
// func (ip Addr) Unmap() Addr {
|
||||
// if ip.Is4() {
|
||||
// ip.z = z6noz
|
||||
// }
|
||||
// return ip
|
||||
// }
|
||||
func NetIPTo6(ip netip.Addr) netip.Addr {
|
||||
if ip.Is4() {
|
||||
p := (*uint64)(unsafe.Add(unsafe.Pointer(&ip), 16))
|
||||
*p = z6noz
|
||||
}
|
||||
return ip
|
||||
}
|
||||
116
common/helpers/ipv6_test.go
Normal file
116
common/helpers/ipv6_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// SPDX-FileCopyrightText: 2025 Free Mobile
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package helpers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
)
|
||||
|
||||
func TestNetIPTo6(t *testing.T) {
|
||||
cases := []struct {
|
||||
input netip.Addr
|
||||
output netip.Addr
|
||||
}{
|
||||
{netip.Addr{}, netip.Addr{}},
|
||||
{netip.MustParseAddr("192.168.1.1"), netip.MustParseAddr("::ffff:192.168.1.1")},
|
||||
{netip.MustParseAddr("2a01:db8::1"), netip.MustParseAddr("2a01:db8::1")},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := helpers.NetIPTo6(tc.input)
|
||||
if diff := helpers.Diff(got, tc.output); diff != "" {
|
||||
t.Errorf("NetIPTo6(%s) (-got, +want):\n%s", tc.input, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetIPAddrStructure(t *testing.T) {
|
||||
var addr netip.Addr
|
||||
addrType := reflect.TypeOf(addr)
|
||||
|
||||
// Test total size: 24 bytes (16 for uint128 + 8 for unique.Handle)
|
||||
if unsafe.Sizeof(addr) != 24 {
|
||||
t.Errorf("netip.Addr size = %d, want 24", unsafe.Sizeof(addr))
|
||||
}
|
||||
|
||||
// Test number of fields
|
||||
if addrType.NumField() != 2 {
|
||||
t.Errorf("netip.Addr has %d fields, want 2", addrType.NumField())
|
||||
}
|
||||
|
||||
// Test field 0: addr (uint128, 16 bytes)
|
||||
field0 := addrType.Field(0)
|
||||
if field0.Name != "addr" {
|
||||
t.Errorf("field 0 name = %q, want %q", field0.Name, "addr")
|
||||
}
|
||||
if field0.Type.String() != "netip.uint128" {
|
||||
t.Errorf("field 0 type = %q, want %q", field0.Type.String(), "netip.uint128")
|
||||
}
|
||||
if field0.Offset != 0 {
|
||||
t.Errorf("field 0 offset = %d, want 0", field0.Offset)
|
||||
}
|
||||
if field0.Type.Size() != 16 {
|
||||
t.Errorf("field 0 (addr) size = %d, want 16", field0.Type.Size())
|
||||
}
|
||||
|
||||
// Test field 1: z (unique.Handle, 8 bytes)
|
||||
field1 := addrType.Field(1)
|
||||
if field1.Name != "z" {
|
||||
t.Errorf("field 1 name = %q, want %q", field1.Name, "z")
|
||||
}
|
||||
if field1.Type.String() != "unique.Handle[net/netip.addrDetail]" {
|
||||
t.Errorf("field 0 type = %q, want %q", field1.Type.String(), "unique.Handle[net/netip.addrDetail]")
|
||||
}
|
||||
if field1.Offset != 16 {
|
||||
t.Errorf("field 1 offset = %d, want 16", field1.Offset)
|
||||
}
|
||||
if field1.Type.Size() != 8 {
|
||||
t.Errorf("field 1 (z) size = %d, want 8", field1.Type.Size())
|
||||
}
|
||||
|
||||
t.Logf("netip.Addr structure verified: [addr %d bytes @ 0] [z %d bytes @ 16]",
|
||||
field0.Type.Size(), field1.Type.Size())
|
||||
}
|
||||
|
||||
func netIPTo6Safe(ip netip.Addr) netip.Addr {
|
||||
if ip.Is4() {
|
||||
return netip.AddrFrom16(ip.As16())
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
func netIPTo6SafeNocheck(ip netip.Addr) netip.Addr {
|
||||
return netip.AddrFrom16(ip.As16())
|
||||
}
|
||||
|
||||
func BenchmarkNetIPTo6(b *testing.B) {
|
||||
ipv4 := netip.MustParseAddr("192.168.1.1")
|
||||
ipv6 := netip.MustParseAddr("2a01:db8::1")
|
||||
for _, ip := range []netip.Addr{ipv4, ipv6} {
|
||||
version := "v4"
|
||||
if ip.Is6() {
|
||||
version = "v6"
|
||||
}
|
||||
b.Run(fmt.Sprintf("safe %s", version), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_ = netIPTo6Safe(ip)
|
||||
}
|
||||
})
|
||||
b.Run(fmt.Sprintf("safe nocheck %s", version), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_ = netIPTo6SafeNocheck(ip)
|
||||
}
|
||||
})
|
||||
b.Run(fmt.Sprintf("unsafe %s", version), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_ = helpers.NetIPTo6(ip)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ func PrefixTo16(prefix netip.Prefix) netip.Prefix {
|
||||
return prefix
|
||||
}
|
||||
// Convert IPv4 to IPv4-mapped IPv6
|
||||
return netip.PrefixFrom(netip.AddrFrom16(prefix.Addr().As16()), prefix.Bits()+96)
|
||||
return netip.PrefixFrom(NetIPTo6(prefix.Addr()), prefix.Bits()+96)
|
||||
}
|
||||
|
||||
// SubnetMapParseKey parses a prefix or an IP address into a netip.Prefix that
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestCore(t *testing.T) {
|
||||
msg := &schema.FlowMessage{
|
||||
TimeReceived: 200,
|
||||
SamplingRate: 1000,
|
||||
ExporterAddress: netip.AddrFrom16(netip.MustParseAddr(exporter).As16()),
|
||||
ExporterAddress: helpers.NetIPTo6(netip.MustParseAddr(exporter)),
|
||||
InIf: in,
|
||||
OutIf: out,
|
||||
SrcAddr: netip.MustParseAddr("::ffff:67.43.156.77"),
|
||||
|
||||
@@ -176,7 +176,7 @@ func ParseEthernet(sch *schema.Component, bf *schema.FlowMessage, data []byte) u
|
||||
// DecodeIP decodes an IP address
|
||||
func DecodeIP(b []byte) netip.Addr {
|
||||
if ip, ok := netip.AddrFromSlice(b); ok {
|
||||
return netip.AddrFrom16(ip.As16())
|
||||
return helpers.NetIPTo6(ip)
|
||||
}
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func setupTestCache(t *testing.T) (*reporter.Reporter, *metadataCache) {
|
||||
func expectCacheLookup(t *testing.T, sc *metadataCache, exporterIP string, ifIndex uint, expected provider.Answer) {
|
||||
t.Helper()
|
||||
ip := netip.MustParseAddr(exporterIP)
|
||||
ip = netip.AddrFrom16(ip.As16())
|
||||
ip = helpers.NetIPTo6(ip)
|
||||
got, ok := sc.Lookup(time.Time{}, provider.Query{
|
||||
ExporterIP: ip,
|
||||
IfIndex: ifIndex,
|
||||
|
||||
@@ -7,9 +7,9 @@ package snmp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
"akvorado/common/reporter"
|
||||
"akvorado/outlet/metadata/provider"
|
||||
)
|
||||
@@ -38,8 +38,8 @@ func (configuration Configuration) New(_ context.Context, r *reporter.Reporter)
|
||||
for exporterIP, agentIP := range configuration.Agents {
|
||||
if exporterIP.Is4() || agentIP.Is4() {
|
||||
delete(configuration.Agents, exporterIP)
|
||||
exporterIP = netip.AddrFrom16(exporterIP.As16())
|
||||
agentIP = netip.AddrFrom16(agentIP.As16())
|
||||
exporterIP = helpers.NetIPTo6(exporterIP)
|
||||
agentIP = helpers.NetIPTo6(agentIP)
|
||||
configuration.Agents[exporterIP] = agentIP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
func expectMockLookup(t *testing.T, c *Component, exporter string, ifIndex uint, expected provider.Answer) {
|
||||
t.Helper()
|
||||
ip := netip.AddrFrom16(netip.MustParseAddr(exporter).As16())
|
||||
ip := helpers.NetIPTo6(netip.MustParseAddr(exporter))
|
||||
got := c.Lookup(time.Now(), ip, ifIndex)
|
||||
if diff := helpers.Diff(got, expected); diff != "" {
|
||||
t.Fatalf("Lookup() (-got, +want):\n%s", diff)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"gopkg.in/tomb.v2"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
"akvorado/common/reporter"
|
||||
"akvorado/outlet/routing/provider"
|
||||
"akvorado/outlet/routing/provider/bmp"
|
||||
@@ -183,7 +184,7 @@ func (p *Provider) Refresh(ctx context.Context) {
|
||||
p.r.Err(err).Msgf("error while parsing router address %s", router.Address)
|
||||
continue
|
||||
}
|
||||
routerAddress = netip.AddrFrom16(routerAddress.As16())
|
||||
routerAddress = helpers.NetIPTo6(routerAddress)
|
||||
routers[routerAddress] = append(routers[routerAddress], p.instances[config.GRPCAddr])
|
||||
|
||||
p.metrics.knownRouters.WithLabelValues(config.GRPCAddr).Inc()
|
||||
@@ -318,7 +319,7 @@ func (p *Provider) lpmResponseToLookupResult(lpm *pb.LPMResponse) (bmp.LookupRes
|
||||
if !ok {
|
||||
return res, errInvalidNextHop
|
||||
}
|
||||
res.NextHop = nhAddr
|
||||
res.NextHop = helpers.NetIPTo6(nhAddr)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
||||
@@ -239,7 +239,7 @@ func BenchmarkRIBInsertion(b *testing.B) {
|
||||
if prng2[p].IntN(10) == 0 {
|
||||
continue
|
||||
}
|
||||
pfx := netip.PrefixFrom(netip.AddrFrom16(r.Prefix.Addr().As16()), r.Prefix.Bits()+96)
|
||||
pfx := netip.PrefixFrom(helpers.NetIPTo6(r.Prefix.Addr()), r.Prefix.Bits()+96)
|
||||
tentative++
|
||||
inserted += rib.AddPrefix(pfx, route{
|
||||
peer: uint32(p),
|
||||
@@ -295,7 +295,7 @@ func BenchmarkRIBLookup(b *testing.B) {
|
||||
if prng2[p].IntN(10) == 0 {
|
||||
continue
|
||||
}
|
||||
pfx := netip.PrefixFrom(netip.AddrFrom16(r.Prefix.Addr().As16()), r.Prefix.Bits()+96)
|
||||
pfx := netip.PrefixFrom(helpers.NetIPTo6(r.Prefix.Addr()), r.Prefix.Bits()+96)
|
||||
rib.AddPrefix(pfx, route{
|
||||
peer: uint32(p),
|
||||
nlri: rib.nlris.Put(nlri{family: bgp.RF_IPv4_UC}),
|
||||
@@ -344,7 +344,7 @@ func BenchmarkRIBFlush(b *testing.B) {
|
||||
if prng2[p].IntN(10) == 0 {
|
||||
continue
|
||||
}
|
||||
pfx := netip.PrefixFrom(netip.AddrFrom16(r.Prefix.Addr().As16()), r.Prefix.Bits()+96)
|
||||
pfx := netip.PrefixFrom(helpers.NetIPTo6(r.Prefix.Addr()), r.Prefix.Bits()+96)
|
||||
rib.AddPrefix(pfx, route{
|
||||
peer: uint32(p),
|
||||
nlri: rib.nlris.Put(nlri{family: bgp.RF_IPv4_UC}),
|
||||
|
||||
Reference in New Issue
Block a user