zebra-network 13.0.0

Networking code for Zebra
Documentation
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
//! The address book updater: an actor-style task that owns the address book
//! write path, wrapped in a [`tower::buffer::Buffer`] service handle.

use std::{
    cmp::max,
    net::SocketAddr,
    sync::Arc,
    task::{Context, Poll},
    time::Instant,
};

use chrono::Utc;
use futures::future;
use thiserror::Error;
use tokio::{
    sync::{mpsc, oneshot, watch},
    task::JoinHandle,
};
use tower::{buffer::Buffer, util::BoxService, Service};
use tracing::{Instrument, Level, Span};

use crate::{
    address_book::AddressMetrics,
    address_book_peers::AddressBookPeers,
    meta_addr::{MetaAddr, MetaAddrChange},
    AddressBook, BanList, BoxError, Config,
};

#[cfg(test)]
mod tests;

/// The minimum size of the address book updater channel.
pub const MIN_CHANNEL_SIZE: usize = 10;

/// The `AddressBookUpdater` hooks into incoming message streams for each peer
/// and lets the owner of the sender handle update the address book. For
/// example, it can be used to record per-connection last-seen timestamps, or
/// add new initial peers to the address book.
///
/// It also serves [`AddressBookRequest`]s through a [`Buffer`]-wrapped
/// [`AddressBookService`], which shares a single ordered request queue with
/// the change events. This makes compound operations like
/// [`AddressBookRequest::NextReconnectPeer`] atomic by construction.
#[derive(Debug, Eq, PartialEq)]
pub struct AddressBookUpdater;

#[derive(Copy, Clone, Debug, Error, Eq, PartialEq, Hash)]
#[error("all address book updater senders are closed")]
pub struct AllAddressBookUpdaterSendersClosed;

/// A request to the address book updater task.
#[derive(Clone, Debug)]
pub enum AddressBookRequest {
    /// Apply a single [`MetaAddrChange`] to the address book.
    Change(MetaAddrChange),

    /// Extend the address book with a batch of validated gossiped changes.
    ///
    /// The address book handles duplicate addresses internally.
    ExtendGossiped(Vec<MetaAddrChange>),

    /// Atomically choose the next reconnection candidate, and mark it as
    /// [`AttemptPending`](crate::PeerAddrState::AttemptPending).
    ///
    /// Because the address book updater serves one request at a time,
    /// concurrent `NextReconnectPeer` requests never return the same peer.
    NextReconnectPeer,

    /// Return the peers that are considered alive, in connection order.
    //
    // Hot reads like `getpeerinfo` currently use the shared address book
    // handle instead of this request, so they stay off the write queue.
    #[allow(dead_code)]
    RecentlyLivePeers,

    /// Return the peers that should be written to the peer cache on disk.
    CacheablePeers,

    /// Return the number of candidate peers that are currently ready for a
    /// connection attempt.
    ///
    /// The returned count is a snapshot: candidates can become ready or be
    /// attempted by other tasks immediately afterwards.
    ReadyPeerCount,
}

/// A response from the address book updater task.
#[derive(Clone, Debug)]
pub enum AddressBookResponse {
    /// The updated address book entry,
    /// or `None` if the change was rejected or delayed.
    //
    // Production changes are fire-and-forget, so the updated entry is
    // currently only read by tests.
    Updated(#[allow(dead_code)] Option<MetaAddr>),

    /// The address book was extended with the gossiped changes.
    Extended,

    /// The next reconnection candidate, already marked as
    /// [`AttemptPending`](crate::PeerAddrState::AttemptPending),
    /// or `None` if no peers are ready for a connection attempt.
    NextReconnectPeer(Option<MetaAddr>),

    /// A list of peers, in response to
    /// [`RecentlyLivePeers`](AddressBookRequest::RecentlyLivePeers) or
    /// [`CacheablePeers`](AddressBookRequest::CacheablePeers).
    Peers(Vec<MetaAddr>),

    /// The number of candidate peers that are currently ready for a
    /// connection attempt.
    ReadyPeerCount(usize),
}

/// A queued call to the address book updater task.
///
/// Fire-and-forget change events don't have a response sender.
#[derive(Debug)]
pub struct AddressBookCall {
    /// The request to serve.
    request: AddressBookRequest,

