diff options
Diffstat (limited to 'crates/tor-proto')
| -rw-r--r-- | crates/tor-proto/src/circuit.rs | 15 | ||||
| -rw-r--r-- | crates/tor-proto/src/circuit/halfstream.rs | 112 | ||||
| -rw-r--r-- | crates/tor-proto/src/circuit/reactor.rs | 49 | ||||
| -rw-r--r-- | crates/tor-proto/src/circuit/streammap.rs | 41 | ||||
| -rw-r--r-- | crates/tor-proto/src/stream.rs | 3 | ||||
| -rw-r--r-- | crates/tor-proto/src/stream/cmdcheck.rs | 55 | ||||
| -rw-r--r-- | crates/tor-proto/src/stream/data.rs | 60 | ||||
| -rw-r--r-- | crates/tor-proto/src/stream/resolve.rs | 40 |
8 files changed, 273 insertions, 102 deletions
diff --git a/crates/tor-proto/src/circuit.rs b/crates/tor-proto/src/circuit.rs index f3dae893e..0868bac23 100644 --- a/crates/tor-proto/src/circuit.rs +++ b/crates/tor-proto/src/circuit.rs @@ -56,7 +56,10 @@ use crate::circuit::reactor::{ }; pub use crate::circuit::unique_id::UniqId; use crate::crypto::cell::{HopNum, InboundClientCrypt, OutboundClientCrypt}; -use crate::stream::{DataStream, ResolveStream, StreamParameters, StreamReader}; +use crate::stream::{ + AnyCmdChecker, DataCmdChecker, DataStream, ResolveCmdChecker, ResolveStream, StreamParameters, + StreamReader, +}; use crate::{Error, ResolveError, Result}; use tor_cell::{ chancell::{self, msg::AnyChanMsg, CircId}, @@ -404,6 +407,7 @@ impl ClientCirc { async fn begin_stream_impl( &self, begin_msg: AnyRelayMsg, + cmd_checker: AnyCmdChecker, ) -> Result<(StreamReader, StreamTarget)> { // TODO: Possibly this should take a hop, rather than just // assuming it's the last hop. @@ -428,6 +432,7 @@ impl ClientCirc { sender, rx: msg_rx, done: tx, + cmd_checker, }) .map_err(|_| Error::CircuitClosed)?; @@ -453,7 +458,9 @@ impl ClientCirc { /// Start a DataStream (anonymized connection) to the given /// address and port, using a BEGIN cell. async fn begin_data_stream(&self, msg: AnyRelayMsg, optimistic: bool) -> Result<DataStream> { - let (reader, target) = self.begin_stream_impl(msg).await?; + let (reader, target) = self + .begin_stream_impl(msg, DataCmdChecker::new_any()) + .await?; let mut stream = DataStream::new(reader, target); if !optimistic { stream.wait_for_connection().await?; @@ -539,7 +546,9 @@ impl ClientCirc { /// Helper: Send the resolve message, and read resolved message from /// resolve stream. async fn try_resolve(&self, msg: Resolve) -> Result<Resolved> { - let (reader, _) = self.begin_stream_impl(msg.into()).await?; + let (reader, _) = self + .begin_stream_impl(msg.into(), ResolveCmdChecker::new_any()) + .await?; let mut resolve_stream = ResolveStream::new(reader); resolve_stream.read_msg().await } diff --git a/crates/tor-proto/src/circuit/halfstream.rs b/crates/tor-proto/src/circuit/halfstream.rs index f6204a171..b0cbf322f 100644 --- a/crates/tor-proto/src/circuit/halfstream.rs +++ b/crates/tor-proto/src/circuit/halfstream.rs @@ -3,10 +3,10 @@ //! A half-closed stream is one that we've sent an END on, but where //! we might still receive some cells. -use crate::circuit::sendme::{StreamRecvWindow, StreamSendWindow}; +use crate::circuit::sendme::{cmd_counts_towards_windows, StreamRecvWindow, StreamSendWindow}; +use crate::stream::{AnyCmdChecker, StreamStatus}; use crate::{Error, Result}; -use tor_cell::relaycell::UnparsedRelayCell; -use tor_cell::restricted_msg; +use tor_cell::relaycell::{RelayCmd, UnparsedRelayCell}; /// Type to track state of half-closed streams. /// @@ -23,23 +23,8 @@ pub(super) struct HalfStream { /// Receive window for this stream. Used to detect whether we get too /// many data cells. recvw: StreamRecvWindow, - /// If true, accept a connected cell on this stream. - connected_ok: bool, -} - -restricted_msg! { - enum HalfStreamMsg : RelayMsg { - Sendme, Data, Connected, End, Resolved - } -} - -/// A status value returned by [`HalfStream::handle_msg`]. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub(crate) enum HalfStreamStatus { - /// The stream has been closed successfully and can now be dropped. - Closed, - /// The stream is still half,open, and must still be tracked. - Open, + /// Object to tell us which cells to accept on this stream. + cmd_checker: AnyCmdChecker, } impl HalfStream { @@ -47,12 +32,12 @@ impl HalfStream { pub(super) fn new( sendw: StreamSendWindow, recvw: StreamRecvWindow, - connected_ok: bool, + cmd_checker: AnyCmdChecker, ) -> Self { HalfStream { sendw, recvw, - connected_ok, + cmd_checker, } } @@ -62,37 +47,25 @@ impl HalfStream { /// The caller must handle END cells; it is an internal error to pass /// END cells to this method. /// no ends here. - pub(super) fn handle_msg(&mut self, msg: UnparsedRelayCell) -> Result<HalfStreamStatus> { - use HalfStreamMsg::*; - use HalfStreamStatus::*; - let msg = msg - .decode::<HalfStreamMsg>() - .map_err(|e| Error::from_bytes_err(e, "message on half-closed stream"))? - .into_msg(); - match msg { - Sendme(_) => { - self.sendw.put(Some(()))?; - Ok(Open) - } - Data(_) => { - self.recvw.take()?; - Ok(Open) - } - Connected(_) => { - if self.connected_ok { - self.connected_ok = false; - Ok(Open) - } else { - Err(Error::CircProto( - "Bad CONNECTED cell on a closed stream!".into(), - )) - } - } - End(_) => Ok(Closed), - // TODO XXXX: We should only allow a Resolved() on streams where we sent - // Resolve. My intended solution for #774 will fix this too. - Resolved(_) => Ok(Closed), + pub(super) fn handle_msg(&mut self, msg: UnparsedRelayCell) -> Result<StreamStatus> { + use tor_cell::relaycell::msg::Sendme; + use StreamStatus::*; + if msg.cmd() == RelayCmd::SENDME { + // We handle SENDME separately, and don't give it to the checker. + let _ = msg + .decode::<Sendme>() + .map_err(|e| Error::from_bytes_err(e, "SENDME on half-closed stream"))?; + self.sendw.put(Some(()))?; + return Ok(Open); + } + + if cmd_counts_towards_windows(msg.cmd()) { + self.recvw.take()?; } + + let status = self.cmd_checker.check_msg(&msg)?; + self.cmd_checker.consume_checked_msg(msg)?; + Ok(status) } } @@ -109,7 +82,10 @@ mod test { #![allow(clippy::unchecked_duration_subtraction)] //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> use super::*; - use crate::circuit::sendme::{StreamRecvWindow, StreamSendWindow}; + use crate::{ + circuit::sendme::{StreamRecvWindow, StreamSendWindow}, + stream::DataCmdChecker, + }; use rand::{CryptoRng, Rng}; use tor_basic_utils::test_rng::testing_rng; use tor_cell::relaycell::{ @@ -132,7 +108,7 @@ mod test { let mut sendw = StreamSendWindow::new(101); sendw.take(&())?; // Make sure that it will accept one sendme. - let mut hs = HalfStream::new(sendw, StreamRecvWindow::new(20), true); + let mut hs = HalfStream::new(sendw, StreamRecvWindow::new(20), DataCmdChecker::new_any()); // one sendme is fine let m = msg::Sendme::new_empty(); @@ -152,7 +128,11 @@ mod test { } fn hs_new() -> HalfStream { - HalfStream::new(StreamSendWindow::new(20), StreamRecvWindow::new(20), true) + HalfStream::new( + StreamSendWindow::new(20), + StreamRecvWindow::new(20), + DataCmdChecker::new_any(), + ) } #[test] @@ -160,6 +140,10 @@ mod test { let mut hs = hs_new(); let mut rng = testing_rng(); + // we didn't give a connected cell during setup, so do it now. + hs.handle_msg(to_unparsed(&mut rng, msg::Connected::new_empty().into())) + .unwrap(); + // 20 data cells are okay. let m = msg::Data::new(&b"this offer is unrepeatable"[..]).unwrap(); for _ in 0_u8..20 { @@ -193,16 +177,26 @@ mod test { .handle_msg(to_unparsed(&mut rng, m.clone().into())) .is_err()); - // If we try that again with connected_ok == false, we won't + // If we try that again _after getting a connected_, // accept any. - let mut hs = HalfStream::new(StreamSendWindow::new(20), StreamRecvWindow::new(20), false); + let mut cmd_checker = DataCmdChecker::new_any(); + { + cmd_checker + .check_msg(&to_unparsed(&mut rng, msg::Connected::new_empty().into())) + .unwrap(); + } + let mut hs = HalfStream::new( + StreamSendWindow::new(20), + StreamRecvWindow::new(20), + cmd_checker, + ); let e = hs .handle_msg(to_unparsed(&mut rng, m.into())) .err() .unwrap(); assert_eq!( format!("{}", e), - "Circuit protocol violation: Bad CONNECTED cell on a closed stream!" + "Stream protocol violation: Received CONNECTED twice on a stream." ); } @@ -217,7 +211,7 @@ mod test { .unwrap(); assert_eq!( format!("{}", e), - "Unable to parse message on half-closed stream" + "Stream protocol violation: Unexpected EXTENDED2 on a data stream!" ); } } diff --git a/crates/tor-proto/src/circuit/reactor.rs b/crates/tor-proto/src/circuit/reactor.rs index 6ece08ed5..87040a79f 100644 --- a/crates/tor-proto/src/circuit/reactor.rs +++ b/crates/tor-proto/src/circuit/reactor.rs @@ -1,5 +1,20 @@ //! Code to handle incoming cells on a circuit. -use super::halfstream::HalfStreamStatus; +//! +//! ## On message validation +//! +//! There are three steps for validating an incoming message on a stream: +//! +//! 1. Is the message contextually appropriate? (e.g., no more than one +//! `CONNECTED` message per stream.) This is handled by calling +//! [`CmdChecker::check_msg`](crate::stream::CmdChecker::check_msg). +//! 2. Does the message comply with flow-control rules? (e.g., no more data than +//! we've gotten SENDMEs for.) For open streams, the stream itself handles +//! this; for half-closed streams, the reactor handles it using the +//! `halfstream` module. +//! 3. Does the message have an acceptable command type, and is the message +//! well-formed? For open streams, the streams themselves handle this check. +//! For half-closed streams, the reactor handles it by calling +//! `consume_checked_msg()`. use super::streammap::{ShouldSendEnd, StreamEnt}; use crate::circuit::celltypes::{ClientCircChanMsg, CreateResponse}; use crate::circuit::unique_id::UniqId; @@ -10,6 +25,7 @@ use crate::crypto::cell::{ ClientLayer, CryptInit, HopNum, InboundClientCrypt, InboundClientLayer, OutboundClientCrypt, OutboundClientLayer, RelayCellBody, Tor1RelayCrypto, }; +use crate::stream::{AnyCmdChecker, StreamStatus}; use crate::util::err::{ChannelClosed, ReactorError}; use crate::{Error, Result}; use std::collections::VecDeque; @@ -124,6 +140,8 @@ pub(super) enum CtrlMsg { rx: mpsc::Receiver<AnyRelayMsg>, /// Oneshot channel to notify on completion, with the allocated stream ID. done: ReactorResultChannel<StreamId>, + /// A `CmdChecker` to keep track of which message types are acceptable. + cmd_checker: AnyCmdChecker, }, /// Send a SENDME cell (used to ask for more data to be sent) on the given stream. SendSendme { @@ -1104,8 +1122,9 @@ impl Reactor { sender, rx, done, + cmd_checker, } => { - let ret = self.begin_stream(cx, hop_num, message, sender, rx); + let ret = self.begin_stream(cx, hop_num, message, sender, rx, cmd_checker); let _ = done.send(ret); // don't care if sender goes away } CtrlMsg::SendSendme { stream_id, hop_num } => { @@ -1169,12 +1188,13 @@ impl Reactor { message: AnyRelayMsg, sender: mpsc::Sender<UnparsedRelayCell>, rx: mpsc::Receiver<AnyRelayMsg>, + cmd_checker: AnyCmdChecker, ) -> Result<StreamId> { let hop = self .hop_mut(hopnum) .ok_or_else(|| Error::from(internal!("No such hop {:?}", hopnum)))?; let send_window = StreamSendWindow::new(SEND_WINDOW_INIT); - let r = hop.map.add_ent(sender, rx, send_window)?; + let r = hop.map.add_ent(sender, rx, send_window, cmd_checker)?; let cell = AnyRelayCell::new(r, message); self.send_relay_cell(cx, hopnum, false, cell)?; Ok(r) @@ -1310,7 +1330,7 @@ impl Reactor { sink, send_window, dropped, - ref mut received_connected, + cmd_checker, .. }) => { // The stream for this message exists, and is open. @@ -1327,18 +1347,7 @@ impl Reactor { return Ok(CellStatus::Continue); } - if msg.cmd() == RelayCmd::CONNECTED { - // Remember that we've received a Connected cell, and can't get another, - // even if we become a HalfStream. (This rule is enforced separately at - // DataStreamReader.) - - // TODO: This is problematic; see #774. - *received_connected = true; - } - - // Remember whether this was an end cell: if so we should - // close the stream. - let is_end_cell = msg.cmd() == RelayCmd::END; + let message_closes_stream = cmd_checker.check_msg(&msg)? == StreamStatus::Closed; if let Err(e) = sink.try_send(msg) { if e.is_full() { @@ -1357,16 +1366,16 @@ impl Reactor { *dropped += 1; } } - if is_end_cell { - hop.map.end_received(streamid)?; + if message_closes_stream { + hop.map.ending_msg_received(streamid)?; } } Some(StreamEnt::EndSent(halfstream)) => { // We sent an end but maybe the other side hasn't heard. match halfstream.handle_msg(msg)? { - HalfStreamStatus::Open => {} - HalfStreamStatus::Closed => hop.map.end_received(streamid)?, + StreamStatus::Open => {} + StreamStatus::Closed => hop.map.ending_msg_received(streamid)?, } } _ => { diff --git a/crates/tor-proto/src/circuit/streammap.rs b/crates/tor-proto/src/circuit/streammap.rs index 87245e41a..3b2b36c3a 100644 --- a/crates/tor-proto/src/circuit/streammap.rs +++ b/crates/tor-proto/src/circuit/streammap.rs @@ -2,6 +2,7 @@ use crate::circuit::halfstream::HalfStream; use crate::circuit::sendme; +use crate::stream::AnyCmdChecker; use crate::{Error, Result}; use tor_cell::relaycell::UnparsedRelayCell; /// Mapping from stream ID to streams. @@ -33,9 +34,8 @@ pub(super) enum StreamEnt { /// Number of cells dropped due to the stream disappearing before we can /// transform this into an `EndSent`. dropped: u16, - /// True iff we've received a CONNECTED cell on this stream. - /// (This is redundant with `DataStreamReader::connected`.) - received_connected: bool, + /// A `CmdChecker` used to tell whether cells on this stream are valid. + cmd_checker: AnyCmdChecker, }, /// A stream for which we have received an END cell, but not yet /// had the stream object get dropped. @@ -109,17 +109,14 @@ impl StreamMap { sink: mpsc::Sender<UnparsedRelayCell>, rx: mpsc::Receiver<AnyRelayMsg>, send_window: sendme::StreamSendWindow, + cmd_checker: AnyCmdChecker, ) -> Result<StreamId> { let stream_ent = StreamEnt::Open { sink, rx, send_window, dropped: 0, - // TODO: This is true for all streams at this point, but it is - // problematic to accept even one CONNECTED for RESOLVE/RESOLVED - // streams, and will become more so once UDP streams are - // implemented. This is #774. - received_connected: false, + cmd_checker, }; // This "65536" seems too aggressive, but it's what tor does. // @@ -146,10 +143,11 @@ impl StreamMap { self.m.get_mut(&id) } - /// Note that we received an END cell on the stream with `id`. + /// Note that we received an END message (or other message indicating the end of + /// the stream) on the stream with `id`. /// /// Returns true if there was really a stream there. - pub(super) fn end_received(&mut self, id: StreamId) -> Result<()> { + pub(super) fn ending_msg_received(&mut self, id: StreamId) -> Result<()> { // Check the hashmap for the right stream. Bail if not found. // Also keep the hashmap handle so that we can do more efficient inserts/removals let mut stream_entry = match self.m.entry(id) { @@ -194,7 +192,7 @@ impl StreamMap { StreamEnt::Open { send_window, dropped, - received_connected, + cmd_checker, // notably absent: the channels for sink and stream, which will get dropped and // closed (meaning reads/writes from/to this stream will now fail) .. @@ -205,9 +203,7 @@ impl StreamMap { let mut recv_window = StreamRecvWindow::new(RECV_WINDOW_INIT); recv_window.decrement_n(dropped)?; // TODO: would be nice to avoid new_ref. - // If we haven't gotten a CONNECTED already, we accept one on the half-stream. - let connected_ok = !received_connected; - let halfstream = HalfStream::new(send_window, recv_window, connected_ok); + let halfstream = HalfStream::new(send_window, recv_window, cmd_checker); self.m.insert(id, StreamEnt::EndSent(halfstream)); Ok(ShouldSendEnd::Send) } @@ -234,7 +230,7 @@ mod test { #![allow(clippy::unchecked_duration_subtraction)] //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> use super::*; - use crate::circuit::sendme::StreamSendWindow; + use crate::{circuit::sendme::StreamSendWindow, stream::DataCmdChecker}; #[test] fn streammap_basics() -> Result<()> { @@ -246,7 +242,12 @@ mod test { for _ in 0..128 { let (sink, _) = mpsc::channel(128); let (_, rx) = mpsc::channel(2); - let id = map.add_ent(sink, rx, StreamSendWindow::new(500))?; + let id = map.add_ent( + sink, + rx, + StreamSendWindow::new(500), + DataCmdChecker::new_any(), + )?; let expect_id: StreamId = next_id.into(); assert_eq!(expect_id, id); next_id = next_id.wrapping_add(1); @@ -262,10 +263,10 @@ mod test { assert!(map.get_mut(nonesuch_id).is_none()); // Test end_received - assert!(map.end_received(nonesuch_id).is_err()); - assert!(map.end_received(ids[1]).is_ok()); + assert!(map.ending_msg_received(nonesuch_id).is_err()); + assert!(map.ending_msg_received(ids[1]).is_ok()); assert!(matches!(map.get_mut(ids[1]), Some(StreamEnt::EndReceived))); - assert!(map.end_received(ids[1]).is_err()); + assert!(map.ending_msg_received(ids[1]).is_err()); // Test terminate assert!(map.terminate(nonesuch_id).is_err()); @@ -275,7 +276,7 @@ mod test { assert!(matches!(map.get_mut(ids[1]), None)); // Try receiving an end after a terminate. - assert!(map.end_received(ids[2]).is_ok()); + assert!(map.ending_msg_received(ids[2]).is_ok()); assert!(matches!(map.get_mut(ids[2]), None)); Ok(()) diff --git a/crates/tor-proto/src/stream.rs b/crates/tor-proto/src/stream.rs index 6ccab73e8..f4f3ab55c 100644 --- a/crates/tor-proto/src/stream.rs +++ b/crates/tor-proto/src/stream.rs @@ -9,6 +9,7 @@ //! //! There is no fairness, rate-limiting, or flow control. +mod cmdcheck; mod data; #[cfg(feature = "onion-service")] mod incoming; @@ -16,6 +17,7 @@ mod params; mod raw; mod resolve; +pub(crate) use cmdcheck::{AnyCmdChecker, CmdChecker, StreamStatus}; pub use data::{DataReader, DataStream, DataWriter}; #[cfg(feature = "onion-service")] #[cfg_attr(docsrs, doc(cfg(feature = "onion-service")))] @@ -23,5 +25,6 @@ pub use incoming::{IncomingStream, IncomingStreamRequest}; pub use params::StreamParameters; pub use raw::StreamReader; pub use resolve::ResolveStream; +pub(crate) use {data::DataCmdChecker, resolve::ResolveCmdChecker}; pub use tor_cell::relaycell::msg::IpVersionPreference; diff --git a/crates/tor-proto/src/stream/cmdcheck.rs b/crates/tor-proto/src/stream/cmdcheck.rs new file mode 100644 index 000000000..76808e0df --- /dev/null +++ b/crates/tor-proto/src/stream/cmdcheck.rs @@ -0,0 +1,55 @@ +//! Declare a "command checker" trait that checks whether a given relay message +//! is acceptable on a given stream. + +use tor_cell::relaycell::UnparsedRelayCell; + +use crate::Result; + +/// A value returned by CmdChecker on success. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum StreamStatus { + /// The stream is still open. + Open, + /// The stream has been closed successfully; any further messages received + /// on this stream would be a protocol violation, which should cause + /// us to close the circuit. + Closed, +} + +/// An object that checks incoming commands before they are sent to a stream. +/// +/// These checks are called from the circuit reactor code, which runs in its own +/// task. The reactor code continues calling these checks we have sent our own +/// END cell on the stream. See `crate::circuit::halfstream` for more +/// information. +/// +/// NOTE: The checking DOES NOT take SENDME messages into account; those are +/// handled separately. Neither of the methods on this trait will ever be +/// passed a SENDME message. +/// +/// See [`circuit::reactor`](crate::circuit::reactor) for more information on +/// how these checks relate to other checks performed on incoming messages. +pub(crate) trait CmdChecker: std::fmt::Debug { + /// Look at a message `msg` and decide whether it can be handled on this + /// stream. + /// + /// If `msg` is invalid, return an error, indicating that the protocol has + /// been violated and the corresponding circuit should be closed. + /// + /// If `msg` is invalid, update the state of this checker, and return a + /// `StreamStatus` indicating whether the last message closed. + fn check_msg(&mut self, msg: &UnparsedRelayCell) -> Result<StreamStatus>; + + /// Consume `msg` and make sure it can be parsed correctly. + /// + /// This is an additional check, beyond check_msg(), performed for half-open + /// streams. It should only be called if check_msg() succeeds. It shouldn't + /// be called on open streams: for those, the stream itself parses the message + /// and consumes it. + fn consume_checked_msg(&mut self, msg: UnparsedRelayCell) -> Result<()>; +} + +/// Type alias for a CmdChecker of unspecified type. +// +// TODO: Someday we might turn this into an enum if we decide it's beneficial. +pub(crate) type AnyCmdChecker = Box<dyn CmdChecker + Send + 'static>; diff --git a/crates/tor-proto/src/stream/data.rs b/crates/tor-proto/src/stream/data.rs index 1d460e801..24a097713 100644 --- a/crates/tor-proto/src/stream/data.rs +++ b/crates/tor-proto/src/stream/data.rs @@ -3,6 +3,7 @@ use crate::{Error, Result}; use tor_cell::relaycell::msg::EndReason; +use tor_cell::relaycell::RelayCmd; use futures::io::{AsyncRead, AsyncWrite}; use futures::task::{Context, Poll}; @@ -28,6 +29,8 @@ use tor_basic_utils::skip_fmt; use tor_cell::relaycell::msg::Data; use tor_error::internal; +use super::AnyCmdChecker; + /// An anonymized stream over the Tor network. /// /// For most purposes, you can think of this type as an anonymized @@ -657,3 +660,60 @@ impl DataReaderImpl { } } } + +/// A `CmdChecker` that enforces invariants for outbound data streams. +#[derive(Debug, Default)] +pub(crate) struct DataCmdChecker { + /// True if we have received a CONNECTED message on this stream. + connected_received: bool, +} + +impl super::CmdChecker for DataCmdChecker { + fn check_msg( + &mut self, + msg: &tor_cell::relaycell::UnparsedRelayCell, + ) -> Result<super::StreamStatus> { + use super::StreamStatus::*; + match msg.cmd() { + RelayCmd::CONNECTED => { + if self.connected_received { + Err(Error::StreamProto( + "Received CONNECTED twice on a stream.".into(), + )) + } else { + self.connected_received = true; + Ok(Open) + } + } + RelayCmd::DATA => { + if self.connected_received { + Ok(Open) + } else { + Err(Error::StreamProto( + "Received DATA before CONNECTED on a stream".into(), + )) + } + } + RelayCmd::END => Ok(Closed), + _ => Err(Error::StreamProto(format!( + "Unexpected {} on a data stream!", + msg.cmd() + ))), + } + } + + fn consume_checked_msg(&mut self, msg: tor_cell::relaycell::UnparsedRelayCell) -> Result<()> { + let _ = msg + .decode::<DataStreamMsg>() + .map_err(|err| Error::from_bytes_err(err, "cell on half-closed stream"))?; + Ok(()) + } +} + +impl DataCmdChecker { + /// Return a new boxed `DataCmdChecker` in a state suitable for a newly + /// constructed connection. + pub(crate) fn new_any() -> AnyCmdChecker { + Box::<Self>::default() + } +} diff --git a/crates/tor-proto/src/stream/resolve.rs b/crates/tor-proto/src/stream/resolve.rs index e81823e3d..f5c84b155 100644 --- a/crates/tor-proto/src/stream/resolve.rs +++ b/crates/tor-proto/src/stream/resolve.rs @@ -3,8 +3,11 @@ use crate::stream::StreamReader; use crate::{Error, Result}; use tor_cell::relaycell::msg::Resolved; +use tor_cell::relaycell::RelayCmd; use tor_cell::restricted_msg; +use super::AnyCmdChecker; + /// A ResolveStream represents a pending DNS request made with a RESOLVE /// cell. pub struct ResolveStream { @@ -46,3 +49,40 @@ impl ResolveStream { } } } + +/// A `CmdChecker` that enforces correctness for incoming commands on an +/// outbound resolve stream. +#[derive(Debug, Default)] +pub(crate) struct ResolveCmdChecker {} + +impl super::CmdChecker for ResolveCmdChecker { + fn check_msg( + &mut self, + msg: &tor_cell::relaycell::UnparsedRelayCell, + ) -> Result<super::StreamStatus> { + use super::StreamStatus::Closed; + match msg.cmd() { + RelayCmd::RESOLVED => Ok(Closed), + RelayCmd::END => Ok(Closed), + _ => Err(Error::StreamProto(format!( + "Unexpected {} on resolve stream", + msg.cmd() + ))), + } + } + + fn consume_checked_msg(&mut self, msg: tor_cell::relaycell::UnparsedRelayCell) -> Result<()> { + let _ = msg + .decode::<ResolveResponseMsg>() + .map_err(|err| Error::from_bytes_err(err, "message on resolve stream."))?; + Ok(()) + } +} + +impl ResolveCmdChecker { + /// Return a new boxed `DataCmdChecker` in a state suitable for a newly + /// constructed connection. + pub(crate) fn new_any() -> AnyCmdChecker { + Box::<Self>::default() + } +} |
