cell: Move UDP to its own module and feature gate it

Related to #463

Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
David Goulet 2022-06-01 10:38:50 -04:00
parent 8cb012ed78
commit 8fd6541985
6 changed files with 61 additions and 314 deletions

View File

@ -11,6 +11,13 @@ keywords = ["tor", "arti", "protocol"]
categories = ["parser-implementations", "network-programming"]
repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = []
# Enable experimental UDP support.
experimental-udp = []
# Enable testing only API
testing = [ "experimental-udp" ]
[dependencies]
arrayref = "0.3"
bitflags = "1"

View File

@ -10,6 +10,8 @@ use caret::caret_int;
use rand::{CryptoRng, Rng};
pub mod msg;
#[cfg(feature = "experimental-udp")]
pub mod udp;
caret_int! {
/// A command that identifies the type of a relay cell
@ -45,6 +47,8 @@ caret_int! {
/// Reply to an EXTEND2 cell.
EXTENDED2 = 15,
/// NOTE: UDP command are reserved but only used with experimental-udp feature
/// UDP: Start of a stream
CONNECT_UDP = 16,
/// UDP: Acknowledge a CONNECT_UDP. Stream is open.
@ -99,10 +103,11 @@ impl RelayCmd {
| RelayCmd::CONNECTED
| RelayCmd::RESOLVE
| RelayCmd::RESOLVED
| RelayCmd::BEGIN_DIR
| RelayCmd::CONNECT_UDP
| RelayCmd::CONNECTED_UDP
| RelayCmd::DATAGRAM => StreamIdReq::WantNonZero,
| RelayCmd::BEGIN_DIR => StreamIdReq::WantNonZero,
#[cfg(feature = "experimental-udp")]
RelayCmd::CONNECT_UDP | RelayCmd::CONNECTED_UDP | RelayCmd::DATAGRAM => {
StreamIdReq::WantNonZero
}
RelayCmd::EXTEND
| RelayCmd::EXTENDED
| RelayCmd::TRUNCATE

View File

@ -9,8 +9,7 @@ use crate::chancell::CELL_DATA_LEN;
use caret::caret_int;
use educe::Educe;
use std::fmt::Write;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use std::net::{IpAddr, Ipv4Addr};
use tor_bytes::{Error, Result};
use tor_bytes::{Readable, Reader, Writeable, Writer};
use tor_linkspec::LinkSpec;
@ -18,6 +17,9 @@ use tor_llcrypto::pk::rsa::RsaIdentity;
use bitflags::bitflags;
#[cfg(feature = "experimental-udp")]
use super::udp;
/// A single parsed relay message, sent or received along a circuit
#[derive(Debug, Clone)]
#[non_exhaustive]
@ -53,11 +55,14 @@ pub enum RelayMsg {
/// Start a directory stream
BeginDir,
/// Start a UDP stream.
ConnectUdp(ConnectUdp),
#[cfg(feature = "experimental-udp")]
ConnectUdp(udp::ConnectUdp),
/// Successful response to a ConnectUdp message
ConnectedUdp(ConnectedUdp),
#[cfg(feature = "experimental-udp")]
ConnectedUdp(udp::ConnectedUdp),
/// UDP stream data
Datagram(Datagram),
#[cfg(feature = "experimental-udp")]
Datagram(udp::Datagram),
/// An unrecognized command.
Unrecognized(Unrecognized),
@ -100,8 +105,11 @@ impl RelayMsg {
Resolve(_) => RelayCmd::RESOLVE,
Resolved(_) => RelayCmd::RESOLVED,
BeginDir => RelayCmd::BEGIN_DIR,
#[cfg(feature = "experimental-udp")]
ConnectUdp(_) => RelayCmd::CONNECT_UDP,
#[cfg(feature = "experimental-udp")]
ConnectedUdp(_) => RelayCmd::CONNECTED_UDP,
#[cfg(feature = "experimental-udp")]
Datagram(_) => RelayCmd::DATAGRAM,
Unrecognized(u) => u.cmd(),
}
@ -124,9 +132,14 @@ impl RelayMsg {
RelayCmd::RESOLVE => RelayMsg::Resolve(Resolve::decode_from_reader(r)?),
RelayCmd::RESOLVED => RelayMsg::Resolved(Resolved::decode_from_reader(r)?),
RelayCmd::BEGIN_DIR => RelayMsg::BeginDir,
RelayCmd::CONNECT_UDP => RelayMsg::ConnectUdp(ConnectUdp::decode_from_reader(r)?),
RelayCmd::CONNECTED_UDP => RelayMsg::ConnectedUdp(ConnectedUdp::decode_from_reader(r)?),
RelayCmd::DATAGRAM => RelayMsg::Datagram(Datagram::decode_from_reader(r)?),
#[cfg(feature = "experimental-udp")]
RelayCmd::CONNECT_UDP => RelayMsg::ConnectUdp(udp::ConnectUdp::decode_from_reader(r)?),
#[cfg(feature = "experimental-udp")]
RelayCmd::CONNECTED_UDP => {
RelayMsg::ConnectedUdp(udp::ConnectedUdp::decode_from_reader(r)?)
}
#[cfg(feature = "experimental-udp")]
RelayCmd::DATAGRAM => RelayMsg::Datagram(udp::Datagram::decode_from_reader(r)?),
_ => RelayMsg::Unrecognized(Unrecognized::decode_with_cmd(c, r)?),
})
}
@ -149,8 +162,11 @@ impl RelayMsg {
Resolve(b) => b.encode_onto(w),
Resolved(b) => b.encode_onto(w),
BeginDir => (),
#[cfg(feature = "experimental-udp")]
ConnectUdp(b) => b.encode_onto(w),
#[cfg(feature = "experimental-udp")]
ConnectedUdp(b) => b.encode_onto(w),
#[cfg(feature = "experimental-udp")]
Datagram(b) => b.encode_onto(w),
Unrecognized(b) => b.encode_onto(w),
}
@ -1132,297 +1148,6 @@ impl Body for Resolved {
}
}
/// Address contained in a ConnectUdp and ConnectedUdp cell which can
/// represent a hostname, IPv4 or IPv6.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Address {
/// Hostname
Hostname(Vec<u8>),
/// IP version 4 address
Ipv4(Ipv4Addr),
/// IP version 6 address
Ipv6(Ipv6Addr),
}
/// Indicates the payload is a hostname.
const T_HOSTNAME: u8 = 0x01;
/// Indicates the payload is an IPv4.
const T_IPV4: u8 = 0x04;
/// Indicates the payload is an IPv6.
const T_IPV6: u8 = 0x06;
/// Maximum length of an Address::Hostname. It must fit in a u8 minus the nul term byte.
const MAX_HOSTNAME_LEN: usize = (u8::MAX - 1) as usize;
impl Address {
/// Return true iff this is a Hostname.
pub fn is_hostname(&self) -> bool {
matches!(self, Address::Hostname(_))
}
/// Return the cell ABI address type value.
fn abi_addr_type(&self) -> u8 {
match self {
Address::Hostname(_) => T_HOSTNAME,
Address::Ipv4(_) => T_IPV4,
Address::Ipv6(_) => T_IPV6,
}
}
/// Return the cell ABI address length. Note that the Hostname has an extra byte added to its
/// length due to the nulterm character needed for encoding.
fn abi_addr_len(&self) -> u8 {
match self {
// Add nulterm byte to length. Length can't be above MAX_HOSTNAME_LEN.
Address::Hostname(h) => (h.len() + 1).try_into().expect("Address hostname too long"),
Address::Ipv4(_) => 4,
Address::Ipv6(_) => 16,
}
}
}
impl Readable for Address {
fn take_from(r: &mut Reader<'_>) -> Result<Self> {
let addr_type = r.take_u8()?;
let addr_len = r.take_u8()? as usize;
Ok(match addr_type {
T_HOSTNAME => {
let h = r.take_until(0)?;
if h.len() != (addr_len - 1) {
return Err(Error::BadMessage(
"Address length doesn't match nulterm hostname",
));
}
Self::Hostname(h.into())
}
T_IPV4 => Self::Ipv4(r.extract()?),
T_IPV6 => Self::Ipv6(r.extract()?),
_ => return Err(Error::BadMessage("Unknown address type")),
})
}
}
impl Writeable for Address {
fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) {
// Address type.
w.write_u8(self.abi_addr_type());
// Address length.
w.write_u8(self.abi_addr_len());
match self {
Address::Hostname(h) => {
w.write_all(&h[..]);
w.write_zeros(1); // Nul terminating byte.
}
Address::Ipv4(ip) => w.write(ip),
Address::Ipv6(ip) => w.write(ip),
}
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if s.is_empty() {
return Err(Error::BadMessage("Empty address"));
}
if !s.is_ascii() {
return Err(Error::BadMessage("Non-ascii address"));
}
if let Ok(ipv4) = Ipv4Addr::from_str(s) {
Ok(Self::Ipv4(ipv4))
} else if let Ok(ipv6) = Ipv6Addr::from_str(s) {
Ok(Self::Ipv6(ipv6))
} else {
if s.len() > MAX_HOSTNAME_LEN {
return Err(Error::BadMessage("Hostname too long"));
}
let mut addr = s.to_string();
addr.make_ascii_lowercase();
Ok(Self::Hostname(addr.into_bytes()))
}
}
}
impl From<IpAddr> for Address {
fn from(ip: IpAddr) -> Self {
match ip {
IpAddr::V4(ip) => Address::Ipv4(ip),
IpAddr::V6(ip) => Address::Ipv6(ip),
}
}
}
/// A ConnectUdp message creates a new UDP data stream.
///
/// Upon receiving a ConnectUdp message, a relay tries to connect to the given address with the UDP
/// procotol if the xit policy permits.
///
/// If the exit decides to reject the message, or if the UDP connection fails, the exit should send
/// an End message.
///
/// Clients should reject these messages.
#[derive(Debug, Clone)]
pub struct ConnectUdp {
/// Same as Begin flags.
flags: BeginFlags,
/// Address to connect to. Can be Hostname, IPv4 or IPv6.
addr: Address,
/// Target port
port: u16,
}
impl ConnectUdp {
/// Construct a new ConnectUdp cell
pub fn new<F>(addr: &str, port: u16, flags: F) -> crate::Result<Self>
where
F: Into<BeginFlags>,
{
Ok(Self {
addr: Address::from_str(addr)?,
port,
flags: flags.into(),
})
}
}
impl Body for ConnectUdp {
fn into_message(self) -> RelayMsg {
RelayMsg::ConnectUdp(self)
}
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let flags = r.take_u32()?;
let addr = r.extract()?;
let port = r.take_u16()?;
Ok(Self {
flags: flags.into(),
addr,
port,
})
}
fn encode_onto(self, w: &mut Vec<u8>) {
w.write_u32(self.flags.bits());
w.write(&self.addr);
w.write_u16(self.port);
}
}
/// A ConnectedUdp cell sent in response to a ConnectUdp.
#[derive(Debug, Clone)]
pub struct ConnectedUdp {
/// The address that the relay has bound locally of a ConnectUdp. Note
/// that this might not be the relay address from the descriptor.
our_address: Address,
/// The address that the stream is connected to.
their_address: Address,
}
impl ConnectedUdp {
/// Construct a new ConnectedUdp cell.
pub fn new(our: IpAddr, their: IpAddr) -> Result<Self> {
Ok(Self {
our_address: our.into(),
their_address: their.into(),
})
}
}
impl Body for ConnectedUdp {
fn into_message(self) -> RelayMsg {
RelayMsg::ConnectedUdp(self)
}
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
let our_address: Address = r.extract()?;
if our_address.is_hostname() {
return Err(Error::BadMessage("Our address is a Hostname"));
}
let their_address: Address = r.extract()?;
if their_address.is_hostname() {
return Err(Error::BadMessage("Their address is a Hostname"));
}
Ok(Self {
our_address,
their_address,
})
}
fn encode_onto(self, w: &mut Vec<u8>) {
w.write(&self.our_address);
w.write(&self.their_address);
}
}
/// A Datagram message represents data sent along a UDP stream.
///
/// Upon receiving a Datagram message for a live stream, the client or
/// exit sends that data onto the associated UDP connection.
///
/// These messages hold between 1 and [Datagram::MAXLEN] bytes of data each.
#[derive(Debug, Clone)]
pub struct Datagram {
/// Contents of the cell, to be sent on a specific stream
body: Vec<u8>,
}
impl Datagram {
/// The longest allowable body length for a single data cell.
pub const MAXLEN: usize = CELL_DATA_LEN - 11;
/// Construct a new data cell.
///
/// Returns an error if `inp` is longer than [`Data::MAXLEN`] bytes.
pub fn new(inp: &[u8]) -> crate::Result<Self> {
if inp.len() > Data::MAXLEN {
return Err(crate::Error::CantEncode);
}
Ok(Self::new_unchecked(inp.into()))
}
/// Construct a new cell from a provided vector of bytes.
///
/// The vector _must_ have fewer than [`Data::MAXLEN`] bytes.
fn new_unchecked(body: Vec<u8>) -> Self {
Self { body }
}
}
impl From<Datagram> for Vec<u8> {
fn from(data: Datagram) -> Vec<u8> {
data.body
}
}
impl AsRef<[u8]> for Datagram {
fn as_ref(&self) -> &[u8] {
&self.body[..]
}
}
impl Body for Datagram {
fn into_message(self) -> RelayMsg {
RelayMsg::Datagram(self)
}
fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
Ok(Datagram {
body: r.take(r.remaining())?.into(),
})
}
fn encode_onto(mut self, w: &mut Vec<u8>) {
w.append(&mut self.body);
}
}
/// A relay message that we didn't recognize
///
/// NOTE: Clients should generally reject these.

