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")]
7
pub(crate) mod build;
8
mod md;
9
#[cfg(feature = "ns_consensus")]
10
mod ns;
11

            
12
use super::{ConsensusFlavor, NetstatusKwd, RelayFlags, RelayWeight};
13
use crate::doc;
14
use crate::parse::parser::Section;
15
use crate::types::misc::*;
16
use crate::types::version::TorVersion;
17
use crate::util::intern::InternCache;
18
use crate::{Error, NetdocErrorKind as EK, Result};
19
use std::sync::Arc;
20
use std::{net, time};
21

            
22
use tor_llcrypto::pk::rsa::RsaIdentity;
23
use tor_protover::Protocols;
24

            
25
pub use md::MdConsensusRouterStatus;
26
#[cfg(feature = "ns_consensus")]
27
pub 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)]
37
struct 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]
75
pub 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.
86
static OTHER_VERSION_CACHE: InternCache<str> = InternCache::new();
87

            
88
impl std::str::FromStr for Version {
89
    type Err = Error;
90

            
91
1624
    fn from_str(s: &str) -> Result<Self> {
92
1624
        let mut elts = s.splitn(3, ' ');
93
1624
        if elts.next() == Some("Tor") {
94
1622
            if let Some(Ok(v)) = elts.next().map(str::parse) {
95
1622
                return Ok(Version::Tor(v));
96
            }
97
2
        }
98

            
99
2
        Ok(Version::Other(OTHER_VERSION_CACHE.intern_ref(s)))
100
1624
    }
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.
106
macro_rules! implement_accessors {
107
    ($name:ident) => {
108
        impl $name {
109
            /// Return an iterator of ORPort addresses for this routerstatus
110
4
            pub fn orport_addrs(&self) -> impl Iterator<Item = &net::SocketAddr> {
111
4
                self.rs.addrs.iter()
112
4
            }
113
            /// Return the declared weight of this routerstatus in the directory.
114
19846587
            pub fn weight(&self) -> &RelayWeight {
115
19846587
                &self.rs.weight
116
19846587
            }
117
            /// Return the ORPort addresses of this routerstatus
118
79521386
            pub fn addrs(&self) -> &[net::SocketAddr] {
119
79521386
                &self.rs.addrs[..]
120
79521386
            }
121
            /// Return the protovers that this routerstatus says it implements.
122
1940573
            pub fn protovers(&self) -> &Protocols {
123
1940573
                &self.rs.protos
124
1940573
            }
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
39073287
            pub fn ed25519_id_is_usable(&self) -> bool {
140
39073287
                !self.rs.flags.contains(RelayFlags::NO_ED_CONSENSUS)
141
39073287
            }
142
            /// Return true if this routerstatus is listed with the BadExit flag.
143
27166600
            pub fn is_flagged_bad_exit(&self) -> bool {
144
27166600
                self.rs.flags.contains(RelayFlags::BAD_EXIT)
145
27166600
            }
146
            /// Return true if this routerstatus is listed with the v2dir flag.
147
20440509
            pub fn is_flagged_v2dir(&self) -> bool {
148
20440509
                self.rs.flags.contains(RelayFlags::V2DIR)
149
20440509
            }
150
            /// Return true if this routerstatus is listed with the Exit flag.
151
19899227
            pub fn is_flagged_exit(&self) -> bool {
152
19899227
                self.rs.flags.contains(RelayFlags::EXIT)
153
19899227
            }
154
            /// Return true if this routerstatus is listed with the Guard flag.
155
21245749
            pub fn is_flagged_guard(&self) -> bool {
156
21245749
                self.rs.flags.contains(RelayFlags::GUARD)
157
21245749
            }
158
            /// Return true if this routerstatus is listed with the HSDir flag.
159
287615
            pub fn is_flagged_hsdir(&self) -> bool {
160
287615
                self.rs.flags.contains(RelayFlags::HSDIR)
161
287615
            }
162
            /// Return true if this routerstatus is listed with the Stable flag.
163
2589560
            pub fn is_flagged_stable(&self) -> bool {
164
2589560
                self.rs.flags.contains(RelayFlags::STABLE)
165
2589560
            }
166
            /// Return true if this routerstatus is listed with the Fast flag.
167
29215575
            pub fn is_flagged_fast(&self) -> bool {
168
29215575
                self.rs.flags.contains(RelayFlags::FAST)
169
29215575
            }
170
            /// Return true if this routerstatus is listed with the MiddleOnly flag.
171
            ///
172
            /// Note that this flag is only used by authorities as part of
173
            /// the voting process; clients do not and should not act
174
            /// based on whether it is set.
175
            pub fn is_flagged_middle_only(&self) -> bool {
176
                self.rs.flags.contains(RelayFlags::MIDDLE_ONLY)
177
            }
178
        }
179
    };
180
}
181

            
182
// Make the macro public in the crate.
183
pub(crate) use implement_accessors;
184

            
185
/// Helper to decode a document digest in the format in which it
186
/// appears in a given kind of routerstatus.
187
#[cfg_attr(feature = "dangerous-expose-struct-fields", visibility::make(pub))]
188
trait FromRsString: Sized {
189
    /// Try to decode the given object.
190
    fn decode(s: &str) -> Result<Self>;
191
}
192

            
193
impl<D> GenericRouterStatus<D>
194
where
195
    D: FromRsString,
196
{
197
    /// Parse a generic routerstatus from a section.
198
    ///
199
    /// Requires that the section obeys the right SectionRules,
200
    /// matching `consensus_flavor`.
201
1624
    fn from_section(
202
1624
        sec: &Section<'_, NetstatusKwd>,
203
1624
        consensus_flavor: ConsensusFlavor,
204
1624
    ) -> Result<GenericRouterStatus<D>> {
205
        use NetstatusKwd::*;
206
        // R line
207
1624
        let r_item = sec.required(RS_R)?;
208
1624
        let nickname = r_item.required_arg(0)?.parse()?;
209
1624
        let ident = r_item.required_arg(1)?.parse::<B64>()?;
210
1624
        let identity = RsaIdentity::from_bytes(ident.as_bytes()).ok_or_else(|| {
211
            EK::BadArgument
212
                .at_pos(r_item.pos())
213
                .with_msg("Wrong identity length")
214
1624
        })?;
215
        // Fields to skip in the "r" line.
216
1624
        let n_skip = match consensus_flavor {
217
1608
            ConsensusFlavor::Microdesc => 0,
218
16
            ConsensusFlavor::Ns => 1,
219
        };
220
        // We check that the published time is well-formed, but we never use it
221
        // for anything in a consensus document.
222
1624
        let _ignore_published: time::SystemTime = {
223
            // TODO: It's annoying to have to do this allocation, since we
224
            // already have a slice that contains both of these arguments.
225
            // Instead, we could get a slice of arguments: we'd have to add
226
            // a feature for that.
227
1624
            let mut p = r_item.required_arg(2 + n_skip)?.to_string();
228
1624
            p.push(' ');
229
1624
            p.push_str(r_item.required_arg(3 + n_skip)?);
230
1624
            p.parse::<Iso8601TimeSp>()?.into()
231
        };
232
1624
        let ipv4addr = r_item.required_arg(4 + n_skip)?.parse::<net::Ipv4Addr>()?;
233
1624
        let or_port = r_item.required_arg(5 + n_skip)?.parse::<u16>()?;
234
1624
        let _ = r_item.required_arg(6 + n_skip)?.parse::<u16>()?;
235

            
236
        // main address and A lines.
237
1624
        let a_items = sec.slice(RS_A);
238
1624
        let mut addrs = Vec::with_capacity(1 + a_items.len());
239
1624
        addrs.push(net::SocketAddr::V4(net::SocketAddrV4::new(
240
1624
            ipv4addr, or_port,
241
1624
        )));
242
2904
        for a_item in a_items {
243
1280
            addrs.push(a_item.required_arg(0)?.parse::<net::SocketAddr>()?);
244
        }
245

            
246
        // S line
247
1624
        let flags = RelayFlags::from_item(sec.required(RS_S)?)?;
248

            
249
        // V line
250
1622
        let version = sec.maybe(RS_V).args_as_str().map(str::parse).transpose()?;
251

            
252
        // PR line
253
1622
        let protos = {
254
1622
            let tok = sec.required(RS_PR)?;
255
1622
            doc::PROTOVERS_CACHE.intern(
256
1622
                tok.args_as_str()
257
1622
                    .parse::<Protocols>()
258
1622
                    .map_err(|e| EK::BadArgument.at_pos(tok.pos()).with_source(e))?,
259
            )
260
        };
261

            
262
        // W line
263
1622
        let weight = sec
264
1622
            .get(RS_W)
265
1622
            .map(RelayWeight::from_item)
266
1622
            .transpose()?
267
1620
            .unwrap_or_default();
268

            
269
        // No p line
270
        // no ID line
271

            
272
        // Try to find the document digest.  This is in different
273
        // places depending on the kind of consensus we're in.
274
1620
        let doc_digest: D = match consensus_flavor {
275
            ConsensusFlavor::Microdesc => {
276
                // M line
277
1604
                let m_item = sec.required(RS_M)?;
278
1604
                D::decode(m_item.required_arg(0)?)?
279
            }
280
16
            ConsensusFlavor::Ns => D::decode(r_item.required_arg(2)?)?,
281
        };
282

            
283
1618
        Ok(GenericRouterStatus {
284
1618
            nickname,
285
1618
            identity,
286
1618
            addrs,
287
1618
            doc_digest,
288
1618
            flags,
289
1618
            version,
290
1618
            protos,
291
1618
            weight,
292
1618
        })
293
1624
    }
294
}