Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/core/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ func VMConfigFromFlags(cmd *cobra.Command, image string) (*types.VMConfig, error
noDirectIO, _ := cmd.Flags().GetBool("no-direct-io")
windows, _ := cmd.Flags().GetBool("windows")
sharedMemory, _ := cmd.Flags().GetBool("shared-memory")
isolated, _ := cmd.Flags().GetBool("isolated")
dataDiskRaw, _ := cmd.Flags().GetStringArray("data-disk")

if vmName == "" {
Expand Down Expand Up @@ -325,6 +326,7 @@ func VMConfigFromFlags(cmd *cobra.Command, image string) (*types.VMConfig, error
NoDirectIO: noDirectIO,
Windows: windows,
SharedMemory: sharedMemory,
Isolated: isolated,
},
User: user,
Password: password,
Expand All @@ -350,6 +352,7 @@ func CloneVMConfigFromFlags(cmd *cobra.Command, snapCfg types.SnapshotConfig) (*
}

onDemand, _ := cmd.Flags().GetBool("on-demand")
isolated, _ := cmd.Flags().GetBool("isolated")

return &types.VMConfig{
Name: vmName,
Expand All @@ -366,6 +369,7 @@ func CloneVMConfigFromFlags(cmd *cobra.Command, snapCfg types.SnapshotConfig) (*
NoDirectIO: noDirectIO,
Windows: snapCfg.Windows,
SharedMemory: snapCfg.SharedMemory,
Isolated: isolated,
},
OnDemand: onDemand,
}, nil
Expand Down
2 changes: 2 additions & 0 deletions cmd/vm/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ func addVMFlags(cmd *cobra.Command) {
cmd.Flags().Bool("no-direct-io", false, "disable O_DIRECT on writable disks (use page cache instead; CH only)")
cmd.Flags().Bool("windows", false, "Windows guest (UEFI boot, kvm_hyperv=on, no cidata)")
cmd.Flags().Bool("shared-memory", false, "enable CH memory shared=on; required to attach vhost-user-fs later (CH only, fixed for VM lifetime)")
cmd.Flags().Bool("isolated", false, "isolate this VM's host bridge port so same-node VMs cannot reach each other (egress/gateway unaffected)")
cmd.Flags().StringArray("data-disk", nil, "extra data disk: size=20G[,name=...][,fstype=ext4|none][,mount=/mnt/x][,directio=on|off|auto]; repeatable")
}

Expand All @@ -299,6 +300,7 @@ func addCloneFlags(cmd *cobra.Command) {
cmd.Flags().String("bridge", "", "use TAP-on-bridge instead of CNI (value is bridge device, e.g. cni0)")
cmd.Flags().Bool("no-direct-io", false, "disable O_DIRECT on writable disks (inherit from snapshot if not set)")
cmd.Flags().Bool("on-demand", false, "use UFFD on-demand memory loading for faster clone (CH only; snapshot file must remain on disk)")
cmd.Flags().Bool("isolated", false, "isolate this VM's host bridge port so same-node VMs cannot reach each other (egress/gateway unaffected)")
cmd.Flags().Bool("pull", false, "auto-pull base image if not found locally (for cross-node clone)")
cmd.Flags().String("from-dir", "", "clone from a snapshot directory (must contain snapshot.json) instead of the local snapshot DB; mutually exclusive with positional SNAPSHOT")
}
6 changes: 6 additions & 0 deletions network/bridge/bridge_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ func (b *Bridge) Add(ctx context.Context, vmID string, vmCfg *types.VMConfig, sp
return nil, fmt.Errorf("add %s to %s: %w", name, b.bridgeDev, mErr)
}

if vmCfg.Isolated {
if iErr := netlink.LinkSetIsolated(tap, true); iErr != nil {
return nil, fmt.Errorf("isolate %s: %w", name, iErr)
}
}

_ = netlink.LinkSetLearning(tap, false)
if mtu := br.Attrs().MTU; mtu > 0 {
_ = netlink.LinkSetMTU(tap, mtu)
Expand Down
27 changes: 27 additions & 0 deletions network/cni/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/fs"
"os"
"strings"

"github.com/containernetworking/cni/libcni"
cnitypes "github.com/containernetworking/cni/pkg/types"
Expand Down Expand Up @@ -106,6 +107,16 @@ func (c *CNI) Add(ctx context.Context, vmID string, vmCfg *types.VMConfig, specs
return nil, fmt.Errorf("parse CNI result: %w", parseErr)
}

if vmCfg.Isolated {
hostVeth, vErr := hostVethFromResult(cniResult)
if vErr != nil {
return nil, fmt.Errorf("isolate %s: %w", vmID, vErr)
}
if iErr := setPortIsolated(hostVeth); iErr != nil {
return nil, fmt.Errorf("isolate %s port %s: %w", vmID, hostVeth, iErr)
}
}

var overrideMAC string
if spec.Existing != nil {
overrideMAC = spec.Existing.MAC
Expand Down Expand Up @@ -211,6 +222,22 @@ func ensureNetns(name, nsPath string) (bool, error) {
return true, nil
}

// hostVethFromResult returns the host-side veth name the bridge CNI plugin
// created. The bridge plugin reports it with an empty Sandbox and a "veth"
// name prefix (the bridge device itself, e.g. cni0, also has an empty Sandbox).
func hostVethFromResult(result cnitypes.Result) (string, error) {
r, err := current.NewResultFromResult(result)
if err != nil {
return "", fmt.Errorf("convert CNI result: %w", err)
}
for _, iface := range r.Interfaces {
if iface.Sandbox == "" && strings.HasPrefix(iface.Name, "veth") {
return iface.Name, nil
}
}
return "", errors.New("no host veth in CNI result")
}

// extractNetworkInfo converts a CNI ADD result into types.Network.
func extractNetworkInfo(result cnitypes.Result) (*types.Network, error) {
newResult, err := current.NewResultFromResult(result)
Expand Down
4 changes: 4 additions & 0 deletions network/cni/lifecycle_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ func setupTCRedirect(_, _, _ string, _ int, _ string) (string, error) {
func deleteTAPInNetns(_, _ string) error {
return errNotSupported
}

func setPortIsolated(_ string) error {
return errNotSupported
}
10 changes: 10 additions & 0 deletions network/cni/lifecycle_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ func deleteNetns(ctx context.Context, name string) error {
})
}

// setPortIsolated sets the bridge "isolated" flag on a host port so it cannot
// forward frames to other isolated ports on the same bridge.
func setPortIsolated(name string) error {
link, err := netlink.LinkByName(name)
if err != nil {
return fmt.Errorf("find %s: %w", name, err)
}
return netlink.LinkSetIsolated(link, true)
}

// deleteTAPInNetns deletes a named TAP device inside target netns.
func deleteTAPInNetns(nsPath, tapName string) error {
return cns.WithNetNSPath(nsPath, func(_ cns.NetNS) error {
Expand Down
4 changes: 4 additions & 0 deletions types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ type Config struct {
Windows bool `json:"windows,omitempty"` // Windows guest: UEFI boot, kvm_hyperv=on, no cidata
// SharedMemory toggles CH memory shared=on (vhost-user-fs prerequisite); fixed at create, persists through clone/restore.
SharedMemory bool `json:"shared_memory,omitempty"`
// Isolated sets the bridge "isolated" flag on the VM's host port so VMs on
// the same node/bridge cannot reach each other (they can still reach the
// gateway and route out). Re-applied on NIC recover.
Isolated bool `json:"isolated,omitempty"`
}
Loading