View File

@ -35,7 +35,7 @@ const MAX_HOSTNAME_LEN: usize = (u8::MAX - 1) as usize;
impl Address {
/// Return true iff this is a Hostname.
fn is_hostname(&self) -> bool {
pub fn is_hostname(&self) -> bool {
matches!(self, Address::Hostname(_))
}

View File

@ -1,12 +1,15 @@
// Tests for encoding/decoding relay messages into relay cell bodies.
use tor_bytes::Error;
use tor_cell::relaycell::{msg, msg::RelayMsg, RelayCell, RelayCmd, StreamId};
#[cfg(feature = "experimental-udp")]
use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use tor_bytes::Error;
use tor_cell::relaycell::{msg, msg::Address, msg::RelayMsg, RelayCell, RelayCmd, StreamId};
#[cfg(feature = "experimental-udp")]
use tor_cell::relaycell::udp::Address;
const CELL_BODY_LEN: usize = 509;
@ -120,6 +123,7 @@ fn test_streamid() {
assert!(!RelayCmd::EXTEND2.accepts_streamid_val(two));
}
#[cfg(feature = "experimental-udp")]
#[test]
fn test_address() {
// IPv4

View File

@ -8,10 +8,14 @@ use tor_cell::relaycell::{msg, RelayCmd};
use tor_llcrypto::pk::rsa::RsaIdentity;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::str::FromStr;
use hex_literal::hex;
#[cfg(feature = "experimental-udp")]
use std::str::FromStr;
#[cfg(feature = "experimental-udp")]
use tor_cell::relaycell::udp;
/// Decode `s`, a hexadecimal value that may have spaces in it.
///
/// Panic if the input is not valid hexadecimal
@ -475,6 +479,7 @@ fn test_data() {
assert_eq!(rest, &b[498..]);
}
#[cfg(feature = "experimental-udp")]
#[test]
fn test_connect_udp() {
let cmd = RelayCmd::CONNECT_UDP;
@ -485,7 +490,7 @@ fn test_connect_udp() {
cmd,
"00000000 01 0B
7269736575702E6E657400 01BB",
&msg::ConnectUdp::new("riseup.net", 443, 0).unwrap().into(),
&udp::ConnectUdp::new("riseup.net", 443, 0).unwrap().into(),
);
// Valid encoded message with flags. Generated by hand with python.
@ -493,7 +498,7 @@ fn test_connect_udp() {
cmd,
"00000003 01 0F
746F7270726F6A6563742E6F726700 0050",
&msg::ConnectUdp::new("torproject.org", 80, 3)
&udp::ConnectUdp::new("torproject.org", 80, 3)
.unwrap()
.into(),
);
@ -503,7 +508,7 @@ fn test_connect_udp() {
cmd,
"00000000 04 04
01020304 0050",
&msg::ConnectUdp::new("1.2.3.4", 80, 0).unwrap().into(),
&udp::ConnectUdp::new("1.2.3.4", 80, 0).unwrap().into(),
);
// Valid encoded message with IPv6
@ -511,7 +516,7 @@ fn test_connect_udp() {
cmd,
"00000000 06 10
26000001000200000000000000000004 0050",
&msg::ConnectUdp::new("2600:1:2::4", 80, 0).unwrap().into(),
&udp::ConnectUdp::new("2600:1:2::4", 80, 0).unwrap().into(),
);
// Invalid length for hostname.
@ -537,6 +542,7 @@ fn test_connect_udp() {
msg_error(cmd, "00000000 01 00 18167251 01BB", BytesError::Truncated);
}
#[cfg(feature = "experimental-udp")]
#[test]
fn test_connected_udp() {
let cmd = RelayCmd::CONNECTED_UDP;
@ -553,7 +559,7 @@ fn test_connected_udp() {
cmd,
"04 04 01020304
04 04 05060708",
&msg::ConnectedUdp::new(a_ipv4, b_ipv4).unwrap().into(),
&udp::ConnectedUdp::new(a_ipv4, b_ipv4).unwrap().into(),
);
// Valid encoded message. Generated by hand with python.
@ -561,7 +567,7 @@ fn test_connected_udp() {
cmd,
"06 10 26000000000000000000000000000001
06 10 27000000000000000000000000000001",
&msg::ConnectedUdp::new(a_ipv6, b_ipv6).unwrap().into(),
&udp::ConnectedUdp::new(a_ipv6, b_ipv6).unwrap().into(),
);
// Length doesn't match hostname length.