From 198f72814c8f02afd64889ae83ebc577e484e8a4 Mon Sep 17 00:00:00 2001 From: hayzam Date: Wed, 11 Feb 2026 13:18:34 +0530 Subject: [PATCH] engine: mac: more ipv6 gm --- engine/mac_resolver.go | 110 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/engine/mac_resolver.go b/engine/mac_resolver.go index 1c8d985..a1eecd8 100644 --- a/engine/mac_resolver.go +++ b/engine/mac_resolver.go @@ -4,6 +4,7 @@ import ( "bufio" "net" "os" + "os/exec" "strings" "sync" "time" @@ -91,6 +92,17 @@ func (r *sourceMACResolver) Resolve(ip net.IP) net.HardwareAddr { if mac := r.ndpByIP[ipKey]; len(mac) != 0 { return append(net.HardwareAddr(nil), mac...) } + + // On-demand IPv6 neighbor lookup via route-netlink as a last fast path. + if ip.To4() == nil { + if mac, ok := lookupNeighborMACNetlink(ip); ok { + out := append(net.HardwareAddr(nil), mac...) + r.mu.Lock() + r.ndpByIP[ipKey] = append(net.HardwareAddr(nil), mac...) + r.mu.Unlock() + return out + } + } return nil } @@ -170,6 +182,10 @@ func (r *sourceMACResolver) refreshNDPCache(now time.Time) { // Fallback for environments without /proc/net/ndisc_cache. m = readIPv6NeighNetlink() } + if len(m) == 0 { + // Last-resort fallback for environments where route-netlink dumps are restricted. + m = readIPv6NeighCommand() + } r.mu.Lock() r.ndpByIP = m r.lastNDPRefresh = now @@ -258,6 +274,100 @@ func readIPv6NeighNetlink() map[string]net.HardwareAddr { return m } +func lookupNeighborMACNetlink(target net.IP) (net.HardwareAddr, bool) { + const ( + ndMsgLen = 12 + ndaDst = 1 + ndaLLAddr = 2 + ) + if target == nil || target.To4() != nil { + return nil, false + } + target16 := target.To16() + if target16 == nil { + return nil, false + } + + conn, err := netlink.Dial(unix.NETLINK_ROUTE, nil) + if err != nil { + return nil, false + } + defer conn.Close() + + req := make([]byte, ndMsgLen) + req[0] = unix.AF_INET6 + msgs, err := conn.Execute(netlink.Message{ + Header: netlink.Header{ + Type: unix.RTM_GETNEIGH, + Flags: netlink.Request | netlink.Dump, + }, + Data: req, + }) + if err != nil { + return nil, false + } + + for _, msg := range msgs { + if msg.Header.Type != unix.RTM_NEWNEIGH || len(msg.Data) < ndMsgLen || msg.Data[0] != unix.AF_INET6 { + continue + } + attrs, err := netlink.UnmarshalAttributes(msg.Data[ndMsgLen:]) + if err != nil { + continue + } + var dstIP net.IP + var mac net.HardwareAddr + for _, a := range attrs { + switch a.Type { + case ndaDst: + if len(a.Data) == net.IPv6len { + dstIP = net.IP(append([]byte(nil), a.Data...)) + } + case ndaLLAddr: + if len(a.Data) >= 6 { + candidate := append(net.HardwareAddr(nil), a.Data...) + if candidate.String() != "00:00:00:00:00:00" { + mac = candidate + } + } + } + } + if dstIP != nil && mac != nil && dstIP.Equal(target16) { + return mac, true + } + } + + return nil, false +} + +func readIPv6NeighCommand() map[string]net.HardwareAddr { + commands := [][]string{ + {"ip", "-6", "neigh", "show"}, + {"/sbin/ip", "-6", "neigh", "show"}, + {"/usr/sbin/ip", "-6", "neigh", "show"}, + {"busybox", "ip", "-6", "neigh", "show"}, + {"/bin/busybox", "ip", "-6", "neigh", "show"}, + } + for _, cmd := range commands { + out, err := exec.Command(cmd[0], cmd[1:]...).Output() + if err != nil || len(out) == 0 { + continue + } + m := make(map[string]net.HardwareAddr) + for _, line := range strings.Split(string(out), "\n") { + ip, mac, ok := parseNeighborLine(line) + if !ok { + continue + } + m[ip] = mac + } + if len(m) != 0 { + return m + } + } + return map[string]net.HardwareAddr{} +} + func parseNeighborLine(line string) (string, net.HardwareAddr, bool) { fields := strings.Fields(line) if len(fields) < 2 {