Skip to content
Merged
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::ioerror_to_oserror};
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(ioerror_to_oserror)?;
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::{ioerror_to_oserror, 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(ioerror_to_oserror)?;
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 ioerror_to_oserror(error: anyhow::Error) -> PyErr {
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 = ioerror_to_oserror(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