From a73bf976975bba11b0456d32b565fefcceff8906 Mon Sep 17 00:00:00 2001 From: GnomeZworc Date: Sun, 4 Oct 2026 19:56:42 +0200 Subject: [PATCH] f-51: netif: poser l'adresse VTEP locale des VXLAN #51 Signed-off-by: GnomeZworc --- internal/netif/vxlan.go | 28 ++++++++++++++++ internal/netif/vxlan_test.go | 64 ++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 internal/netif/vxlan_test.go diff --git a/internal/netif/vxlan.go b/internal/netif/vxlan.go index 70740bc..0930cb3 100644 --- a/internal/netif/vxlan.go +++ b/internal/netif/vxlan.go @@ -1,14 +1,31 @@ package netif import ( + "fmt" + "net" + "syscall" + "github.com/vishvananda/netlink" ) +const ( + scopeUniverse = 0 + addressSecondary = 0x01 +) + func CreateVxlan(name string, vxlanID int, localIface string, mtu int) error { link, err := netlink.LinkByName(localIface) if err != nil { return err } + addrs, err := netlink.AddrList(link, syscall.AF_INET) + if err != nil { + return err + } + local, err := vtepAddress(localIface, addrs) + if err != nil { + return err + } vxlan := &netlink.Vxlan{ LinkAttrs: netlink.LinkAttrs{ Name: name, @@ -17,7 +34,18 @@ func CreateVxlan(name string, vxlanID int, localIface string, mtu int) error { VxlanId: vxlanID, Port: 4789, VtepDevIndex: link.Attrs().Index, + SrcAddr: local, Learning: false, } return netlink.LinkAdd(vxlan) } + +func vtepAddress(iface string, addrs []netlink.Addr) (net.IP, error) { + for _, a := range addrs { + if a.IPNet == nil || a.IP.To4() == nil || a.Scope != scopeUniverse || a.Flags&addressSecondary != 0 { + continue + } + return a.IP.To4(), nil + } + return nil, fmt.Errorf("no primary global IPv4 address on %s for the VXLAN local endpoint", iface) +} diff --git a/internal/netif/vxlan_test.go b/internal/netif/vxlan_test.go new file mode 100644 index 0000000..6ea7ca4 --- /dev/null +++ b/internal/netif/vxlan_test.go @@ -0,0 +1,64 @@ +package netif + +import ( + "net" + "strings" + "testing" + + "github.com/vishvananda/netlink" +) + +func addr(cidr string, scope, flags int) netlink.Addr { + ip, network, err := net.ParseCIDR(cidr) + if err != nil { + panic(err) + } + network.IP = ip + return netlink.Addr{IPNet: network, Scope: scope, Flags: flags} +} + +func TestVtepAddress_PicksThePrimaryGlobalIPv4(t *testing.T) { + addrs := []netlink.Addr{ + addr("127.0.0.1/8", 254, 0), + addr("169.254.0.3/28", 0, 0x01), + addr("192.168.14.11/24", 0, 0x80), + addr("192.168.14.99/24", 0, 0x01), + } + got, err := vtepAddress("br-000000", addrs) + if err != nil || got.String() != "192.168.14.11" { + t.Errorf("vtepAddress = %v, %v, want 192.168.14.11", got, err) + } +} + +func TestVtepAddress_SkipsLinkScopeAndSecondaryAddresses(t *testing.T) { + addrs := []netlink.Addr{ + addr("169.254.0.3/28", 253, 0), + addr("192.168.14.99/24", 0, 0x01), + addr("192.168.14.12/24", 0, 0), + } + got, err := vtepAddress("br-000000", addrs) + if err != nil || got.String() != "192.168.14.12" { + t.Errorf("vtepAddress = %v, %v, want 192.168.14.12", got, err) + } +} + +func TestVtepAddress_RefusesAnInterfaceWithoutUsableAddress(t *testing.T) { + for name, addrs := range map[string][]netlink.Addr{ + "no address": nil, + "only secondary": {addr("192.168.14.99/24", 0, 0x01)}, + "only link": {addr("169.254.0.3/28", 253, 0)}, + "only IPv6": {addr("fd00::11/64", 0, 0)}, + } { + _, err := vtepAddress("br-000000", addrs) + if err == nil || !strings.Contains(err.Error(), "no primary global IPv4 address on br-000000") { + t.Errorf("%s: error = %v", name, err) + } + } +} + +func TestVtepAddress_ReturnsAFourByteAddress(t *testing.T) { + got, err := vtepAddress("br-000000", []netlink.Addr{addr("192.168.14.11/24", 0, 0)}) + if err != nil || len(got) != 4 { + t.Errorf("vtepAddress = %v (%d bytes), %v", got, len(got), err) + } +}