working VM setup
This commit is contained in:
		| @@ -437,7 +437,23 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> { | ||||
|  | ||||
|                 // Ensure bridge, NAT, and DHCP | ||||
|                 net::ensure_bridge(&nat.bridge_name, &nat.bridge_addr_cidr, ipv6_bridge_cidr.as_deref())?; | ||||
|                 net::ensure_nat(&nat.subnet_cidr)?; | ||||
|                 // Derive IPv6 subnet for NAT | ||||
|                 let ipv6_subnet = ipv6_bridge_cidr.as_ref().map(|cidr| { | ||||
|                     let parts: Vec<&str> = cidr.split('/').collect(); | ||||
|                     if parts.len() == 2 { | ||||
|                         let addr = parts[0]; | ||||
|                         if let Ok(ip) = addr.parse::<std::net::Ipv6Addr>() { | ||||
|                             let seg = ip.segments(); | ||||
|                             let pfx = std::net::Ipv6Addr::new(seg[0], seg[1], seg[2], seg[3], 0, 0, 0, 0); | ||||
|                             format!("{}/64", pfx) | ||||
|                         } else { | ||||
|                             "".to_string() | ||||
|                         } | ||||
|                     } else { | ||||
|                         "".to_string() | ||||
|                     } | ||||
|                 }); | ||||
|                 net::ensure_nat(&nat.subnet_cidr, ipv6_subnet.as_deref())?; | ||||
|                 let lease_used = net::ensure_dnsmasq( | ||||
|                     &nat.bridge_name, | ||||
|                     &nat.dhcp_start, | ||||
|   | ||||
| @@ -86,7 +86,8 @@ sysctl -w net.ipv4.ip_forward=1 >/dev/null || true | ||||
|  | ||||
| /// Ensure nftables NAT masquerading for the given subnet toward the default WAN interface. | ||||
| /// Creates table/chain if missing and adds/keeps a single masquerade rule. | ||||
| pub fn ensure_nat(subnet_cidr: &str) -> Result<(), CloudHvError> { | ||||
| /// If ipv6_subnet is provided, also sets up IPv6 NAT. | ||||
| pub fn ensure_nat(subnet_cidr: &str, ipv6_subnet: Option<&str>) -> Result<(), CloudHvError> { | ||||
|     for bin in ["ip", "nft"] { | ||||
|         if sal_process::which(bin).is_none() { | ||||
|             return Err(CloudHvError::DependencyMissing(format!( | ||||
| @@ -95,23 +96,34 @@ pub fn ensure_nat(subnet_cidr: &str) -> Result<(), CloudHvError> { | ||||
|             ))); | ||||
|         } | ||||
|     } | ||||
|     let v6_subnet = ipv6_subnet.unwrap_or(""); | ||||
|     let body = format!( | ||||
|         "set -e | ||||
| SUBNET={subnet} | ||||
| IPV6_SUBNET={v6subnet} | ||||
|  | ||||
| WAN_IF=$(ip -o route show default | awk '{{print $5}}' | head -n1) | ||||
| if [ -z \"$WAN_IF\" ]; then | ||||
|   echo \"No default WAN interface detected (required for IPv4 NAT)\" >&2 | ||||
|   echo \"No default WAN interface detected (required for NAT)\" >&2 | ||||
|   exit 2 | ||||
| fi | ||||
|  | ||||
| # IPv4 NAT | ||||
| nft list table ip hero >/dev/null 2>&1 || nft add table ip hero | ||||
| nft list chain ip hero postrouting >/dev/null 2>&1 || nft add chain ip hero postrouting {{ type nat hook postrouting priority 100 \\; }} | ||||
| # Only add rule if not present | ||||
| nft list chain ip hero postrouting | grep -q \"ip saddr $SUBNET oifname \\\"$WAN_IF\\\" masquerade\" \ | ||||
|   || nft add rule ip hero postrouting ip saddr $SUBNET oifname \"$WAN_IF\" masquerade | ||||
|  | ||||
| # IPv6 NAT (if subnet provided) | ||||
| if [ -n \"$IPV6_SUBNET\" ]; then | ||||
|   nft list table ip6 hero >/dev/null 2>&1 || nft add table ip6 hero | ||||
|   nft list chain ip6 hero postrouting >/dev/null 2>&1 || nft add chain ip6 hero postrouting {{ type nat hook postrouting priority 100 \\; }} | ||||
|   nft list chain ip6 hero postrouting | grep -q \"ip6 saddr $IPV6_SUBNET oifname \\\"$WAN_IF\\\" masquerade\" \ | ||||
|     || nft add rule ip6 hero postrouting ip6 saddr $IPV6_SUBNET oifname \"$WAN_IF\" masquerade | ||||
| fi | ||||
| ", | ||||
|         subnet = shell_escape(subnet_cidr), | ||||
|         v6subnet = shell_escape(v6_subnet), | ||||
|     ); | ||||
|     run_heredoc("HERONAT", &body) | ||||
| } | ||||
| @@ -174,11 +186,13 @@ printf '%s\\n' \ | ||||
|  | ||||
| # Optional IPv6 RA/DHCPv6 | ||||
| if [ -n \"$IPV6_CIDR\" ]; then | ||||
|   BRIDGE_ADDR=\"${{IPV6_CIDR%/*}}\" | ||||
|   BRIDGE_PREFIX=$(echo \"$IPV6_CIDR\" | cut -d: -f1-4):: | ||||
|   printf '%s\\n' \ | ||||
|     \"enable-ra\" \ | ||||
|     \"dhcp-range=::,constructor:BR_PLACEHOLDER,ra-names,64,12h\" \ | ||||
|     \"dhcp-option=option6:dns-server,[2001:4860:4860::8888],[2606:4700:4700::1111]\" >>\"$TMP\" | ||||
|   sed -i \"s/BR_PLACEHOLDER/$BR/g\" \"$TMP\" | ||||
|     \"dhcp-range=$BRIDGE_PREFIX,ra-names,12h\" \ | ||||
|     \"dhcp-option=option6:dns-server,[2001:4860:4860::8888]\" \ | ||||
|     \"dhcp-option=option6:route,3600,400::/7,[$BRIDGE_ADDR]\" >>\"$TMP\" | ||||
| fi | ||||
|  | ||||
| if [ ! -f \"$CFG\" ] || ! cmp -s \"$CFG\" \"$TMP\"; then | ||||
|   | ||||
| @@ -175,11 +175,11 @@ pub fn image_prepare(opts: &ImagePrepOptions) -> Result<ImagePrepResult, ImagePr | ||||
|             // Build bash script that performs all steps and echos "RAW|ROOT_UUID|BOOT_UUID" at end | ||||
|             let disable_ci_net = opts.disable_cloud_init_net; | ||||
|  | ||||
|             // IPv6 static guest assignment (derive from mycelium interface) - enabled by default | ||||
|             // If HERO_VIRT_IPV6_STATIC_GUEST=false, keep dynamic behavior (SLAAC/DHCPv6). | ||||
|             // IPv6 static guest assignment (derive from mycelium interface) - disabled by default to use RA | ||||
|             // If HERO_VIRT_IPV6_STATIC_GUEST=true, use static IPv6; else use RA/SLAAC. | ||||
|             let static_v6 = std::env::var("HERO_VIRT_IPV6_STATIC_GUEST") | ||||
|                 .map(|v| matches!(v.to_lowercase().as_str(), "" | "1" | "true" | "yes")) | ||||
|                 .unwrap_or(true); | ||||
|                 .unwrap_or(false); | ||||
|             let myc_if = | ||||
|                 std::env::var("HERO_VIRT_MYCELIUM_IF").unwrap_or_else(|_| "mycelium".into()); | ||||
|  | ||||
| @@ -211,6 +211,7 @@ pub fn image_prepare(opts: &ImagePrepOptions) -> Result<ImagePrepResult, ImagePr | ||||
|  | ||||
|             // Derive per-host /64 from mycelium and deterministic per-VM guest address | ||||
|             let mut np_v6_block = String::new(); | ||||
|             let mut accept_ra = String::new(); | ||||
|             let mut dhcp6_effective = opts.net.dhcp6; | ||||
|             if static_v6 { | ||||
|                 if let Some(h) = host_v6 { | ||||
| @@ -229,12 +230,16 @@ pub fn image_prepare(opts: &ImagePrepOptions) -> Result<ImagePrepResult, ImagePr | ||||
|  | ||||
|                     // Inject a YAML block for static v6 | ||||
|                     np_v6_block = format!( | ||||
|                         "      addresses:\n        - {}/64\n      routes:\n        - to: \"::/0\"\n          via: {}\n", | ||||
|                         guest_ip, gw_ip | ||||
|                         "      addresses:\n        - {}/64\n      routes:\n        - to: \"::/0\"\n          via: {}\n        - to: \"400::/7\"\n          via: {}\n", | ||||
|                         guest_ip, gw_ip, gw_ip | ||||
|                     ); | ||||
|                     // Disable dhcp6 when we provide a static address | ||||
|                     dhcp6_effective = false; | ||||
|                 } | ||||
|             } else { | ||||
|                 // Use RA for IPv6 | ||||
|                 accept_ra = "\n      accept-ra: true".to_string(); | ||||
|                 dhcp6_effective = false; | ||||
|             } | ||||
|  | ||||
|             // Keep script small and robust; avoid brace-heavy awk to simplify escaping. | ||||
| @@ -416,8 +421,8 @@ network: | ||||
|         macaddress: {vm_mac} | ||||
|       set-name: eth0 | ||||
|       dhcp4: {dhcp4} | ||||
|       dhcp6: {dhcp6} | ||||
| {np_v6_block}      nameservers: | ||||
|       dhcp6: {dhcp6}{accept_ra}{np_v6_block} | ||||
|       nameservers: | ||||
|         addresses: [8.8.8.8, 1.1.1.1, 2001:4860:4860::8888] | ||||
| EOF | ||||
| # Enable SSH password authentication and set a default password for 'ubuntu' | ||||
| @@ -721,6 +726,7 @@ exit 0 | ||||
|                 vm_mac = vm_mac, | ||||
|                 dhcp4 = if opts.net.dhcp4 { "true" } else { "false" }, | ||||
|                 dhcp6 = if dhcp6_effective { "true" } else { "false" }, | ||||
|                 accept_ra = accept_ra, | ||||
|                 np_v6_block = np_v6_block, | ||||
|                 disable_ci_net = if disable_ci_net { "true" } else { "false" } | ||||
|             ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user