Complete infrastructure platform CLI: - Container runtime (systemd-nspawn) - VoltVisor VMs (Neutron Stardust / QEMU) - Stellarium CAS (content-addressed storage) - ORAS Registry - GitOps integration - Landlock LSM security - Compose orchestration - Mesh networking Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
241 lines
6.1 KiB
Go
241 lines
6.1 KiB
Go
/*
|
|
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/<pid>/ns/net — used implicitly by
|
|
// ip link set ... netns <pid> 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
|
|
}
|