1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
|
//! Relay descriptor publishing task.
//!
//! This task is responsible for building our relay descriptor and uploading it to the directory
//! authorities using [`tor_dirpublish`].
//!
//! It is driven by two sources of input:
//! * When receiving a [`DescriptorCommand`]. For instance, the crypto task
//! ([`crate::tasks::crypto`]) will send a [`DescriptorCommand::Publish`] when at least
//! one of the relay keys changes. It triggers a rebuild and publish of the relay descriptor.
//! * Consensus events from the [`NetDirProvider`], so that we can pick up new consensus
//! parameters when a new consensus arrives.
use std::collections::HashSet;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Context;
use futures::channel::mpsc;
use futures::{StreamExt as _, select_biased};
use tracing::{debug, trace};
use tor_async_utils::{mpsc_channel_no_memquota, oneshot};
use tor_dirclient::request::{Requestable, UploadRouterDesc};
use tor_dircommon::authority::AuthorityContacts;
use tor_dirpublish::{Publisher, http::DirectHttpUploader};
use tor_netdir::{DirEvent, NetDirProvider};
use tor_rtcompat::Runtime;
use crate::tasks::crypto::{CryptoCommand, CryptoCommandSender};
/// Initial delay before retrying a failed descriptor upload.
///
/// This is simply the initial delay then the [`tor_dirpublish::Publisher`] has its back off
/// algorithm seeded with this value.
const INITIAL_RETRY_DELAY: Duration = Duration::from_secs(60);
/// A command sent to the [`RelayDescriptorPublisherTask`] over its control channel.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub(crate) enum DescriptorCommand {
/// Rebuild the relay descriptor and publish it to the directory authorities. This
/// task asks the crypto task for the latest known identities/keys in order to
/// rebuild a new descriptor.
Publish,
}
/// The sending side of the [`DescriptorCommand`] channel.
pub(crate) type DescriptorCommandSender = mpsc::Sender<DescriptorCommand>;
/// The receiving side of the [`DescriptorCommand`] channel.
pub(crate) type DescriptorCommandReceiver = mpsc::Receiver<DescriptorCommand>;
/// Returns a new [`DescriptorCommand`] channel.
///
/// This is a bounded to limit descriptor publication spamming (in case of a bug).
pub(crate) fn new_command_channel() -> (DescriptorCommandSender, DescriptorCommandReceiver) {
// TODO(relay): We might want to make those memquota actually?
mpsc_channel_no_memquota(16)
}
/// Background task that builds and publishes the relay's descriptor.
pub(crate) struct RelayDescriptorPublisherTask {
/// Directory provider, used to learn about new consensus documents and parameters.
netdir: Arc<dyn NetDirProvider>,
/// The directory authorities we upload our descriptor to.
///
/// This is either from the config file or the compiled-in default list.
authorities: AuthorityContacts,
/// Channel on which we receive [`DescriptorCommand`]s from other tasks.
command_rx: DescriptorCommandReceiver,
/// The [`tor_dirpublish`] publisher that manages uploads to all targets.
publisher: Arc<Publisher<dyn Requestable, Vec<SocketAddr>>>,
/// The crypto task sender channel.
crypto_tx: CryptoCommandSender,
}
impl RelayDescriptorPublisherTask {
/// Construct a new descriptor publisher task.
///
/// This launches the underlying [`tor_dirpublish`] publisher (which spawns its own reactor),
/// but does not start listening for commands or consensus events until start() is called.
///
/// The publisher reactor won't try to upload until the
/// [`tor_dirpublish::Publisher::set_document`] is called.
pub(crate) fn new<R: Runtime>(
runtime: &R,
netdir: Arc<dyn NetDirProvider>,
authorities: AuthorityContacts,
crypto_tx: CryptoCommandSender,
command_rx: DescriptorCommandReceiver,
) -> anyhow::Result<Self> {
let uploader = Arc::new(DirectHttpUploader::new(runtime.clone()));
// We start with no document and no targets. Both are populated once we build a descriptor.
// This way we catch any new directory authorities showing up in the config or consensus.
let publisher = Publisher::launch(
runtime,
"relay descriptor".to_string(),
/* initial_document=*/ None,
/* initial_targets=*/ HashSet::new(),
INITIAL_RETRY_DELAY,
uploader,
)
.context("Failed to launch descriptor publisher")?;
Ok(Self {
netdir,
authorities,
command_rx,
publisher,
crypto_tx,
})
}
/// Build the relay's descriptor document as ready to be uploaded.
///
/// Returns `None` if we don't have everything we need to build a descriptor.
#[allow(clippy::unused_async)] // TODO(relay): remove once used.
async fn build_descriptor(&mut self) -> anyhow::Result<Option<Arc<str>>> {
// TODO(relay): No relay desc encoding support yet from tor-netdoc.
//
// Once encoding exists, this should:
// * encode and sign the descriptor,
// Get the latest ntor key (onion key) from the crypto task.
let (tx, rx) = oneshot::channel();
self.crypto_tx
.try_send(CryptoCommand::GetLatestNtorKey { tx })
.context("Crypto task try_send failed")?;
let _ntor_key = rx.await.context("Unable to get ntor key")?;
// Get the relay signing key from the crypto task.
let (tx, rx) = oneshot::channel();
self.crypto_tx
.try_send(CryptoCommand::GetSignKey { tx })
.context("Crypto task try_send failed")?;
let _relay_sign_kp = rx.await.context("Unable to get relay sign keypair")?;
// Keep the publisher idle until descriptor encoding is implemented.
Ok(None)
}
/// Recompute the set of directory authorities we upload to.
///
/// Each authority becomes one target, carrying all of its upload addresses so the
/// [`DirectHttpUploader`] can try them in turn.
///
/// Returns an empty set if we somehow have no authorities at all.
fn compute_targets(&self) -> HashSet<Vec<SocketAddr>> {
// This should never be empty because we have compiled in authorities by default.
// If that case ever happens, the publisher will just do nothing.
//
// TODO(relay): We have to check those against our relay capabilities as in if we
// support IPv6 or if we have an IPv4. For now, we pass all targets and let any
// failures be handled at the connect() attempt.
self.authorities
.uploads()
.iter()
.filter(|&addrs| !addrs.is_empty())
.cloned()
.collect()
}
/// Rebuild the descriptor (and refresh targets) and hand it to the publisher.
async fn rebuild_and_publish(&mut self) -> anyhow::Result<()> {
// Adjust the targets onto our publisher if we have any targets. An empty set
// means something has gone wrong somehow so don't touch the publisher in an
// attempt to use what was there before.
let targets = self.compute_targets();
if !targets.is_empty() {
let targets = targets.into_iter().map(Arc::new).collect();
self.publisher.adjust_targets(|t| *t = targets);
}
// Get the latest descriptor.
let desc = self
.build_descriptor()
.await
.context("Failed to build relay descriptor")?;
// Turn the encoded descriptor into a request and erase its concrete type for the
// generic HTTP publisher.
let doc = desc.map(|desc| Arc::new(UploadRouterDesc::new(desc)) as Arc<dyn Requestable>);
// Tell the publisher to publish the new document. Failing to build the descriptor, as in a
// None value, will make the publisher wait and do nothing.
self.publisher.set_document(doc, false);
Ok(())
}
/// Start the task.
///
/// This runs forever. It listens for [`DescriptorCommand`] and consensus events.
pub(crate) async fn start(mut self) -> anyhow::Result<void::Void> {
debug!("Starting Relay descriptor publisher task");
// Subscribe before the first run so we don't miss any events that arrive between
// startup and entering the select loop.
let mut consensus_events = self
.netdir
.events()
.filter(|ev| std::future::ready(matches!(ev, DirEvent::NewConsensus)))
.fuse();
// Do an initial build now, in case we already have a consensus.
self.rebuild_and_publish()
.await
.context("Failed initial descriptor publish")?;
loop {
select_biased! {
command = self.command_rx.next() => {
let command = command
.context("descriptor command channel closed unexpectedly")?;
trace!(?command, "Descriptor publisher received command");
match command {
DescriptorCommand::Publish => {
self.rebuild_and_publish()
.await
.context("Failed to publish descriptor on command")?;
}
}
}
event = consensus_events.next() => {
let _event = event
.context("netdir consensus event stream ended unexpectedly")?;
trace!("Descriptor publisher task saw new consensus. Rebuilding and publishing.");
self.rebuild_and_publish()
.await
.context("Failed to publish descriptor on new consensus")?;
}
// TODO(relay)
//
// Here are the other conditions documented in the spec for when we
// upload a new descriptor:
// https://spec.torproject.org/dir-spec/uploading-relay-documents.html
//
// - A period of time (18 hrs by default) has passed since the last
// upload.
// - A descriptor field other than bandwidth or uptime has changed.
// Its uptime is less than 24h and bandwidth has changed by a factor of
// 2 from the last time a descriptor was generated, and at least a given
// interval of time (3 hours by default) has passed since then.
// - Its uptime has been reset (by restarting).
// - It receives a networkstatus consensus in which it is not listed.
// - It receives a networkstatus consensus in which it is listed with
// the StaleDesc flag.
}
}
}
}
|