    /// The channel used to send the response, if the caller wants one.
    rsp_tx: Option<oneshot::Sender<AddressBookResponse>>,
}

/// A cheap cloneable handle that sends fire-and-forget [`MetaAddrChange`]
/// events to the address book updater task.
///
/// Changes share the updater's single ordered queue with
/// [`AddressBookService`] requests.
#[derive(Clone, Debug)]
pub struct AddressBookChangeSender(mpsc::Sender<AddressBookCall>);

impl AddressBookChangeSender {
    /// Sends `change` to the address book updater task,
    /// waiting until the queue has spare capacity.
    ///
    /// Returns an error if the address book updater task has exited.
    pub async fn send(
        &self,
        change: MetaAddrChange,
    ) -> Result<(), AllAddressBookUpdaterSendersClosed> {
        self.0
            .send(AddressBookCall {
                request: AddressBookRequest::Change(change),
                rsp_tx: None,
            })
            .await
            .map_err(|_| AllAddressBookUpdaterSendersClosed)
    }
}

/// Creates an [`AddressBookChangeSender`] and the receiving half of its
/// channel, without spawning an updater task.
///
/// Used to create stub change senders in tests, and for isolated connections
/// that don't have an address book.
pub fn change_channel(size: usize) -> (AddressBookChangeSender, mpsc::Receiver<AddressBookCall>) {
    let (tx, rx) = mpsc::channel(size);
    (AddressBookChangeSender(tx), rx)
}

/// Serves [`AddressBookRequest`]s directly from the address book it holds.
///
/// Used both as the inner service behind [`AddressBookService`], and by the
/// updater task that drains fire-and-forget [`AddressBookChangeSender`] events.
#[derive(Clone)]
struct AddressBookHandler {
    /// The address book to read and update.
    address_book: Arc<std::sync::Mutex<AddressBook>>,

    /// The channel used to publish the ban list when it changes.
    bans_sender: Arc<watch::Sender<BanList>>,
}

impl AddressBookHandler {
    /// Serves a single `request`.
    ///
    /// # Correctness
    ///
    /// Briefly holds the address book threaded mutex, with no awaits while it is
    /// held. External tasks must only use that mutex for hot reads, so this never
    /// blocks for long.
    fn handle(&self, request: AddressBookRequest) -> AddressBookResponse {
        trace!(?request, "got address book request");

        // Every arm needs the address book, so take the lock once, up front. The clock is
        // then sampled while the lock is already held: sampling it first would let the
        // timestamps go stale by however long the lock was contended, which makes
        // `reconnection_peers` under-report how many peers are ready to reconnect.
        let mut address_book = self
            .address_book
            .lock()
            .expect("mutex should be unpoisoned");

        match request {
            AddressBookRequest::Change(event) => {
                let event_ip = event.addr().ip();
                let updated = address_book.update(event);

                // `UpdateMisbehavior` events should only be passed to `update()` here,
                // so that this channel is always updated when new addresses are banned.
                //
                let bans = updated
                    .is_none()
                    .then(|| address_book.bans())
                    .filter(|bans| bans.is_banned(event_ip));

                // Don't hold the lock while sending the list of `bans`
                drop(address_book);

                if let Some(bans) = bans {
                    let _ = self.bans_sender.send(bans);
                }

                AddressBookResponse::Updated(updated)
            }

            AddressBookRequest::ExtendGossiped(changes) => {
                // Extend handles duplicate addresses internally.
                address_book.extend(changes);

                AddressBookResponse::Extended
            }

            AddressBookRequest::NextReconnectPeer => {
                // Choose the next candidate, and mark it as `AttemptPending`,
                // in a single atomic request.
                let next_peer = address_book
                    .reconnection_peers(Instant::now(), Utc::now())
                    .next()
                    .map(|next_peer| MetaAddr::new_reconnect(next_peer.addr));
                let next_peer = next_peer.and_then(|change| address_book.update(change));

                AddressBookResponse::NextReconnectPeer(next_peer)
            }

            AddressBookRequest::RecentlyLivePeers => {
                AddressBookResponse::Peers(address_book.recently_live_peers(Utc::now()))
            }

            AddressBookRequest::CacheablePeers => {
                AddressBookResponse::Peers(address_book.cacheable(Utc::now()))
            }

            AddressBookRequest::ReadyPeerCount => {
                let ready_peer_count = address_book
                    .reconnection_peers(Instant::now(), Utc::now())
                    .count();

                AddressBookResponse::ReadyPeerCount(ready_peer_count)
            }
        }
    }
}

impl Service<AddressBookRequest> for AddressBookHandler {
    type Response = AddressBookResponse;
    type Error = BoxError;
    type Future = future::Ready<Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, request: AddressBookRequest) -> Self::Future {
        future::ready(Ok(self.handle(request)))
    }
}

/// A shared [`Buffer`]-wrapped service handle to the address book.
///
/// Requests are served one at a time, directly from the address book. Writes are
/// serialised with fire-and-forget [`AddressBookChangeSender`] events by the
/// address book mutex.
pub type AddressBookService =
    Buffer<BoxService<AddressBookRequest, AddressBookResponse, BoxError>, AddressBookRequest>;

