From a1563b97a3be46a2c4f121ff81b71de1cd060329 Mon Sep 17 00:00:00 2001 From: Sonike <1700162+Sonike@users.noreply.github.com> Date: Sun, 23 Aug 2026 22:01:31 +0800 Subject: [PATCH] Preserve OS errors for UDP server startup --- CHANGELOG.md | 1 + mitmproxy-rs/src/server/udp.rs | 6 ++-- mitmproxy-rs/src/server/wireguard.rs | 6 ++-- mitmproxy-rs/src/util.rs | 43 ++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa64943..82d789c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Unreleased: mitmproxy_rs next +- Preserve OS error details when UDP and WireGuard servers fail to start. ## 20 July 2026: mitmproxy_rs 0.12.11 diff --git a/mitmproxy-rs/src/server/udp.rs b/mitmproxy-rs/src/server/udp.rs index 87d15fde..42965eb0 100644 --- a/mitmproxy-rs/src/server/udp.rs +++ b/mitmproxy-rs/src/server/udp.rs @@ -2,7 +2,7 @@ use std::net::{IpAddr, SocketAddr}; use mitmproxy::packet_sources::udp::UdpConf; -use crate::server::base::Server; +use crate::{server::base::Server, util::anyhow_to_pyerr}; use pyo3::prelude::*; /// A running UDP server. @@ -64,7 +64,9 @@ pub fn start_udp_server( }; let handle_tcp_stream = py.None(); pyo3_async_runtimes::tokio::future_into_py(py, async move { - let (server, local_addr) = Server::init(conf, handle_tcp_stream, handle_udp_stream).await?; + let (server, local_addr) = Server::init(conf, handle_tcp_stream, handle_udp_stream) + .await + .map_err(anyhow_to_pyerr)?; Ok(UdpServer { server, local_addr }) }) } diff --git a/mitmproxy-rs/src/server/wireguard.rs b/mitmproxy-rs/src/server/wireguard.rs index 29d6bbcd..b635fc05 100644 --- a/mitmproxy-rs/src/server/wireguard.rs +++ b/mitmproxy-rs/src/server/wireguard.rs @@ -1,6 +1,6 @@ use std::net::{IpAddr, SocketAddr}; -use crate::util::string_to_key; +use crate::util::{anyhow_to_pyerr, string_to_key}; use mitmproxy::packet_sources::wireguard::WireGuardConf; @@ -81,7 +81,9 @@ pub fn start_wireguard_server( peer_public_keys, }; pyo3_async_runtimes::tokio::future_into_py(py, async move { - let (server, local_addr) = Server::init(conf, handle_tcp_stream, handle_udp_stream).await?; + let (server, local_addr) = Server::init(conf, handle_tcp_stream, handle_udp_stream) + .await + .map_err(anyhow_to_pyerr)?; Ok(WireGuardServer { server, local_addr }) }) } diff --git a/mitmproxy-rs/src/util.rs b/mitmproxy-rs/src/util.rs index f347573f..7e0ed932 100644 --- a/mitmproxy-rs/src/util.rs +++ b/mitmproxy-rs/src/util.rs @@ -33,6 +33,49 @@ pub fn event_queue_unavailable(_: mpsc::error::SendError) -> PyErr { PyOSError::new_err("Server has been shut down.") } +/// Convert I/O errors wrapped by anyhow into Python OSErrors, preserving errno. +pub fn anyhow_to_pyerr(error: anyhow::Error) -> PyErr { + if let Some(io_error) = error + .chain() + .find_map(|cause| cause.downcast_ref::()) + { + let message = error.to_string(); + match io_error.raw_os_error() { + Some(errno) => PyOSError::new_err((errno, message)), + None => PyOSError::new_err(message), + } + } else { + error.into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::TcpListener; + + #[test] + fn anyhow_io_error_becomes_oserror() { + Python::initialize(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let io_error = TcpListener::bind(listener.local_addr().unwrap()).unwrap_err(); + let errno = io_error.raw_os_error().unwrap(); + let error = anyhow::Error::new(io_error).context("Failed to bind socket"); + + let py_error = anyhow_to_pyerr(error); + + Python::attach(|py| { + let value = py_error.value(py); + assert!(py_error.is_instance_of::(py)); + assert_eq!( + value.getattr("errno").unwrap().extract::().unwrap(), + errno + ); + assert!(value.to_string().contains("Failed to bind socket")); + }); + } +} + /// Generate a WireGuard private key, analogous to the `wg genkey` command. #[pyfunction] pub fn genkey() -> String {