From d0c0ff454b701c5a911094e29ee5bd9ad39a19d0 Mon Sep 17 00:00:00 2001 From: sidneychang <2190206983@qq.com> Date: Fri, 23 Jan 2026 21:43:21 -0500 Subject: [PATCH] fix: add container veth discovery to replace hardcoded eth0 - Add `discoverContainerIface` in `pkg/network/network.go` to discover the container-side veth endpoint by scanning links in the current netns. - Replace places that relied on the hardcoded `DefaultInterface` with the discovered link. This implements interface discovery instead of assuming `eth0`, improving compatibility across different runtimes/CNIs. Fixes #14 Signed-off-by: sidneychang <2190206983@qq.com> --- .github/contributors.yaml | 3 ++ pkg/network/network.go | 94 +++++++++++++++++++++++----------- pkg/network/network_dynamic.go | 13 ++--- pkg/network/network_static.go | 9 ++-- 4 files changed, 75 insertions(+), 44 deletions(-) diff --git a/.github/contributors.yaml b/.github/contributors.yaml index 71c7cbc7..11c9f5f1 100644 --- a/.github/contributors.yaml +++ b/.github/contributors.yaml @@ -44,3 +44,6 @@ users: IrvingMg: name: Irving Mondragón email: mirvingr@gmail.com + sidneychang: + name: Xuedong Zhang + email: 2190206983@qq.com diff --git a/pkg/network/network.go b/pkg/network/network.go index d2d8ecac..187b122c 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -27,8 +27,7 @@ import ( ) const ( - DefaultInterface = "eth0" // FIXME: Discover the veth endpoint name instead of using default "eth0". See: https://github.com/urunc-dev/urunc/issues/14 - DefaultTap = "tapX_urunc" + DefaultTap = "tapX_urunc" ) var netlog = logrus.WithField("subsystem", "network") @@ -120,21 +119,6 @@ func createTapDevice(name string, mtu int, ownerUID, ownerGID uint32) (netlink.L return tapLink, nil } -// ensureEth0Exists checks all network interfaces in current netns and returns -// nil if eth0 is present or ErrEth0NotFound if not -func ensureEth0Exists() error { - ifaces, err := net.Interfaces() - if err != nil { - return err - } - for _, iface := range ifaces { - if iface.Name == DefaultInterface { - return nil - } - } - return errors.New("eth0 device not found") -} - func getInterfaceInfo(iface string) (Interface, error) { ief, err := net.InterfaceByName(iface) if err != nil { @@ -163,7 +147,7 @@ func getInterfaceInfo(iface string) (Interface, error) { } } if mask == "" { - return Interface{}, fmt.Errorf("failed to find mask for %q", DefaultInterface) + return Interface{}, fmt.Errorf("failed to find mask for %q", iface) } // convert to decimal notation decimalParts := make([]string, len(netMask)) @@ -172,7 +156,7 @@ func getInterfaceInfo(iface string) (Interface, error) { } mask = strings.Join(decimalParts, ".") if ipAddress == "" { - return Interface{}, fmt.Errorf("failed to find IPv4 address for %q", DefaultInterface) + return Interface{}, fmt.Errorf("failed to find IPv4 address for %q", iface) } gateway, err := gateway.DiscoverGateway() if err != nil { @@ -182,7 +166,7 @@ func getInterfaceInfo(iface string) (Interface, error) { IP: ipAddress, DefaultGateway: gateway.String(), Mask: mask, - Interface: DefaultInterface, + Interface: iface, MAC: IfMAC, }, nil } @@ -219,14 +203,6 @@ func addRedirectFilter(source netlink.Link, target netlink.Link) error { func networkSetup(tapName string, ipAddress string, redirectLink netlink.Link, addTCRules bool, uid uint32, gid uint32) (netlink.Link, error) { netlog.Debugf("starting for tapName=%s ipAddress=%s redirectLink=%s addTCRules=%v", tapName, ipAddress, redirectLink.Attrs().Name, addTCRules) - - // Sanity check: ensure eth0 exists in this namespace - err := ensureEth0Exists() - if err != nil { - netlog.Warnf("eth0 interface not found in namespace (unikernel may have been spawned using ctr): %v", err) - return nil, nil - } - // Create TAP netlog.Debugf("creating tap device %s (mtu=%d)", tapName, redirectLink.Attrs().MTU) newTapDevice, err := createTapDevice(tapName, redirectLink.Attrs().MTU, uid, gid) @@ -333,12 +309,69 @@ func deleteIngressQdisc(link netlink.Link) error { return nil } +// discoverContainerIface discovers the container's network interface by +// scanning all links in the current network namespace and returning the first +// non-loopback link that has an IP address and a default route. +func discoverContainerIface() (netlink.Link, error) { + handle, err := netlink.NewHandle() + if err != nil { + return nil, err + } + defer handle.Close() + links, err := handle.LinkList() + if err != nil { + return nil, err + } + for _, link := range links { + attrs := link.Attrs() + if attrs == nil { + netlog.Debug("skipping link with nil attributes") + continue + } + // skip loopback + if (attrs.Flags & net.FlagLoopback) != 0 { + netlog.Debugf("skipping loopback interface %s", attrs.Name) + continue + } + // must have addresses configured + addrs, err := handle.AddrList(link, netlink.FAMILY_ALL) + if err != nil { + netlog.Debugf("skipping interface %s: failed to list addresses: %v", attrs.Name, err) + continue + } + if len(addrs) == 0 { + netlog.Debugf("skipping interface %s: no addresses configured", attrs.Name) + continue + } + // look for a default route on this interface + routes, err := handle.RouteList(link, netlink.FAMILY_ALL) + if err != nil { + netlog.Debugf("skipping interface %s: failed to list routes: %v", attrs.Name, err) + continue + } + for _, r := range routes { + // default route: Dst == nil OR represented as 0.0.0.0/0 or ::/0 + if r.Dst == nil { + return link, nil + } + if r.Dst != nil { + dstStr := r.Dst.String() + if dstStr == "0.0.0.0/0" || dstStr == "::/0" { + return link, nil + } + } + } + netlog.Debugf("skipping interface %s: no default route found", attrs.Name) + } + return nil, errors.New("no suitable network interface found in namespace") +} + func deleteAllQDiscs(device netlink.Link) error { err := deleteIngressQdisc(device) if err != nil { return err } - device, err = netlink.LinkByName(DefaultInterface) + device, err = discoverContainerIface() if err != nil { return err } @@ -357,8 +390,7 @@ func deleteAllTCFilters(device netlink.Link) error { return nil } allFilters = append(allFilters, tapFilters...) - - device, err = netlink.LinkByName(DefaultInterface) + device, err = discoverContainerIface() if err != nil { return err } diff --git a/pkg/network/network_dynamic.go b/pkg/network/network_dynamic.go index 87b0be21..1d6a2237 100644 --- a/pkg/network/network_dynamic.go +++ b/pkg/network/network_dynamic.go @@ -18,8 +18,6 @@ import ( "fmt" "strconv" "strings" - - "github.com/vishvananda/netlink" ) type DynamicNetwork struct { @@ -43,10 +41,9 @@ func (n DynamicNetwork) NetworkSetup(uid uint32, gid uint32) (*UnikernelNetworkI return nil, fmt.Errorf("unsupported operation: can't spawn multiple unikernels in the same network namespace") } - netlog.Debugf("trying LinkByName(%s)", DefaultInterface) - redirectLink, err := netlink.LinkByName(DefaultInterface) + redirectLink, err := discoverContainerIface() if err != nil { - return nil, fmt.Errorf("failed to find interface %s: %w", DefaultInterface, err) + return nil, fmt.Errorf("failed to find container interface, (unikernel may have been spawned using ctr): %w", err) } netlog.Debugf("found interface %s (index=%d)", redirectLink.Attrs().Name, redirectLink.Attrs().Index) @@ -59,10 +56,10 @@ func (n DynamicNetwork) NetworkSetup(uid uint32, gid uint32) (*UnikernelNetworkI } netlog.Debugf("tap device created: %s", newTapDevice.Attrs().Name) - netlog.Debugf("fetching info for %s", DefaultInterface) - ifInfo, err := getInterfaceInfo(DefaultInterface) + netlog.Debugf("fetching info for %s", redirectLink.Attrs().Name) + ifInfo, err := getInterfaceInfo(redirectLink.Attrs().Name) if err != nil { - return nil, fmt.Errorf("getInterfaceInfo(%s) failed: %w", DefaultInterface, err) + return nil, fmt.Errorf("getInterfaceInfo(%s) failed: %w", redirectLink.Attrs().Name, err) } return &UnikernelNetworkInfo{ diff --git a/pkg/network/network_static.go b/pkg/network/network_static.go index 7197170e..fa46608c 100644 --- a/pkg/network/network_static.go +++ b/pkg/network/network_static.go @@ -22,7 +22,6 @@ import ( "strings" "github.com/urunc-dev/urunc/internal/constants" - "github.com/vishvananda/netlink" ) var StaticIPAddr = fmt.Sprintf("%s/24", constants.StaticNetworkTapIP) @@ -92,16 +91,16 @@ func setNATRule(iface string, sourceIP string) error { func (n StaticNetwork) NetworkSetup(uid uint32, gid uint32) (*UnikernelNetworkInfo, error) { newTapName := strings.ReplaceAll(DefaultTap, "X", "0") addTCRules := false - redirectLink, err := netlink.LinkByName(DefaultInterface) + redirectLink, err := discoverContainerIface() if err != nil { - netlog.Errorf("failed to find %s interface", DefaultInterface) + netlog.Errorf("failed to find container interface, (unikernel may have been spawned using ctr): %v", err) return nil, err } newTapDevice, err := networkSetup(newTapName, StaticIPAddr, redirectLink, addTCRules, uid, gid) if err != nil { return nil, err } - err = setNATRule(DefaultInterface, StaticIPAddr) + err = setNATRule(redirectLink.Attrs().Name, StaticIPAddr) if err != nil { return nil, err } @@ -111,7 +110,7 @@ func (n StaticNetwork) NetworkSetup(uid uint32, gid uint32) (*UnikernelNetworkIn IP: constants.StaticNetworkUnikernelIP, DefaultGateway: constants.StaticNetworkTapIP, Mask: "255.255.255.0", - Interface: DefaultInterface, // or tap0_urunc? + Interface: redirectLink.Attrs().Name, // or tap0_urunc? MAC: redirectLink.Attrs().HardwareAddr.String(), }, }, nil