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