Volt CLI: source-available under AGPSL v5.0
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
This commit is contained in:
240
pkg/network/network.go
Normal file
240
pkg/network/network.go
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user