Skip to content

Commit e5adb79

Browse files
committed
track ddm state durations
1 parent de065a8 commit e5adb79

File tree

9 files changed

+388
-30
lines changed

9 files changed

+388
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ddm-admin-client/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ progenitor::generate_api!(
1414
}),
1515
post_hook = (|log: &slog::Logger, result: &Result<_, _>| {
1616
slog::trace!(log, "client response"; "result" => ?result);
17-
})
17+
}),
18+
replace = { Duration = std::time::Duration }
1819
);
1920

2021
impl Copy for types::Ipv4Prefix {}

ddm/src/admin.rs

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use crate::db::{Db, PeerInfo, TunnelRoute};
5+
use crate::db::{Db, RouterKind, TunnelRoute};
66
use crate::exchange::PathVector;
77
use crate::sm::{AdminEvent, Event, PrefixSet, SmContext};
88
use dropshot::endpoint;
@@ -27,6 +27,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
2727
use std::sync::mpsc::Sender;
2828
use std::sync::Arc;
2929
use std::sync::Mutex;
30+
use std::time::{Duration, Instant};
3031
use tokio::spawn;
3132
use tokio::task::JoinHandle;
3233
use uuid::Uuid;
@@ -103,12 +104,130 @@ pub fn handler(
103104
Ok(())
104105
}
105106

107+
/// Status of a DDM peer with state expressed as durations.
108+
#[derive(
109+
Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema,
110+
)]
111+
#[serde(tag = "type", content = "value")]
112+
pub enum PeerStatusV2 {
113+
NoContact,
114+
Init(Duration),
115+
Solicit(Duration),
116+
Exchange(Duration),
117+
Expired(Duration),
118+
}
119+
120+
// Translate internal peer status which is based on instants, to API
121+
// representation which is based on durations.
122+
impl From<crate::db::PeerStatus> for PeerStatusV2 {
123+
fn from(value: crate::db::PeerStatus) -> Self {
124+
match value {
125+
crate::db::PeerStatus::NoContact => Self::NoContact,
126+
crate::db::PeerStatus::Init(t) => {
127+
Self::Init(Instant::now().duration_since(t))
128+
}
129+
crate::db::PeerStatus::Solicit(t) => {
130+
Self::Solicit(Instant::now().duration_since(t))
131+
}
132+
crate::db::PeerStatus::Exchange(t) => {
133+
Self::Exchange(Instant::now().duration_since(t))
134+
}
135+
crate::db::PeerStatus::Expired(t) => {
136+
Self::Expired(Instant::now().duration_since(t))
137+
}
138+
}
139+
}
140+
}
141+
142+
/// Information about a DDM peer.
143+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
144+
pub struct PeerInfoV2 {
145+
pub status: PeerStatusV2,
146+
pub addr: Ipv6Addr,
147+
pub host: String,
148+
pub kind: RouterKind,
149+
}
150+
151+
#[derive(
152+
Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema,
153+
)]
154+
pub enum PeerStatus {
155+
NoContact,
156+
Active,
157+
Expired,
158+
}
159+
160+
// Translate internal peer status which is based on instants, to API
161+
// representation which is based on durations.
162+
impl From<crate::db::PeerStatus> for PeerStatus {
163+
fn from(value: crate::db::PeerStatus) -> Self {
164+
match value {
165+
crate::db::PeerStatus::NoContact => Self::NoContact,
166+
crate::db::PeerStatus::Init(_)
167+
| crate::db::PeerStatus::Solicit(_)
168+
| crate::db::PeerStatus::Exchange(_) => Self::Active,
169+
crate::db::PeerStatus::Expired(_) => Self::Expired,
170+
}
171+
}
172+
}
173+
174+
impl From<crate::db::PeerInfo> for PeerInfo {
175+
fn from(value: crate::db::PeerInfo) -> Self {
176+
Self {
177+
status: value.status.into(),
178+
addr: value.addr,
179+
host: value.host,
180+
kind: value.kind,
181+
}
182+
}
183+
}
184+
185+
/// Information about a DDM peer.
186+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
187+
pub struct PeerInfo {
188+
pub status: PeerStatus,
189+
pub addr: Ipv6Addr,
190+
pub host: String,
191+
pub kind: RouterKind,
192+
}
193+
194+
impl From<crate::db::PeerInfo> for PeerInfoV2 {
195+
fn from(value: crate::db::PeerInfo) -> Self {
196+
Self {
197+
status: value.status.into(),
198+
addr: value.addr,
199+
host: value.host,
200+
kind: value.kind,
201+
}
202+
}
203+
}
204+
106205
#[endpoint { method = GET, path = "/peers" }]
107206
async fn get_peers(
108207
ctx: RequestContext<Arc<Mutex<HandlerContext>>>,
109208
) -> Result<HttpResponseOk<HashMap<u32, PeerInfo>>, HttpError> {
110209
let ctx = ctx.context().lock().unwrap();
111-
Ok(HttpResponseOk(ctx.db.peers()))
210+
let peers = ctx
211+
.db
212+
.peers()
213+
.into_iter()
214+
.map(|(k, v)| (k, v.into()))
215+
.collect();
216+
Ok(HttpResponseOk(peers))
217+
}
218+
219+
#[endpoint { method = GET, path = "/peers_v2" }]
220+
async fn get_peers_v2(
221+
ctx: RequestContext<Arc<Mutex<HandlerContext>>>,
222+
) -> Result<HttpResponseOk<HashMap<u32, PeerInfoV2>>, HttpError> {
223+
let ctx = ctx.context().lock().unwrap();
224+
let peers = ctx
225+
.db
226+
.peers()
227+
.into_iter()
228+
.map(|(k, v)| (k, v.into()))
229+
.collect();
230+
Ok(HttpResponseOk(peers))
112231
}
113232

