Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
6 changes: 4 additions & 2 deletions mitmproxy-rs/src/server/udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 })
})
}
6 changes: 4 additions & 2 deletions mitmproxy-rs/src/server/wireguard.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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 })
})
}
43 changes: 43 additions & 0 deletions mitmproxy-rs/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,49 @@ pub fn event_queue_unavailable<T>(_: mpsc::error::SendError<T>) -> 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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to ioerror_to_oserror or something like this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — ioerror_to_oserror is clearer. I'll rename it. Thanks!

if let Some(io_error) = error
.chain()
.find_map(|cause| cause.downcast_ref::<std::io::Error>())
{
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::<PyOSError>(py));
assert_eq!(
value.getattr("errno").unwrap().extract::<i32>().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 {
Expand Down
Loading