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 |                 // Ensure bridge, NAT, and DHCP | ||||||
|                 net::ensure_bridge(&nat.bridge_name, &nat.bridge_addr_cidr, ipv6_bridge_cidr.as_deref())?; |                 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( |                 let lease_used = net::ensure_dnsmasq( | ||||||
|                     &nat.bridge_name, |                     &nat.bridge_name, | ||||||
|                     &nat.dhcp_start, |                     &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. | /// 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. | /// 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"] { |     for bin in ["ip", "nft"] { | ||||||
|         if sal_process::which(bin).is_none() { |         if sal_process::which(bin).is_none() { | ||||||
|             return Err(CloudHvError::DependencyMissing(format!( |             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!( |     let body = format!( | ||||||
|         "set -e |         "set -e | ||||||
| SUBNET={subnet} | SUBNET={subnet} | ||||||
|  | IPV6_SUBNET={v6subnet} | ||||||
|  |  | ||||||
| WAN_IF=$(ip -o route show default | awk '{{print $5}}' | head -n1) | WAN_IF=$(ip -o route show default | awk '{{print $5}}' | head -n1) | ||||||
| if [ -z \"$WAN_IF\" ]; then | 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 |   exit 2 | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  | # IPv4 NAT | ||||||
| nft list table ip hero >/dev/null 2>&1 || nft add table ip hero | 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 \\; }} | 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 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 |   || 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), |         subnet = shell_escape(subnet_cidr), | ||||||
|  |         v6subnet = shell_escape(v6_subnet), | ||||||
|     ); |     ); | ||||||
|     run_heredoc("HERONAT", &body) |     run_heredoc("HERONAT", &body) | ||||||
| } | } | ||||||
| @@ -174,11 +186,13 @@ printf '%s\\n' \ | |||||||
|  |  | ||||||
| # Optional IPv6 RA/DHCPv6 | # Optional IPv6 RA/DHCPv6 | ||||||
| if [ -n \"$IPV6_CIDR\" ]; then | if [ -n \"$IPV6_CIDR\" ]; then | ||||||
|  |   BRIDGE_ADDR=\"${{IPV6_CIDR%/*}}\" | ||||||
|  |   BRIDGE_PREFIX=$(echo \"$IPV6_CIDR\" | cut -d: -f1-4):: | ||||||
|   printf '%s\\n' \ |   printf '%s\\n' \ | ||||||
|     \"enable-ra\" \ |     \"enable-ra\" \ | ||||||
|     \"dhcp-range=::,constructor:BR_PLACEHOLDER,ra-names,64,12h\" \ |     \"dhcp-range=$BRIDGE_PREFIX,ra-names,12h\" \ | ||||||
|     \"dhcp-option=option6:dns-server,[2001:4860:4860::8888],[2606:4700:4700::1111]\" >>\"$TMP\" |     \"dhcp-option=option6:dns-server,[2001:4860:4860::8888]\" \ | ||||||
|   sed -i \"s/BR_PLACEHOLDER/$BR/g\" \"$TMP\" |     \"dhcp-option=option6:route,3600,400::/7,[$BRIDGE_ADDR]\" >>\"$TMP\" | ||||||
| fi | fi | ||||||
|  |  | ||||||
| if [ ! -f \"$CFG\" ] || ! cmp -s \"$CFG\" \"$TMP\"; then | 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 |             // 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; |             let disable_ci_net = opts.disable_cloud_init_net; | ||||||
|  |  | ||||||
|             // IPv6 static guest assignment (derive from mycelium interface) - enabled by default |             // IPv6 static guest assignment (derive from mycelium interface) - disabled by default to use RA | ||||||
|             // If HERO_VIRT_IPV6_STATIC_GUEST=false, keep dynamic behavior (SLAAC/DHCPv6). |             // 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") |             let static_v6 = std::env::var("HERO_VIRT_IPV6_STATIC_GUEST") | ||||||
|                 .map(|v| matches!(v.to_lowercase().as_str(), "" | "1" | "true" | "yes")) |                 .map(|v| matches!(v.to_lowercase().as_str(), "" | "1" | "true" | "yes")) | ||||||
|                 .unwrap_or(true); |                 .unwrap_or(false); | ||||||
|             let myc_if = |             let myc_if = | ||||||
|                 std::env::var("HERO_VIRT_MYCELIUM_IF").unwrap_or_else(|_| "mycelium".into()); |                 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 |             // Derive per-host /64 from mycelium and deterministic per-VM guest address | ||||||
|             let mut np_v6_block = String::new(); |             let mut np_v6_block = String::new(); | ||||||
|  |             let mut accept_ra = String::new(); | ||||||
|             let mut dhcp6_effective = opts.net.dhcp6; |             let mut dhcp6_effective = opts.net.dhcp6; | ||||||
|             if static_v6 { |             if static_v6 { | ||||||
|                 if let Some(h) = host_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 |                     // Inject a YAML block for static v6 | ||||||
|                     np_v6_block = format!( |                     np_v6_block = format!( | ||||||
|                         "      addresses:\n        - {}/64\n      routes:\n        - to: \"::/0\"\n          via: {}\n", |                         "      addresses:\n        - {}/64\n      routes:\n        - to: \"::/0\"\n          via: {}\n        - to: \"400::/7\"\n          via: {}\n", | ||||||
|                         guest_ip, gw_ip |                         guest_ip, gw_ip, gw_ip | ||||||
|                     ); |                     ); | ||||||
|                     // Disable dhcp6 when we provide a static address |                     // Disable dhcp6 when we provide a static address | ||||||
|                     dhcp6_effective = false; |                     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. |             // Keep script small and robust; avoid brace-heavy awk to simplify escaping. | ||||||
| @@ -416,8 +421,8 @@ network: | |||||||
|         macaddress: {vm_mac} |         macaddress: {vm_mac} | ||||||
|       set-name: eth0 |       set-name: eth0 | ||||||
|       dhcp4: {dhcp4} |       dhcp4: {dhcp4} | ||||||
|       dhcp6: {dhcp6} |       dhcp6: {dhcp6}{accept_ra}{np_v6_block} | ||||||
| {np_v6_block}      nameservers: |       nameservers: | ||||||
|         addresses: [8.8.8.8, 1.1.1.1, 2001:4860:4860::8888] |         addresses: [8.8.8.8, 1.1.1.1, 2001:4860:4860::8888] | ||||||
| EOF | EOF | ||||||
| # Enable SSH password authentication and set a default password for 'ubuntu' | # Enable SSH password authentication and set a default password for 'ubuntu' | ||||||
| @@ -721,6 +726,7 @@ exit 0 | |||||||
|                 vm_mac = vm_mac, |                 vm_mac = vm_mac, | ||||||
|                 dhcp4 = if opts.net.dhcp4 { "true" } else { "false" }, |                 dhcp4 = if opts.net.dhcp4 { "true" } else { "false" }, | ||||||
|                 dhcp6 = if dhcp6_effective { "true" } else { "false" }, |                 dhcp6 = if dhcp6_effective { "true" } else { "false" }, | ||||||
|  |                 accept_ra = accept_ra, | ||||||
|                 np_v6_block = np_v6_block, |                 np_v6_block = np_v6_block, | ||||||
|                 disable_ci_net = if disable_ci_net { "true" } else { "false" } |                 disable_ci_net = if disable_ci_net { "true" } else { "false" } | ||||||
|             ); |             ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user