From 62163034755f51c1d62b20ff3bb18b44689886f4 Mon Sep 17 00:00:00 2001 From: Rucha0901 Date: Wed, 5 Aug 2026 12:56:02 +0530 Subject: [PATCH 1/2] feat(hypervisors): complete Hedge VMM backend integration Fixes: #904 Signed-off-by: Rucha0901 --- pkg/unikontainers/hypervisors/hedge.go | 107 ++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/pkg/unikontainers/hypervisors/hedge.go b/pkg/unikontainers/hypervisors/hedge.go index 7051ba7f0..ddc67f178 100644 --- a/pkg/unikontainers/hypervisors/hedge.go +++ b/pkg/unikontainers/hypervisors/hedge.go @@ -28,18 +28,24 @@ const ( ConsoleEndpoint = "/proc/vmcons" ) -type Hedge struct{} +type Hedge struct { + binary string + binaryPath string +} func (h *Hedge) Ok() error { - return fmt.Errorf("hedge not implemented yet") + if err := hedge.Status(); err != nil { + return ErrVMMNotInstalled + } + return nil } -func (h *Hedge) Signal(_ int, _ unix.Signal) error { - return fmt.Errorf("hedge not implemented yet") +func (h *Hedge) Signal(pid int, signal unix.Signal) error { + return unix.Kill(pid, signal) } -func (h *Hedge) Stop(_ int) error { - return fmt.Errorf("hedge not implemented yet") +func (h *Hedge) Stop(pid int) error { + return killProcess(pid) } func (h *Hedge) UsesKVM() bool { @@ -52,16 +58,93 @@ func (h *Hedge) SupportsSharedfs(_ string) bool { } func (h *Hedge) Path() string { - return "" + if h.binaryPath != "" { + return h.binaryPath + } + return hedge.MONITOR_ENDPOINT } -func (h *Hedge) BuildExecCmd(_ types.ExecArgs, _ types.Unikernel) ([]string, error) { - return nil, fmt.Errorf("hedge not implemented yet") +func (h *Hedge) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) { + memMB := int(bytesToMB(args.MemSizeB)) + if memMB <= 0 { + memMB = int(DefaultMemory) + } + + netDev := "" + if args.Net.TapDev != "" { + netDev = ukernel.MonitorNetCli(args.Net.TapDev, args.Net.MAC) + } + + blkDev := "" + bArgs := ukernel.MonitorBlockCli() + for _, blockArg := range bArgs { + if blkDev != "" { + blkDev += " " + } + blkDev += "--block:" + blockArg.ID + "=" + blockArg.Path + } + + conf := hedge.VMConfig{ + Name: args.ContainerID, + Binary: args.UnikernelPath, + CPU: int(args.VCPUs), + Mem: memMB, + Blk: blkDev, + Net: netDev, + CmdLine: args.Command, + } + + if err := conf.Validate(); err != nil { + return nil, fmt.Errorf("hedge vm config validation failed: %w", err) + } + + execCmd := []string{ + "hedge", + "--name", conf.Name, + "--binary", conf.Binary, + "--cpu", fmt.Sprintf("%d", conf.CPU), + "--mem", fmt.Sprintf("%d", conf.Mem), + "--cmdline", conf.CmdLine, + } + if conf.Net != "" { + execCmd = append(execCmd, "--net", conf.Net) + } + if conf.Blk != "" { + execCmd = append(execCmd, "--blk", conf.Blk) + } + + return execCmd, nil } -// PreExec performs pre-execution setup. Hedge is not fully implemented. -func (h *Hedge) PreExec(_ types.ExecArgs) error { - return fmt.Errorf("hedge not implemented yet") +// PreExec performs pre-execution setup for Hedge using hedge_api. +func (h *Hedge) PreExec(args types.ExecArgs) error { + memMB := int(bytesToMB(args.MemSizeB)) + if memMB <= 0 { + memMB = int(DefaultMemory) + } + + netDev := "" + if args.Net.TapDev != "" { + netDev = args.Net.TapDev + } + + blkDev := "" + + conf := hedge.VMConfig{ + Name: args.ContainerID, + Binary: args.UnikernelPath, + CPU: int(args.VCPUs), + Mem: memMB, + Blk: blkDev, + Net: netDev, + CmdLine: args.Command, + } + + if err := conf.Validate(); err != nil { + return fmt.Errorf("hedge vm config validation failed: %w", err) + } + + return hedge.StartVM(conf) } func (h *Hedge) VMState(name string) string { From 7e08b3f8798bdc5c1a10eedc7c239a1c7e2c576d Mon Sep 17 00:00:00 2001 From: Rucha0901 Date: Wed, 5 Aug 2026 17:18:50 +0530 Subject: [PATCH 2/2] feat: enable multi-unikernel network namespace coexistence Fixes #906 --- pkg/network/network.go | 73 ++++++++++++++++++++++++++++------ pkg/network/network_dynamic.go | 7 +--- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/pkg/network/network.go b/pkg/network/network.go index cbc73c707..0c1cd548f 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -225,6 +225,16 @@ 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) + + bridgeName := "br_urunc" + br, created, err := createBridge(bridgeName) + if err != nil { + return nil, fmt.Errorf("createBridge(%s) failed: %w", bridgeName, err) + } + if err = netlink.LinkSetUp(br); err != nil { + return nil, fmt.Errorf("LinkSetUp(%s) failed: %w", bridgeName, err) + } + // Create TAP netlog.Debugf("creating tap device %s (mtu=%d)", tapName, redirectLink.Attrs().MTU) newTapDevice, err := createTapDevice(tapName, redirectLink.Attrs().MTU, uid, gid) @@ -233,6 +243,10 @@ func networkSetup(tapName string, ipAddress string, redirectLink netlink.Link, a } netlog.Debugf("created tap device %s (index=%d)", newTapDevice.Attrs().Name, newTapDevice.Attrs().Index) + if err = netlink.LinkSetMaster(newTapDevice, br); err != nil { + return nil, fmt.Errorf("LinkSetMaster(%s, %s) failed: %w", tapName, bridgeName, err) + } + // Bring TAP up before qdisc if err = netlink.LinkSetUp(newTapDevice); err != nil { return nil, fmt.Errorf("LinkSetUp(%s) failed: %w", newTapDevice.Attrs().Name, err) @@ -246,24 +260,20 @@ func networkSetup(tapName string, ipAddress string, redirectLink netlink.Link, a netlog.Debugf("redirectLink %s is UP", redirectLink.Attrs().Name) // Add qdisc + redirect filters - if addTCRules { - netlog.Debug("adding tc ingress qdisc + redirect filters") + if addTCRules && created { + netlog.Debug("adding tc ingress qdisc + redirect filters between redirectLink and bridge") - if err = addIngressQdisc(newTapDevice); err != nil { - return nil, fmt.Errorf("addIngressQdisc(tap=%s) failed: %w", - newTapDevice.Attrs().Name, err) + if err = addIngressQdisc(br); err != nil { + return nil, fmt.Errorf("addIngressQdisc(br=%s) failed: %w", br.Attrs().Name, err) } if err = addIngressQdisc(redirectLink); err != nil { - return nil, fmt.Errorf("addIngressQdisc(redirect=%s) failed: %w", - redirectLink.Attrs().Name, err) + return nil, fmt.Errorf("addIngressQdisc(redirect=%s) failed: %w", redirectLink.Attrs().Name, err) } - if err = addRedirectFilter(newTapDevice, redirectLink); err != nil { - return nil, fmt.Errorf("addRedirectFilter(%s->%s) failed: %w", - newTapDevice.Attrs().Name, redirectLink.Attrs().Name, err) + if err = addRedirectFilter(br, redirectLink); err != nil { + return nil, fmt.Errorf("addRedirectFilter(%s->%s) failed: %w", br.Attrs().Name, redirectLink.Attrs().Name, err) } - if err = addRedirectFilter(redirectLink, newTapDevice); err != nil { - return nil, fmt.Errorf("addRedirectFilter(%s->%s) failed: %w", - redirectLink.Attrs().Name, newTapDevice.Attrs().Name, err) + if err = addRedirectFilter(redirectLink, br); err != nil { + return nil, fmt.Errorf("addRedirectFilter(%s->%s) failed: %w", redirectLink.Attrs().Name, br.Attrs().Name, err) } } @@ -300,12 +310,17 @@ func CleanupAllUruncTaps() error { var retErr error tapRe := regexp.MustCompile(`^tap\d+_urunc$`) + var brLink netlink.Link for _, link := range links { attrs := link.Attrs() if attrs == nil { continue } name := attrs.Name + if name == "br_urunc" { + brLink = link + continue + } if !tapRe.MatchString(name) { continue } @@ -330,6 +345,22 @@ func CleanupAllUruncTaps() error { retErr = errors.Join(retErr, devErr) } + if brLink != nil { + netlog.Debugf("cleaning up bridge br_urunc") + if err := deleteAllTCFilters(brLink); err != nil { + netlog.Errorf("failed to delete TC filters for br_urunc: %v", err) + retErr = errors.Join(retErr, err) + } + if err := deleteAllQDiscs(brLink); err != nil { + netlog.Errorf("failed to delete qdiscs for br_urunc: %v", err) + retErr = errors.Join(retErr, err) + } + if err := netlink.LinkDel(brLink); err != nil { + netlog.Errorf("failed to delete bridge br_urunc: %v", err) + retErr = errors.Join(retErr, err) + } + } + return retErr } @@ -461,3 +492,19 @@ func deleteTapDevice(device netlink.Link) error { } return nil } + +func createBridge(bridgeName string) (netlink.Link, bool, error) { + link, err := netlink.LinkByName(bridgeName) + if err == nil { + return link, false, nil + } + br := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridgeName}} + if err := netlink.LinkAdd(br); err != nil { + return nil, false, fmt.Errorf("could not add bridge %s: %w", bridgeName, err) + } + link, err = netlink.LinkByName(bridgeName) + if err != nil { + return nil, false, fmt.Errorf("could not get bridge %s: %w", bridgeName, err) + } + return link, true, nil +} diff --git a/pkg/network/network_dynamic.go b/pkg/network/network_dynamic.go index 1d6a2237b..eadd7fd5d 100644 --- a/pkg/network/network_dynamic.go +++ b/pkg/network/network_dynamic.go @@ -29,17 +29,12 @@ type DynamicNetwork struct { // If no TAP devices are available in the current netns, it creates a new tap device and // sets TC rules between the veth interface and the tap device inside the namespace. // -// FIXME: CUrrently only one tap device per netns can provide functional networking. We need to find a proper way to handle networking -// for multiple unikernels in the same pod/network namespace. -// See: https://github.com/urunc-dev/urunc/issues/13 +// sets TC rules between the veth interface and the tap device inside the namespace. func (n DynamicNetwork) NetworkSetup(uid uint32, gid uint32) (*UnikernelNetworkInfo, error) { tapIndex, err := getTapIndex() if err != nil { return nil, fmt.Errorf("getTapIndex failed: %w", err) } - if tapIndex > 0 { - return nil, fmt.Errorf("unsupported operation: can't spawn multiple unikernels in the same network namespace") - } redirectLink, err := discoverContainerIface() if err != nil {