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
// BgpSim: BGP Network Simulator written in Rust
// Copyright 2022-2024 Tibor Schneider <sctibor@ethz.ch>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Module to record actions on the network and extract a trace of how the forwarding state changes
//! over time.
use serde::{Deserialize, Serialize};
use crate::{
event::EventQueue,
forwarding_state::ForwardingState,
interactive::InteractiveNetwork,
network::Network,
types::{NetworkError, RouterId, SinglePrefix, StepUpdate},
};
/// Extension trait that allows you to record events on the network. This is only available for
/// [`SinglePrefix`].
pub trait RecordNetwork<Q> {
/// Simulate a single action on the network and extract the record of the convergence
/// process. This function will record the forwarding state of all prefixes in the network. In
/// addition, if applicable, the times when the forwarding state changes will be recorded.
///
/// This function will return either the trace and the initial forwarding state, or the network
/// error generated by `f`.
fn record<F>(&mut self, f: F) -> Result<ConvergenceRecording, NetworkError>
where
F: FnOnce(&mut Network<SinglePrefix, Q>) -> Result<(), NetworkError>;
/// Simulate the network (by running all enqueued events). During this, construct a
/// `ConvergenceRecording` by recording every single change in the forwarding behavior of the
/// network. This function takes `initial_fw_state` (before any event happened), and the
/// `initial_trace`, that contains the difference from the initial forarding state to the state
/// that is currently present. The `initial_time` is used to normalize the provided convergence
/// times, e.g., to make every ConvergenceRecording zero-normalized.
///
/// Consider the implementation of [`RecordNetwork::record`] for `Network` to see an example of
/// how to use this function.
fn record_prepared(
&mut self,
initial_fw_state: ForwardingState<SinglePrefix>,
initial_trace: ConvergenceTrace,
initial_time: Option<f64>,
) -> Result<ConvergenceRecording, NetworkError>;
}
impl<Q> RecordNetwork<Q> for Network<SinglePrefix, Q>
where
Q: EventQueue<SinglePrefix>,
{
fn record<F>(&mut self, f: F) -> Result<ConvergenceRecording, NetworkError>
where
F: FnOnce(&mut Network<SinglePrefix, Q>) -> Result<(), NetworkError>,
{
// set the queue to skip mode.
let original_skip_value = self.skip_queue;
self.skip_queue = true;
// get the forwarding state before
let fw_state_before = self.get_forwarding_state();
let initial_time = self.queue().get_time();
// execute the function
f(self)?;
// get the forwarding state difference and start generating the trace
let fw_state_after = self.get_forwarding_state();
let diff = fw_state_before.diff(&fw_state_after);
let trace = vec![(diff, initial_time.map(|_| 0.0).into())];
// now, generate the recording using the intrinsic function
let record = self.record_prepared(fw_state_before, trace, initial_time)?;
// reset the queue skip property
self.skip_queue = original_skip_value;
Ok(record)
}
fn record_prepared(
&mut self,
initial_fw_state: ForwardingState<SinglePrefix>,
mut trace: ConvergenceTrace,
initial_time: Option<f64>,
) -> Result<ConvergenceRecording, NetworkError> {
let t = initial_time.unwrap_or_default();
while let Some((step, event)) = self.simulate_step()? {
match step {
StepUpdate::Unchanged => {}
StepUpdate::Single(delta) => {
let time = self.queue().get_time().map(|x| x - t);
trace.push((vec![(event.router(), delta.old, delta.new)], time.into()));
}
StepUpdate::Multiple => {
log::warn!("Ignoring OSPF event that might changed multiple FIB entries!")
}
}
}
Ok(ConvergenceRecording::new(initial_fw_state, trace))
}
}
/// Record of an entire convergence process that captures the history of each change in the
/// forwarding state of the network. This is a record that allows the state to be moved forwards or
/// backwards in time. The recording can be changed on a per-prefix level.
#[derive(Debug, Clone, PartialEq)]
pub struct ConvergenceRecording {
state: ForwardingState<SinglePrefix>,
trace: ConvergenceTrace,
pointer: usize,
}
impl ConvergenceRecording {
/// Create a Recording from a trace and an initial forwarding state
pub fn new(initial_fw_state: ForwardingState<SinglePrefix>, trace: ConvergenceTrace) -> Self {
ConvergenceRecording {
state: initial_fw_state,
trace,
pointer: 0,
}
}
/// Get a reference to the current forwarding state
pub fn state(&mut self) -> &mut ForwardingState<SinglePrefix> {
&mut self.state
}
/// Get a reference of the convergence trace.
pub fn trace(&self) -> &ConvergenceTrace {
&self.trace
}
/// Transform the recording into a trace.
pub fn as_trace(self) -> ConvergenceTrace {
self.trace
}
/// Perform a single step for an individual prefix. If the forwarding state is already in the
/// final state for the specifiied prefix, then this function will return `None`. Otherwise, it
/// will return a slice containing all deltas that were applied during this function call, the
/// network's convergence time when the change took effect (if applicable), and a mutable
/// reference to the new `ForwardingState`.
pub fn step(
&mut self,
) -> Option<(&[FwDelta], Option<f64>, &mut ForwardingState<SinglePrefix>)> {
if self.pointer >= self.trace.len() {
// already out of bounds
return None;
}
// apply the delta at the current position
let (deltas, time) = self.trace.get(self.pointer)?;
for (router, _, new_nh) in deltas {
self.state.update(*router, SinglePrefix, new_nh.clone());
}
// increment the pointer
self.pointer += 1;
// return the applied deltas
Some((deltas, time.into_inner(), &mut self.state))
}
/// Undo a single step for an individual prefix. If the forwarding state is already in the
/// initial state for the specifiied prefix, then this function will return `None`. Otherwise, it
/// will return a slice containing all deltas that were applied *in reverse direction* during
/// this function call, the network's convergence time when the change took effect (if
/// applicable), and a mutable reference to the new `ForwardingState`.
pub fn back(
&mut self,
) -> Option<(&[FwDelta], Option<f64>, &mut ForwardingState<SinglePrefix>)> {
if self.pointer == 0 {
// already out of bounds
return None;
}
// decrement the pointer
self.pointer -= 1;
// apply the delta at the current position
let (deltas, time) = self.trace.get(self.pointer)?;
for (router, old_nh, _) in deltas {
self.state.update(*router, SinglePrefix, old_nh.clone());
}
// return the applied deltas
Some((deltas, time.into_inner(), &mut self.state))
}
/// Get the position of the recording for the given prefix.
pub fn pos(&self) -> usize {
self.pointer
}
/// Get the length of the recording for the given prefix
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.trace.len()
}
/// Check if the recording is empty for the given prefix
pub fn is_empty(&self) -> bool {
self.trace.is_empty()
}
/// Reverts to and releases the initial `ForwardingState` held by `self`, while consuming
/// `self`. This function will also drop any timing information.
pub fn into_initial_fw_state(self) -> ForwardingState<SinglePrefix> {
let mut state = self.state;
let trace = self.trace;
let mut ptr = self.pointer;
while ptr > 0 {
ptr -= 1;
let (deltas, _) = trace.get(ptr).unwrap();
for (router, old_nh, _) in deltas {
state.update(*router, SinglePrefix, old_nh.clone());
}
}
state
}
}
/// This structure captures the essence of a trace, that is, the entire evolution of the forwarding
/// state during the convergence process. It does not capture the initial or the final state, but it
/// should be efficient to compare entries.
///
/// This structure also stores the time when the forwarding delta occurs. The time is
/// zero-normalized, which means that the time zero always represents the time when the convergence
/// trace recording has started. The time is warpped in an `AlwaysEq` such that comparisons will
/// ignore the time.
pub type ConvergenceTrace = Vec<(Vec<FwDelta>, AlwaysEq<Option<f64>>)>;
/// Structure that wraps the inner type and always returns `true` or `Ordering::Equal` when compared.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(transparent)]
pub struct AlwaysEq<T>(T);
impl<T> std::hash::Hash for AlwaysEq<T> {
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
}
impl<T> Ord for AlwaysEq<T> {
fn cmp(&self, _other: &Self) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
}
impl<T> PartialOrd for AlwaysEq<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Eq for AlwaysEq<T> {}
impl<T> PartialEq for AlwaysEq<T> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T> From<T> for AlwaysEq<T> {
fn from(value: T) -> Self {
AlwaysEq(value)
}
}
impl<T> From<AlwaysEq<Option<T>>> for Option<T> {
fn from(value: AlwaysEq<Option<T>>) -> Self {
value.0
}
}
impl<T> AsRef<T> for AlwaysEq<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T: std::fmt::Display> std::fmt::Display for AlwaysEq<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<T> AlwaysEq<T> {
/// Consume `self` and return the inner `T`.
pub fn into_inner(self) -> T {
self.0
}
}
/// Forwarding state delta.
pub type FwDelta = (RouterId, Vec<RouterId>, Vec<RouterId>);