114233
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
@@ -422,6 +541,7 @@ pub fn api_description(
422541
) -> Result<ApiDescription<Arc<Mutex<HandlerContext>>>, String> {
423542
let mut api = ApiDescription::new();
424543
api.register(get_peers)?;
544+
api.register(get_peers_v2)?;
425545
api.register(expire_peer)?;
426546
api.register(advertise_prefixes)?;
427547
api.register(advertise_tunnel_endpoints)?;

ddm/src/db.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use mg_common::net::{IpPrefix, Ipv6Prefix, TunnelOrigin};
66
use schemars::{JsonSchema, JsonSchema_repr};
77
use serde::{Deserialize, Serialize};
88
use serde_repr::{Deserialize_repr, Serialize_repr};
9-
use slog::{error, Logger};
9+
use slog::{debug, error, Logger};
1010
use std::collections::{HashMap, HashSet};
1111
use std::net::Ipv6Addr;
1212
use std::sync::{Arc, Mutex};
13+
use std::time::Instant;
1314

1415
/// The handle used to open a persistent key-value tree for originated
1516
/// prefixes.
@@ -227,10 +228,34 @@ impl Db {
227228

228229
/// Set peer info at the given index. Returns true if peer information was
229230
/// changed.
230-
pub fn set_peer(&self, index: u32, info: PeerInfo) -> bool {
231-
match self.data.lock().unwrap().peers.insert(index, info.clone()) {
232-
Some(previous) => previous == info,
233-
None => true,
231+
pub fn set_peer_info(
232+
&self,
233+
index: u32,
234+
addr: Ipv6Addr,
235+
host: String,
236+
kind: RouterKind,
237+
) -> bool {
238+
let mut data = self.data.lock().unwrap();
239+
if let Some(peer) = data.peers.get_mut(&index) {
240+
if peer.addr == addr && peer.host == host && peer.kind == kind {
241+
false
242+
} else {
243+
peer.addr = addr;
244+
peer.host = host;
245+
peer.kind = kind;
246+
true
247+
}
248+
} else {
249+
data.peers.insert(
250+
index,
251+
PeerInfo {
252+
addr,
253+
host,
254+
kind,
255+
status: PeerStatus::Init(Instant::now()),
256+
},
257+
);
258+
true
234259
}
235260
}
236261

@@ -267,6 +292,19 @@ impl Db {
267292
self.data.lock().unwrap().peers.remove(&index);
268293
}
269294

295+
pub fn peer_status_transition(&self, index: u32, status: PeerStatus) {
296+
if let Some(info) = self.data.lock().unwrap().peers.get_mut(&index) {
297+
info.status = status;
298+
} else {
299+
// This is expected to happen during initialization as we don't
300+
// add a peer to the db until an advertisement is received.
301+
debug!(
302+
self.log,
303+
"status update: peer with index {} does not exist", index
304+
);
305+
}
306+
}
307+
270308
pub fn routes_by_vector(
271309
&self,
272310
dst: Ipv6Prefix,
@@ -283,16 +321,16 @@ impl Db {
283321
}
284322
}
285323

286-
#[derive(
287-
Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema,
288-
)]
324+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
289325
pub enum PeerStatus {
290326
NoContact,
291-
Active,
292-
Expired,
327+
Init(Instant),
328+
Solicit(Instant),
329+
Exchange(Instant),
330+
Expired(Instant),
293331
}
294332

295-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
333+
#[derive(Clone, Debug, PartialEq)]
296334
pub struct PeerInfo {
297335
pub status: PeerStatus,
298336
pub addr: Ipv6Addr,

ddm/src/discovery.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
//! and 1 for a transit routers. The fourth byte is a hostname length followed
8686
//! directly by a hostname of up to 255 bytes in length.
8787
88-
use crate::db::{Db, PeerInfo, PeerStatus, RouterKind};
88+
use crate::db::{Db, RouterKind};
8989
use crate::sm::{Config, Event, NeighborEvent, SessionStats};
9090
use crate::util::u8_slice_assume_init_ref;
9191
use crate::{dbg, err, inf, trc, wrn};
@@ -504,15 +504,9 @@ fn handle_advertisement(
504504
}
505505
};
506506
drop(guard);
507-
let updated = ctx.db.set_peer(
508-
ctx.config.if_index,
509-
PeerInfo {
510-
status: PeerStatus::Active,
511-
addr: *sender,
512-
host: hostname,
513-
kind,
514-
},
515-
);
507+
let updated =
508+
ctx.db
509+
.set_peer_info(ctx.config.if_index, *sender, hostname, kind);
516510
if updated {
517511
stats.peer_address.lock().unwrap().replace(*sender);
518512
emit_nbr_update(ctx, sender, version);

ddm/src/sm.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use crate::db::{Db, RouterKind};
5+
use crate::db::{Db, PeerStatus, RouterKind};
66
use crate::discovery::Version;
77
use crate::exchange::{PathVector, TunnelUpdate, UnderlayUpdate, Update};
88
use crate::{dbg, discovery, err, exchange, inf, wrn};
@@ -16,7 +16,7 @@ use std::sync::mpsc::{Receiver, Sender};
1616
use std::sync::{Arc, Mutex};
1717
use std::thread::sleep;
1818
use std::thread::spawn;
19-
use std::time::Duration;
19+
use std::time::{Duration, Instant};
2020
use thiserror::Error;
2121

2222
#[derive(Debug)]
@@ -228,6 +228,10 @@ impl State for Init {
228228
&mut self,
229229
event: Receiver<Event>,
230230
) -> (Box<dyn State>, Receiver<Event>) {
231+
self.ctx.db.peer_status_transition(
232+
self.ctx.config.if_index,
233+
PeerStatus::Init(Instant::now()),
234+
);
231235
loop {
232236
let info = match get_ipaddr_info(&self.ctx.config.aobj_name) {
233237
Ok(info) => info,
@@ -303,6 +307,10 @@ impl State for Solicit {
303307
&mut self,
304308
event: Receiver<Event>,
305309
) -> (Box<dyn State>, Receiver<Event>) {
310+
self.ctx.db.peer_status_transition(
311+
self.ctx.config.if_index,
312+
PeerStatus::Solicit(Instant::now()),
313+
);
306314
loop {
307315
let e = match event.recv() {
308316
Ok(e) => e,
@@ -529,6 +537,10 @@ impl State for Exchange {
529537
&mut self,
530538
event: Receiver<Event>,
531539
) -> (Box<dyn State>, Receiver<Event>) {
540+
self.ctx.db.peer_status_transition(
541+
self.ctx.config.if_index,
542+
PeerStatus::Exchange(Instant::now()),
543+
);
532544
let exchange_thread = loop {
533545
match exchange::handler(
534546
self.ctx.clone(),
@@ -759,7 +771,6 @@ impl State for Exchange {
759771
);
760772
}
761773
}
762-
// TODO tunnel
763774
Event::Peer(PeerEvent::Push(update)) => {
764775
inf!(
765776
self.log,

ddmadm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ tabwriter.workspace = true
1717
colored.workspace = true
1818
anyhow.workspace = true
1919
anstyle.workspace = true
20+
humantime.workspace = true

0 commit comments

Comments
 (0)