Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/commands/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,8 @@ pub async fn cmd_snapshot_run(args: SnapshotRunArgs) -> Result<()> {
// With bridge mode, guest IP is always 10.0.2.100 on pasta network
// Each clone runs in its own namespace, so no IP conflict
let net = PastaNetwork::new(vm_id.clone(), tap_device.clone(), port_mappings.clone())
.with_loopback_ip(loopback_ip);
.with_loopback_ip(loopback_ip)
.with_restore_mode();
Box::new(net)
}
};
Expand Down
24 changes: 23 additions & 1 deletion src/network/pasta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub struct PastaNetwork {
pid_file: Option<PathBuf>,
loopback_ip: Option<String>, // Unique loopback IP for port forwarding (127.x.y.z)
holder_pid: Option<u32>, // Namespace PID (set in post_start)
restore_mode: bool, // Skip port probe in post_start (VM not loaded yet)
}

impl PastaNetwork {
Expand All @@ -90,6 +91,7 @@ impl PastaNetwork {
pid_file: None,
loopback_ip: None,
holder_pid: None,
restore_mode: false,
}
}

Expand All @@ -106,6 +108,20 @@ impl PastaNetwork {
self
}

/// Skip port forwarding probe in post_start() for snapshot restore.
///
/// During snapshot restore, post_start() runs BEFORE the VM snapshot is loaded
/// into Firecracker. Probing ports at that point forces pasta to attempt L2
/// forwarding to a non-existent guest, which can poison pasta's internal
/// connection tracking and cause subsequent data-bearing connections to fail
/// (TCP connect succeeds but 0 bytes returned). The proper verification happens
/// later via verify_port_forwarding() after the VM is resumed and fc-agent has
/// sent its gratuitous ARP.
pub fn with_restore_mode(mut self) -> Self {
self.restore_mode = true;
self
}

/// Get the loopback IP assigned to this VM for port forwarding
pub fn loopback_ip(&self) -> Option<&str> {
self.loopback_ip.as_deref()
Expand Down Expand Up @@ -604,7 +620,13 @@ impl NetworkManager for PastaNetwork {
// The PID file only means pasta spawned, not that ports are bound.
// Health checks use nsenter (bridge path), so without this check
// "healthy" doesn't mean port forwarding works.
if !self.port_mappings.is_empty() {
//
// Skip in restore mode: during snapshot restore, post_start() runs BEFORE
// the VM snapshot is loaded. Probing ports now forces pasta to attempt L2
// forwarding to a non-existent guest, poisoning its connection state and
// causing subsequent connections to return 0 bytes. The port check happens
// later via verify_port_forwarding() after the VM is actually running.
if !self.restore_mode && !self.port_mappings.is_empty() {
self.wait_for_port_forwarding().await?;
}

Expand Down
Loading