/* Volt Network - VM networking using Linux networking stack Features: - Network namespaces per VM - veth pairs for connectivity - Bridge networking (voltbr0) - NAT for outbound traffic - Optional direct/macvlan networking - IPv4 and IPv6 support */ package network import ( "fmt" "net" "os" "os/exec" "path/filepath" ) // NetworkConfig defines VM network configuration type NetworkConfig struct { Name string Type string // bridge, macvlan, host, none Bridge string IP string Gateway string DNS []string MTU int EnableNAT bool } // DefaultConfig returns default network configuration func DefaultConfig() *NetworkConfig { return &NetworkConfig{ Type: "bridge", Bridge: "voltbr0", MTU: 1500, EnableNAT: true, DNS: []string{"8.8.8.8", "8.8.4.4"}, } } // Manager handles VM networking type Manager struct { bridgeName string bridgeIP string subnet *net.IPNet nextIP byte } // NewManager creates a new network manager func NewManager(bridgeName, bridgeSubnet string) (*Manager, error) { _, subnet, err := net.ParseCIDR(bridgeSubnet) if err != nil { return nil, fmt.Errorf("invalid subnet: %w", err) } bridgeIP := subnet.IP.To4() bridgeIP[3] = 1 // .1 for bridge return &Manager{ bridgeName: bridgeName, bridgeIP: bridgeIP.String(), subnet: subnet, nextIP: 2, // Start allocating from .2 }, nil } // Setup creates the bridge and configures NAT func (m *Manager) Setup() error { // Check if bridge exists if _, err := net.InterfaceByName(m.bridgeName); err == nil { return nil // Already exists } // Create bridge if err := m.createBridge(); err != nil { return fmt.Errorf("failed to create bridge: %w", err) } // Configure NAT if err := m.setupNAT(); err != nil { return fmt.Errorf("failed to setup NAT: %w", err) } return nil } // createBridge creates the volt bridge interface func (m *Manager) createBridge() error { commands := [][]string{ {"ip", "link", "add", m.bridgeName, "type", "bridge"}, {"ip", "addr", "add", fmt.Sprintf("%s/24", m.bridgeIP), "dev", m.bridgeName}, {"ip", "link", "set", m.bridgeName, "up"}, } for _, cmd := range commands { if err := exec.Command(cmd[0], cmd[1:]...).Run(); err != nil { return fmt.Errorf("command %v failed: %w", cmd, err) } } return nil } // setupNAT configures iptables for NAT func (m *Manager) setupNAT() error { subnet := fmt.Sprintf("%s/24", m.subnet.IP.String()) commands := [][]string{ // Enable IP forwarding {"sysctl", "-w", "net.ipv4.ip_forward=1"}, // NAT for outbound traffic {"iptables", "-t", "nat", "-A", "POSTROUTING", "-s", subnet, "-j", "MASQUERADE"}, // Allow forwarding for bridge {"iptables", "-A", "FORWARD", "-i", m.bridgeName, "-j", "ACCEPT"}, {"iptables", "-A", "FORWARD", "-o", m.bridgeName, "-j", "ACCEPT"}, } for _, cmd := range commands { exec.Command(cmd[0], cmd[1:]...).Run() // Ignore errors for idempotency } return nil } // AllocateIP returns the next available IP func (m *Manager) AllocateIP() string { ip := net.IP(make([]byte, 4)) copy(ip, m.subnet.IP.To4()) ip[3] = m.nextIP m.nextIP++ return ip.String() } // CreateVMNetwork sets up networking for a VM func (m *Manager) CreateVMNetwork(vmName string, pid int) (*VMNetwork, error) { vethHost := fmt.Sprintf("veth_%s_h", vmName[:min(8, len(vmName))]) vethVM := fmt.Sprintf("veth_%s_v", vmName[:min(8, len(vmName))]) vmIP := m.AllocateIP() // Network namespace is at /proc//ns/net — used implicitly by // ip link set ... netns below. _ = fmt.Sprintf("/proc/%d/ns/net", pid) // validate pid is set // Create veth pair if err := exec.Command("ip", "link", "add", vethHost, "type", "veth", "peer", "name", vethVM).Run(); err != nil { return nil, fmt.Errorf("failed to create veth pair: %w", err) } // Move VM end to namespace if err := exec.Command("ip", "link", "set", vethVM, "netns", fmt.Sprintf("%d", pid)).Run(); err != nil { return nil, fmt.Errorf("failed to move veth to namespace: %w", err) } // Attach host end to bridge if err := exec.Command("ip", "link", "set", vethHost, "master", m.bridgeName).Run(); err != nil { return nil, fmt.Errorf("failed to attach to bridge: %w", err) } // Bring up host end if err := exec.Command("ip", "link", "set", vethHost, "up").Run(); err != nil { return nil, fmt.Errorf("failed to bring up host veth: %w", err) } // Configure VM end (inside namespace via nsenter) nsCommands := [][]string{ {"ip", "addr", "add", fmt.Sprintf("%s/24", vmIP), "dev", vethVM}, {"ip", "link", "set", vethVM, "up"}, {"ip", "link", "set", "lo", "up"}, {"ip", "route", "add", "default", "via", m.bridgeIP}, } for _, cmd := range nsCommands { nsCmd := exec.Command("nsenter", append([]string{"-t", fmt.Sprintf("%d", pid), "-n", "--"}, cmd...)...) if err := nsCmd.Run(); err != nil { return nil, fmt.Errorf("ns command %v failed: %w", cmd, err) } } return &VMNetwork{ Name: vmName, IP: vmIP, Gateway: m.bridgeIP, VethHost: vethHost, VethVM: vethVM, PID: pid, }, nil } // DestroyVMNetwork removes VM networking func (m *Manager) DestroyVMNetwork(vn *VMNetwork) error { // Deleting host veth automatically removes the pair exec.Command("ip", "link", "del", vn.VethHost).Run() return nil } // VMNetwork represents a VM's network configuration type VMNetwork struct { Name string IP string Gateway string VethHost string VethVM string PID int } // WriteResolvConf writes DNS configuration to VM func (vn *VMNetwork) WriteResolvConf(rootfs string, dns []string) error { resolvPath := filepath.Join(rootfs, "etc", "resolv.conf") content := "" for _, d := range dns { content += fmt.Sprintf("nameserver %s\n", d) } return os.WriteFile(resolvPath, []byte(content), 0644) } // WriteHostsFile writes /etc/hosts for VM func (vn *VMNetwork) WriteHostsFile(rootfs string) error { hostsPath := filepath.Join(rootfs, "etc", "hosts") content := fmt.Sprintf(`127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback %s %s `, vn.IP, vn.Name) return os.WriteFile(hostsPath, []byte(content), 0644) } // Helper function func min(a, b int) int { if a < b { return a } return b }