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

- `Stream.wait_closed()` now waits for outgoing UDP client tasks to finish.
- Preserve OS error details when UDP and WireGuard servers fail to start.
- `Stream.is_closing()` now returns `True` once the server or UDP client task has shut down.

Expand Down
55 changes: 53 additions & 2 deletions mitmproxy-rs/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pyo3::{IntoPyObjectExt, exceptions::PyOSError, intern, prelude::*};
use tokio::sync::{
mpsc::{self},
oneshot::{self},
watch,
};

use mitmproxy::messages::{ConnectionId, TransportCommand, TunnelInfo};
Expand All @@ -31,6 +32,8 @@ pub struct Stream {
pub peername: SocketAddr,
pub sockname: SocketAddr,
pub tunnel_info: TunnelInfo,
/// For UDP client streams, closed when the underlying task has finished.
pub task_done: Option<watch::Receiver<()>>,
}

#[pymethods]
Expand Down Expand Up @@ -135,9 +138,17 @@ impl Stream {
}
}

/// Wait until the stream is closed (currently a no-op).
/// Wait until the stream is closed.
/// For UDP client streams, this waits for the underlying task to finish.
fn wait_closed<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
pyo3_async_runtimes::tokio::future_into_py(py, std::future::ready(Ok(())))
let task_done = self.task_done.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
if let Some(mut task_done) = task_done {
// Errors once the sender is dropped by the finished task.
task_done.changed().await.ok();
}
Ok(())
})
}

/// Query the stream for details of the underlying network connection.
Expand Down Expand Up @@ -211,6 +222,45 @@ impl Drop for Stream {
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;

#[test]
fn wait_closed_waits_for_udp_task() -> PyResult<()> {
Python::initialize();
Python::attach(|py| {
pyo3_async_runtimes::tokio::run(py, async {
let (command_tx, _command_rx) = mpsc::unbounded_channel();
let (task_done_tx, task_done) = watch::channel(());
let stream = Stream {
connection_id: ConnectionId::unassigned_udp(),
state: StreamState::Closed,
command_tx,
peername: "127.0.0.1:1234".parse()?,
sockname: "127.0.0.1:4321".parse()?,
tunnel_info: TunnelInfo::None,
task_done: Some(task_done),
};
let wait_closed = Python::attach(|py| {
let stream = Py::new(py, stream)?;
pyo3_async_runtimes::tokio::into_future(
stream.bind(py).call_method0("wait_closed")?,
)
})?;
tokio::pin!(wait_closed);
assert!(
tokio::time::timeout(Duration::from_millis(20), &mut wait_closed)
.await
.is_err(),
"wait_closed returned before the task finished"
);
drop(task_done_tx);
tokio::time::timeout(Duration::from_secs(1), wait_closed)
.await
.expect("wait_closed did not return after the task finished")?;
Ok(())
})
})
}

#[test]
fn is_closing_when_server_shut_down() {
Expand All @@ -222,6 +272,7 @@ mod tests {
peername: "127.0.0.1:1234".parse().unwrap(),
sockname: "127.0.0.1:4321".parse().unwrap(),
tunnel_info: TunnelInfo::None,
task_done: None,
};
assert!(!stream.is_closing());
drop(command_rx);
Expand Down
1 change: 1 addition & 0 deletions mitmproxy-rs/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl PyInteropTask {
peername: src_addr,
sockname: dst_addr,
tunnel_info,
task_done: None,
};

let mut conns = active_streams.lock().await;
Expand Down
7 changes: 5 additions & 2 deletions mitmproxy-rs/src/udp_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use anyhow::{Result, anyhow};
use pyo3::prelude::*;
use tokio::net::{UdpSocket, lookup_host};
use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
use tokio::sync::oneshot;
use tokio::sync::{oneshot, watch};

use crate::stream::{Stream, StreamState};
use mitmproxy::MAX_PACKET_SIZE;
Expand All @@ -32,15 +32,17 @@ pub fn open_udp_connection(
let sockname = socket.local_addr()?;

let (command_tx, command_rx) = unbounded_channel();
let (task_done_tx, task_done) = watch::channel(());

tokio::spawn(async {
tokio::spawn(async move {
let task = UdpClientTask {
socket,
transport_commands_rx: command_rx,
};
if let Err(e) = task.run().await {
log::error!("UDP client errored: {e}");
}
drop(task_done_tx);
});

let stream = Stream {
Expand All @@ -50,6 +52,7 @@ pub fn open_udp_connection(
peername,
sockname,
tunnel_info: TunnelInfo::None,
task_done: Some(task_done),
};

Ok(stream)
Expand Down
Loading