tor_netdoc/doc/netstatus/
rs.rs

1//! Routerstatus-specific parts of networkstatus parsing.
2//!
3//! This is a private module; relevant pieces are re-exported by its
4//! parent.
5
6#[cfg(feature = "build_docs")]
7pub(crate) mod build;
8mod md;
9#[cfg(feature = "ns_consensus")]
10mod ns;
11
12use super::{ConsensusFlavor, NetstatusKwd, RelayFlags, RelayWeight};
13use crate::doc;
14use crate::parse::parser::Section;
15use crate::types::misc::*;
16use crate::types::version::TorVersion;
17use crate::util::intern::InternCache;
18use crate::{Error, NetdocErrorKind as EK, Result};
19use std::sync::Arc;
20use std::{net, time};
21
22use tor_llcrypto::pk::rsa::RsaIdentity;
23use tor_protover::Protocols;
24
25pub use md::MdConsensusRouterStatus;
26#[cfg(feature = "ns_consensus")]
27pub use ns::NsConsensusRouterStatus;
28
29/// Shared implementation of MdConsensusRouterStatus and NsConsensusRouterStatus.
30#[cfg_attr(
31    feature = "dangerous-expose-struct-fields",
32    visible::StructFields(pub),
33    visibility::make(pub),
34    non_exhaustive
35)]
36#[derive(Debug, Clone)]
37struct GenericRouterStatus<D> {
38    /// The nickname for this relay.
39    ///
40    /// Nicknames can be used for convenience purpose, but no more:
41    /// there is no mechanism to enforce their uniqueness.
42    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
43    nickname: Nickname,
44    /// Fingerprint of the old-style RSA identity for this relay.
45    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
46    identity: RsaIdentity,
47    /// A list of address:port values where this relay can be reached.
48    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
49    addrs: Vec<net::SocketAddr>,
50    /// Digest of the document for this relay.
51    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
52    doc_digest: D,
53    /// Flags applied by the authorities to this relay.
54    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
55    flags: RelayFlags,
56    /// Version of the software that this relay is running.
57    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
58    version: Option<Version>,
59    /// List of subprotocol versions supported by this relay.
60    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
61    protos: Arc<Protocols>,
62    /// Information about how to weight this relay when choosing a
63    /// relay at random.
64    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
65    weight: RelayWeight,
66}
67
68/// A version as presented in a router status.
69///
70/// This can either be a parsed Tor version, or an unparsed string.
71//
72// TODO: This might want to merge, at some point, with routerdesc::RelayPlatform.
73#[derive(Clone, Debug, Eq, PartialEq, Hash, derive_more::Display)]
74#[non_exhaustive]
75pub enum Version {
76    /// A Tor version
77    Tor(TorVersion),
78    /// A string we couldn't parse.
79    Other(Arc<str>),
80}
81
82/// A cache of unparsable version strings.
83///
84/// We use this because we expect there not to be very many distinct versions of
85/// relay software in existence.
86static OTHER_VERSION_CACHE: InternCache<str> = InternCache::new();
87
88impl std::str::FromStr for Version {
89    type Err = Error;
90
91    fn from_str(s: &str) -> Result<Self> {
92        let mut elts = s.splitn(3, ' ');
93        if elts.next() == Some("Tor") {
94            if let Some(Ok(v)) = elts.next().map(str::parse) {
95                return Ok(Version::Tor(v));
96            }
97        }
98
99        Ok(Version::Other(OTHER_VERSION_CACHE.intern_ref(s)))
100    }
101}
102
103/// Implement a set of accessor functions on a given routerstatus type.
104// TODO: These methods should probably become, in whole or in part,
105// methods on the RouterStatus trait.
106macro_rules! implement_accessors {
107    ($name:ident) => {
108        impl $name {
109            /// Return an iterator of ORPort addresses for this routerstatus
110            pub fn orport_addrs(&self) -> impl Iterator<Item = &net::SocketAddr> {
111                self.rs.addrs.iter()
112            }
113            /// Return the declared weight of this routerstatus in the directory.
114            pub fn weight(&self) -> &RelayWeight {
115                &self.rs.weight
116            }
117            /// Return the ORPort addresses of this routerstatus
118            pub fn addrs(&self) -> &[net::SocketAddr] {
119                &self.rs.addrs[..]
120            }
121            /// Return the protovers that this routerstatus says it implements.
122            pub fn protovers(&self) -> &Protocols {
123                &self.rs.protos
124            }
125            /// Return the nickname of this routerstatus.
126            pub fn nickname(&self) -> &str {
127                self.rs.nickname.as_str()
128            }
129            /// Return the relay flags of this routerstatus.
130            pub fn flags(&self) -> &RelayFlags {
131                &self.rs.flags
132            }
133            /// Return the version of this routerstatus.
134            pub fn version(&self) -> Option<&crate::doc::netstatus::rs::Version> {
135                self.rs.version.as_ref()
136            }
137            /// Return true if the ed25519 identity on this relay reflects a
138            /// true consensus among the authorities.
139            pub fn ed25519_id_is_usable(&self) -> bool {
140                !self.rs.flags.contains(RelayFlags::NO_ED_CONSENSUS)
141            }
142            /// Return true if this routerstatus is listed with the BadExit flag.
143            pub fn is_flagged_bad_exit(&self) -> bool {
144                self.rs.flags.contains(RelayFlags::BAD_EXIT)
145            }
146            /// Return true if this routerstatus is listed with the v2dir flag.
147            pub fn is_flagged_v2dir(&self) -> bool {
148                self.rs.flags.contains(RelayFlags::V2DIR)
149            }
150            /// Return true if this routerstatus is listed with the Exit flag.
151            pub fn is_flagged_exit(&self) -> bool {
152                self.rs.flags.contains(RelayFlags::EXIT)
153            }
154            /// Return true if this routerstatus is listed with the Guard flag.
155            pub fn is_flagged_guard(&self) -> bool {
156                self.rs.flags.contains(RelayFlags::GUARD)
157            }
158            /// Return true if this routerstatus is listed with the HSDir flag.
159            pub fn is_flagged_hsdir(&self) -> bool {
160                self.rs.flags.contains(RelayFlags::HSDIR)
161            }
162            /// Return true if this routerstatus is listed with the Stable flag.
163            pub fn is_flagged_stable(&self) -> bool {
164                self.rs.flags.contains(RelayFlags::STABLE)
165            }
166            /// Return true if this routerstatus is listed with the Fast flag.
167            pub fn is_flagged_fast(&self) -> bool {
168                self.rs.flags.contains(RelayFlags::FAST)
169            }
170            /// Return true if this routerstatus is listed with the MiddleOnly flag.
171            pub fn is_flagged_middle_only(&self) -> bool {
172                self.rs.flags.contains(RelayFlags::MIDDLE_ONLY)
173            }
174        }
175    };
176}
177
178// Make the macro public in the crate.
179pub(crate) use implement_accessors;
180
181/// Helper to decode a document digest in the format in which it
182/// appears in a given kind of routerstatus.
183#[cfg_attr(feature = "dangerous-expose-struct-fields", visibility::make(pub))]
184trait FromRsString: Sized {
185    /// Try to decode the given object.
186    fn decode(s: &str) -> Result<Self>;
187}
188
189impl<D> GenericRouterStatus<D>
190where
191    D: FromRsString,
192{
193    /// Parse a generic routerstatus from a section.
194    ///
195    /// Requires that the section obeys the right SectionRules,
196    /// matching `consensus_flavor`.
197    fn from_section(
198        sec: &Section<'_, NetstatusKwd>,
199        consensus_flavor: ConsensusFlavor,
200    ) -> Result<GenericRouterStatus<D>> {
201        use NetstatusKwd::*;
202        // R line
203        let r_item = sec.required(RS_R)?;
204        let nickname = r_item.required_arg(0)?.parse()?;
205        let ident = r_item.required_arg(1)?.parse::<B64>()?;
206        let identity = RsaIdentity::from_bytes(ident.as_bytes()).ok_or_else(|| {
207            EK::BadArgument
208                .at_pos(r_item.pos())
209                .with_msg("Wrong identity length")
210        })?;
211        // Fields to skip in the "r" line.
212        let n_skip = match consensus_flavor {
213            ConsensusFlavor::Microdesc => 0,
214            ConsensusFlavor::Ns => 1,
215        };
216        // We check that the published time is well-formed, but we never use it
217        // for anything in a consensus document.
218        let _ignore_published: time::SystemTime = {
219            // TODO: It's annoying to have to do this allocation, since we
220            // already have a slice that contains both of these arguments.
221            // Instead, we could get a slice of arguments: we'd have to add
222            // a feature for that.
223            let mut p = r_item.required_arg(2 + n_skip)?.to_string();
224            p.push(' ');
225            p.push_str(r_item.required_arg(3 + n_skip)?);
226            p.parse::<Iso8601TimeSp>()?.into()
227        };
228        let ipv4addr = r_item.required_arg(4 + n_skip)?.parse::<net::Ipv4Addr>()?;
229        let or_port = r_item.required_arg(5 + n_skip)?.parse::<u16>()?;
230        let _ = r_item.required_arg(6 + n_skip)?.parse::<u16>()?;
231
232        // main address and A lines.
233        let a_items = sec.slice(RS_A);
234        let mut addrs = Vec::with_capacity(1 + a_items.len());
235        addrs.push(net::SocketAddr::V4(net::SocketAddrV4::new(
236            ipv4addr, or_port,
237        )));
238        for a_item in a_items {
239            addrs.push(a_item.required_arg(0)?.parse::<net::SocketAddr>()?);
240        }
241
242        // S line
243        let flags = RelayFlags::from_item(sec.required(RS_S)?)?;
244
245        // V line
246        let version = sec.maybe(RS_V).args_as_str().map(str::parse).transpose()?;
247
248        // PR line
249        let protos = {
250            let tok = sec.required(RS_PR)?;
251            doc::PROTOVERS_CACHE.intern(
252                tok.args_as_str()
253                    .parse::<Protocols>()
254                    .map_err(|e| EK::BadArgument.at_pos(tok.pos()).with_source(e))?,
255            )
256        };
257
258        // W line
259        let weight = sec
260            .get(RS_W)
261            .map(RelayWeight::from_item)
262            .transpose()?
263            .unwrap_or_default();
264
265        // No p line
266        // no ID line
267
268        // Try to find the document digest.  This is in different
269        // places depending on the kind of consensus we're in.
270        let doc_digest: D = match consensus_flavor {
271            ConsensusFlavor::Microdesc => {
272                // M line
273                let m_item = sec.required(RS_M)?;
274                D::decode(m_item.required_arg(0)?)?
275            }
276            ConsensusFlavor::Ns => D::decode(r_item.required_arg(2)?)?,
277        };
278
279        Ok(GenericRouterStatus {
280            nickname,
281            identity,
282            addrs,
283            doc_digest,
284            flags,
285            version,
286            protos,
287            weight,
288        })
289    }
290}