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

  1. Align the smoltcp Stack config MTU with the actual tun interface MTU so no oversized packet is written
  2. Keep the tun interface alive for the process lifetime; prevent NetworkManager/scripts from deleting it, and verify with 'ip link show'
  3. Verify /dev/net/tun access and privileges (cap-add NET_ADMIN in containers) before starting the stack
  4. Ensure the tun fd stays open until the network task is fully shut down
  5. 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

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)