impl AddressBookUpdater {
    /// Spawn a new [`AddressBookUpdater`] task, updating a new [`AddressBook`]
    /// configured with Zebra's actual `local_listener` address.
    ///
    /// Returns handles for:
    /// - the address book, which should only be used for hot reads,
    /// - a watch channel for the ban list,
    /// - the transmission channel for address book update events,
    /// - a buffered service for address book requests,
    /// - a watch channel for address book metrics, and
    /// - the address book updater task join handle.
    ///
    /// The task exits with an error when all the returned
    /// [`AddressBookChangeSender`]s and [`AddressBookService`]s are closed.
    pub fn spawn(
        config: &Config,
        local_listener: SocketAddr,
    ) -> (
        Arc<std::sync::Mutex<AddressBook>>,
        watch::Receiver<BanList>,
        AddressBookChangeSender,
        AddressBookService,
        watch::Receiver<AddressMetrics>,
        JoinHandle<Result<(), BoxError>>,
    ) {
        let address_book = AddressBook::new(
            local_listener,
            &config.network,
            config.max_connections_per_ip,
            span!(Level::TRACE, "address book"),
        );

        // Use an update channel and buffer based on the maximum number of
        // inbound and outbound peers.
        let channel_size = max(config.peerset_total_connection_limit(), MIN_CHANNEL_SIZE);

        Self::spawn_with_address_book(address_book, channel_size)
    }

    /// Spawn a new [`AddressBookUpdater`] task for an existing `address_book`,
    /// using `channel_size` for the update channel and service buffer.
    ///
    /// See [`AddressBookUpdater::spawn`] for details.
    pub fn spawn_with_address_book(
        address_book: AddressBook,
        channel_size: usize,
    ) -> (
        Arc<std::sync::Mutex<AddressBook>>,
        watch::Receiver<BanList>,
        AddressBookChangeSender,
        AddressBookService,
        watch::Receiver<AddressMetrics>,
        JoinHandle<Result<(), BoxError>>,
    ) {
        // Create an mpsc channel for both fire-and-forget address book update
        // events and buffered service requests, so all address book writes go
        // through a single ordered queue.
        let (worker_tx, mut worker_rx) = mpsc::channel::<AddressBookCall>(channel_size);

        let address_metrics = address_book.address_metrics_watcher();
        let address_book = Arc::new(std::sync::Mutex::new(address_book));

        #[cfg(feature = "progress-bar")]
        let (mut address_info, address_bar, never_bar, failed_bar) = {
            let address_bar = howudoin::new_root().label("Known Peers");
            let never_bar =
                howudoin::new_with_parent(address_bar.id()).label("Never Attempted Peers");
            let failed_bar = howudoin::new_with_parent(never_bar.id()).label("Failed Peers");

            (address_metrics.clone(), address_bar, never_bar, failed_bar)
        };

        let (bans_sender, bans_receiver) = tokio::sync::watch::channel(
            address_book
                .lock()
                .expect("mutex should be unpoisoned")
                .bans(),
        );

        let handler = AddressBookHandler {
            address_book: address_book.clone(),
            bans_sender: Arc::new(bans_sender),
        };
        let worker_handler = handler.clone();

        let worker = async move {
            info!("starting the address book updater");

            while let Some(AddressBookCall { request, rsp_tx }) = worker_rx.recv().await {
                let response = worker_handler.handle(request);

                if let Some(rsp_tx) = rsp_tx {
                    // The caller might have been cancelled, so ignore send errors.
                    let _ = rsp_tx.send(response);
                }

                #[cfg(feature = "progress-bar")]
                if matches!(howudoin::cancelled(), Some(true)) {
                    address_bar.close();
                    never_bar.close();
                    failed_bar.close();
                } else if address_info.has_changed()? {
                    // We don't track:
                    // - attempt pending because it's always small
                    // - responded because it's the remaining attempted-but-not-failed peers
                    // - recently live because it's similar to the connected peer counts

                    let address_info = *address_info.borrow_and_update();

                    address_bar
                        .set_pos(u64::try_from(address_info.num_addresses).expect("fits in u64"));
                    // .set_len(u64::try_from(address_info.address_limit).expect("fits in u64"));

                    never_bar.set_pos(
                        u64::try_from(address_info.never_attempted_gossiped).expect("fits in u64"),
                    );
                    // .set_len(u64::try_from(address_info.address_limit).expect("fits in u64"));

                    failed_bar.set_pos(u64::try_from(address_info.failed).expect("fits in u64"));
                    // .set_len(u64::try_from(address_info.address_limit).expect("fits in u64"));
                }
            }

            #[cfg(feature = "progress-bar")]
            {
                address_bar.close();
                never_bar.close();
                failed_bar.close();
            }

            let error = Err(AllAddressBookUpdaterSendersClosed.into());
            info!(?error, "stopping address book updater");
            error
        };

        // # Correctness
        //
        // The updater task is an async task, not a blocking thread:
        // - each request only briefly locks the address book mutex, with no
        //   awaits while it is held, so the task never blocks for long, and
        // - a long-lived blocking task would inhibit auto-advance in tests
        //   that pause the tokio clock.
        let span = Span::current();
        let address_book_updater_task_handle = tokio::spawn(worker.instrument(span));

        let change_sender = AddressBookChangeSender(worker_tx.clone());

        let address_book_service = Buffer::new(BoxService::new(handler), channel_size);

        (
            address_book,
            bans_receiver,
            change_sender,
            address_book_service,
            address_metrics,
            address_book_updater_task_handle,
        )
    }
}