summaryrefslogtreecommitdiff
path: root/crates/tor-proto/src/stream
diff options
context:
space:
mode:
Diffstat (limited to 'crates/tor-proto/src/stream')
-rw-r--r--crates/tor-proto/src/stream/cmdcheck.rs55
-rw-r--r--crates/tor-proto/src/stream/data.rs60
-rw-r--r--crates/tor-proto/src/stream/resolve.rs40
3 files changed, 155 insertions, 0 deletions
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()
+ }
+}