net: add support for bridge mode

Signed-off-by: Abiola Ibrahim <git@abiosoft.com>
This commit is contained in:
Abiola Ibrahim
2025-08-19 16:47:42 +01:00
parent e65e477386
commit 44716fac27
11 changed files with 102 additions and 43 deletions
+10 -4
View File
@@ -31,8 +31,8 @@ var startCmd = &cobra.Command{
ctx := cmd.Context()
var processes []process.Process
if daemonArgs.vmnet {
processes = append(processes, vmnet.New())
if daemonArgs.vmnet.enabled {
processes = append(processes, vmnet.New(daemonArgs.vmnet.mode, daemonArgs.vmnet.netInterface))
}
if daemonArgs.inotify.enabled {
processes = append(processes, inotify.New())
@@ -79,7 +79,11 @@ var statusCmd = &cobra.Command{
}
var daemonArgs struct {
vmnet bool
vmnet struct {
enabled bool
mode string
netInterface string
}
inotify struct {
enabled bool
dirs []string
@@ -96,7 +100,9 @@ func init() {
daemonCmd.AddCommand(stopCmd)
daemonCmd.AddCommand(statusCmd)
startCmd.Flags().BoolVar(&daemonArgs.vmnet, "vmnet", false, "start vmnet")
startCmd.Flags().BoolVar(&daemonArgs.vmnet.enabled, "vmnet", false, "start vmnet")
startCmd.Flags().StringVar(&daemonArgs.vmnet.mode, "vmnet-mode", "shared", "vmnet mode (shared, bridged)")
startCmd.Flags().StringVar(&daemonArgs.vmnet.netInterface, "vmnet-interface", "en0", "vmnet interface for bridged mode")
startCmd.Flags().BoolVar(&daemonArgs.inotify.enabled, "inotify", false, "start inotify")
startCmd.Flags().StringSliceVar(&daemonArgs.inotify.dirs, "inotify-dir", nil, "set inotify directories")
startCmd.Flags().StringVar(&daemonArgs.inotify.runtime, "inotify-runtime", "docker", "set runtime")
+12
View File
@@ -186,6 +186,8 @@ func init() {
if util.MacOS() {
// network address
startCmd.Flags().BoolVar(&startCmdArgs.Network.Address, "network-address", false, "assign reachable IP address to the VM")
startCmd.Flags().StringVar(&startCmdArgs.Network.Mode, "network-mode", "shared", "network mode (shared, bridged)")
startCmd.Flags().StringVar(&startCmdArgs.Network.BridgeInterface, "network-interface", "en0", "host network interface to use for bridged mode")
// vm type
if util.MacOS13OrNewer() {
@@ -374,6 +376,10 @@ func setFixedConfigs(conf *config.Config) {
log.Warnln("network address cannot be disabled once enabled")
conf.Network.Address = true
}
if fixedConf.Network.Mode != "" {
warnIfNotEqual("network mode", conf.Network.Mode, fixedConf.Network.Mode)
conf.Network.Mode = fixedConf.Network.Mode
}
}
func prepareConfig(cmd *cobra.Command) {
@@ -513,6 +519,12 @@ func prepareConfig(cmd *cobra.Command) {
if !cmd.Flag("network-address").Changed {
startCmdArgs.Network.Address = current.Network.Address
}
if !cmd.Flag("network-mode").Changed {
startCmdArgs.Network.Mode = current.Network.Mode
}
if !cmd.Flag("network-interface").Changed {
startCmdArgs.Network.BridgeInterface = current.Network.BridgeInterface
}
if util.MacOS13OrNewer() {
if !cmd.Flag("vm-type").Changed {
startCmdArgs.VMType = current.VMType
+6 -4
View File
@@ -77,10 +77,12 @@ type Kubernetes struct {
// Network is VM network configuration
type Network struct {
Address bool `yaml:"address"`
DNSResolvers []net.IP `yaml:"dns"`
DNSHosts map[string]string `yaml:"dnsHosts"`
HostAddresses bool `yaml:"hostAddresses"`
Address bool `yaml:"address"`
DNSResolvers []net.IP `yaml:"dns"`
DNSHosts map[string]string `yaml:"dnsHosts"`
HostAddresses bool `yaml:"hostAddresses"`
Mode string `yaml:"mode"` // shared, bridged
BridgeInterface string `yaml:"interface"`
}
// Mount is volume mount
+3 -1
View File
@@ -94,6 +94,8 @@ func (l processManager) Start(ctx context.Context, conf config.Config) error {
if conf.Network.Address {
args = append(args, "--vmnet")
args = append(args, "--vmnet-mode", conf.Network.Mode)
args = append(args, "--vmnet-interface", conf.Network.BridgeInterface)
}
if conf.MountINotify {
args = append(args, "--inotify")
@@ -125,7 +127,7 @@ func processesFromConfig(conf config.Config) []process.Process {
var processes []process.Process
if conf.Network.Address {
processes = append(processes, vmnet.New())
processes = append(processes, vmnet.New(conf.Network.Mode, conf.Network.BridgeInterface))
}
if conf.MountINotify {
processes = append(processes, inotify.New())
+22 -19
View File
@@ -23,36 +23,39 @@ func (f *inotifyProcess) monitorContainerVolumes(ctx context.Context, c chan<- [
fetch := func() ([]string, error) {
var vols []string
// docker
if f.runtime != containerd.Name {
switch f.runtime {
case docker.Name:
vols, err := f.fetchVolumes(docker.Name)
if err != nil {
return nil, fmt.Errorf("error fetching docker volumes: %w", err)
}
return vols, nil
}
// containerd
var namespaces []string
out, err := f.guest.RunOutput("sudo", "nerdctl", "namespace", "list", "-q")
if err != nil {
return nil, fmt.Errorf("error retrieving containerd namespaces: %w", err)
}
if out != "" {
namespaces = strings.Fields(out)
}
for _, ns := range namespaces {
v, err := f.fetchVolumes("sudo", "nerdctl", "--namespace", ns)
case containerd.Name:
var namespaces []string
out, err := f.guest.RunOutput("sudo", "nerdctl", "namespace", "list", "-q")
if err != nil {
return nil, fmt.Errorf("error retrieving containerd volumes: %w", err)
return nil, fmt.Errorf("error retrieving containerd namespaces: %w", err)
}
if len(v) > 0 {
vols = append(vols, v...)
if out != "" {
namespaces = strings.Fields(out)
}
for _, ns := range namespaces {
v, err := f.fetchVolumes("sudo", "nerdctl", "--namespace", ns)
if err != nil {
return nil, fmt.Errorf("error retrieving containerd volumes: %w", err)
}
if len(v) > 0 {
vols = append(vols, v...)
}
}
return vols, nil
}
return vols, nil
return nil, nil
}
go func() {
+31 -11
View File
@@ -26,9 +26,17 @@ const (
var _ process.Process = (*vmnetProcess)(nil)
func New() process.Process { return &vmnetProcess{} }
func New(mode, netInterface string) process.Process {
return &vmnetProcess{
mode: mode,
netInterface: netInterface,
}
}
type vmnetProcess struct{}
type vmnetProcess struct {
mode string
netInterface string
}
func (*vmnetProcess) Alive(ctx context.Context) error {
info := Info()
@@ -60,7 +68,7 @@ func (*vmnetProcess) Alive(ctx context.Context) error {
func (*vmnetProcess) Name() string { return Name }
// Start implements process.BgProcess
func (*vmnetProcess) Start(ctx context.Context) error {
func (v *vmnetProcess) Start(ctx context.Context) error {
info := Info()
socket := info.Socket.File()
pid := info.PidFile
@@ -73,14 +81,26 @@ func (*vmnetProcess) Start(ctx context.Context) error {
go func() {
// rootfully start the vmnet daemon
command := cli.CommandInteractive("sudo", BinaryPath,
"--vmnet-mode", "shared",
"--socket-group", "staff",
"--vmnet-gateway", NetGateway,
"--vmnet-dhcp-end", NetDHCPEnd,
"--pidfile", pid,
socket,
)
var command *exec.Cmd
if v.mode == "bridged" {
command = cli.CommandInteractive("sudo", BinaryPath,
"--vmnet-mode", "bridged",
"--socket-group", "staff",
"--vmnet-interface", v.netInterface,
"--pidfile", pid,
socket,
)
} else {
command = cli.CommandInteractive("sudo", BinaryPath,
"--vmnet-mode", "shared",
"--socket-group", "staff",
"--vmnet-gateway", NetGateway,
"--vmnet-dhcp-end", NetDHCPEnd,
"--pidfile", pid,
socket,
)
}
if cli.Settings.Verbose {
command.Env = append(command.Env, os.Environ()...)
+11
View File
@@ -59,6 +59,17 @@ network:
# Default: false
address: false
# Network mode for the virtual machine (shared, bridged).
# NOTE: this is currently macOS only and ignored on Linux.
# Default: shared
mode: shared
# Network interface to use for bridged mode.
# This is only used when mode is set to bridged.
# NOTE: this is currently macOS only and ignored on Linux.
# Default: en0
interface: en0
# Custom DNS resolvers for the virtual machine.
#
# EXAMPLE
+1
View File
@@ -1,5 +1,6 @@
# starting vmnet daemon
%staff ALL=(root:wheel) NOPASSWD:NOSETENV: /opt/colima/bin/socket_vmnet --vmnet-mode shared --socket-group staff --vmnet-gateway 192.168.106.1 --vmnet-dhcp-end 192.168.106.254 *
%staff ALL=(root:wheel) NOPASSWD:NOSETENV: /opt/colima/bin/socket_vmnet --vmnet-mode bridged --socket-group staff *
# terminating vmnet daemon
%staff ALL=(root:wheel) NOPASSWD:NOSETENV: /usr/bin/pkill -F /opt/colima/run/*.pid
# validating vmnet daemon
+1 -1
View File
@@ -16,7 +16,7 @@ import (
func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.Context, error) {
// vmnet is used by QEMU and always used by incus (even with VZ)
useVmnet := conf.VMType == limaconfig.QEMU || conf.Runtime == incus.Name
useVmnet := conf.VMType == limaconfig.QEMU || conf.Runtime == incus.Name || conf.Network.Mode == "bridged"
// network daemon is only needed for vmnet
conf.Network.Address = conf.Network.Address && useVmnet
+3 -1
View File
@@ -33,8 +33,10 @@ func (l *limaVM) setupDNS(conf config.Config) error {
internalIP := limautil.InternalIPAddress(config.CurrentProfile().ID)
// extra dns entries
dnsHosts[conf.Hostname] = localhostAddr
dnsHosts["colima.internal"] = internalIP
if (conf.Hostname) != "" {
dnsHosts[conf.Hostname] = localhostAddr
}
var buf bytes.Buffer
+2 -2
View File
@@ -133,8 +133,8 @@ func newConf(ctx context.Context, conf config.Config) (l limaconfig.Config, err
reachableIPAddress := true
if conf.Network.Address {
// incus always uses vmnet
if l.VMType == limaconfig.VZ && conf.Runtime != incus.Name {
// vmnet is always used for incus runtime or bridged mode
if l.VMType == limaconfig.VZ && conf.Runtime != incus.Name && conf.Network.Mode != "bridged" {
l.Networks = append(l.Networks, limaconfig.Network{
VZNAT: true,
Interface: limautil.NetInterface,