embassy-rs/embassy · error
transmit error
Error message
transmit error: {:?} What it means
The tuntap device panicked because writing a packet to the host's TUN device failed with an error other than WouldBlock (WouldBlock is only logged as 'transmit WouldBlock'). Because the smoltcp Device trait's transmit() cannot report errors, any real write failure (fd closed, interface removed, invalid MTU/argument) escalates to a panic in the driver task.
Solutions
- Align the smoltcp Stack config MTU with the actual tun interface MTU so no oversized packet is written
- Keep the tun interface alive for the process lifetime; prevent NetworkManager/scripts from deleting it, and verify with 'ip link show'
- Verify /dev/net/tun access and privileges (cap-add NET_ADMIN in containers) before starting the stack
- Ensure the tun fd stays open until the network task is fully shut down
- Patch the driver locally to log and drop the packet instead of panicking on non-WouldBlock write errors
Example fix
// before
Err(e) => panic!("transmit error: {:?}", e),
// after (local patch)
Err(e) => {
warn!("tun write error: {:?}, dropping packet", e);
return Ok(());
} Defensive patterns
Strategy: validation
Validate before calling
// preflight: match configured MTU to the tun device MTU before starting the stack // actual=$(ip link show tun0 | grep -o 'mtu [0-9]*' | cut -d' ' -f2) // assert configured_mtu <= actual, else writes will fail with EINVAL
Type guard
// Rust: classify write errors before escalation
fn is_fatal_write_error(e: &std::io::Error) -> bool {
!matches!(e.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::Interrupted)
} Try / catch
// run the network task under catch_unwind and restart on failure
let result = std::panic::catch_unwind(AssertUnwindSafe(|| network_task()));
if result.is_err() {
error!("transmit panic; re-creating tun device and restarting task");
} Prevention
- Set the smoltcp stack MTU no larger than the tun interface MTU
- Keep the tun interface alive and owned by the running user for the app lifetime
- Verify privileges (/dev/net/tun, NET_ADMIN) before starting traffic
- Close the tun fd only after the network task has fully stopped
- Test interface teardown scenarios (link del, restarts) in CI to catch panics early
When it happens
Trigger: write() on the TUN fd returns a non-WouldBlock error during transmit(): fd closed or invalid after shutdown, the tun interface deleted at runtime, packet larger than the tun MTU yielding EINVAL, or the device underlying state changed (ENODEV/ENETDOWN).
Common situations: Configuring an interface MTU larger than the tun device's actual MTU so oversized writes fail; scripts/NetworkManager tearing down the tun interface while traffic flows; fd closed by another thread during shutdown; container/permission issues surfacing at first write.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/64eac1b4e997c747.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-net-tuntap/src/lib.rs:197
Ok(n) => {
buf.set_len(n);
Some(buf)
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => None,
Err(e) => panic!("read error: {:?}", e),
}
}
fn can_transmit(&mut self) -> bool {
true
}
fn transmit(&mut self, buf: PacketBuf) -> Result<(), PacketBuf> {
// todo handle WouldBlock with async
match unsafe { self.device.get_mut() }.write(&buf) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => info!("transmit WouldBlock"),
Err(e) => panic!("transmit error: {:?}", e),
}
Ok(())
}
}
View on GitHub (pinned to 463a07b963)