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
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
use crate::dhcp_proxy::lib::g_rpc::{Lease as NetavarkLease, Lease};
use log::{debug, error};
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::{Cursor, Write};
#[derive(Debug)]
#[allow(dead_code)]
pub struct ClearError {
msg: String,
}
/// The Writer on the cache must be clearable so that any new changes can be overwritten
pub trait Clear {
fn clear(&mut self) -> Result<(), ClearError>;
}
impl Clear for Cursor<Vec<u8>> {
fn clear(&mut self) -> Result<(), ClearError> {
self.set_position(0);
self.get_mut().clear();
Ok(())
}
}
impl Clear for File {
fn clear(&mut self) -> Result<(), ClearError> {
match self.set_len(0) {
Ok(_) => Ok(()),
Err(e) => Err(ClearError { msg: e.to_string() }),
}
}
}
/// The leasing cache holds a in memory record of the leases, and a on file version
#[derive(Debug)]
pub struct LeaseCache<W: Write + Clear> {
mem: HashMap<String, Vec<NetavarkLease>>,
writer: W,
}
impl<W: Write + Clear> LeaseCache<W> {
///
///
/// # Arguments
///
/// * `writer`: any type that can has the Write and Clear trait implemented. In production this
/// is a file. In development/testing this is a Cursor of bytes
///
/// returns: Result<LeaseCache<W>, Error>
///
pub fn new(writer: W) -> Result<LeaseCache<W>, io::Error> {
Ok(LeaseCache {
mem: HashMap::new(),
writer,
})
}
/// Add a new lease to a memory and file system cache
///
/// # Arguments
///
/// * `mac_addr`: Mac address of the container
/// * `lease`: New lease that should be saved in the cache
///
/// returns: Result<(), Error>
///
pub fn add_lease(&mut self, mac_addr: &str, lease: &NetavarkLease) -> Result<(), io::Error> {
debug!("add lease: {:?}", mac_addr);
// Update cache memory with new lease
let cache = &mut self.mem;
cache.insert(mac_addr.to_string(), vec![lease.clone()]);
// write updated memory cache to the file system
self.save_memory_to_fs()
}
/// When a lease changes, update the lease in memory and on the writer.
///
/// # Arguments
///
/// * `mac_addr`: Mac address of the container
/// * `lease`: Newest lease information
///
/// returns: Result<(), Error>
///
pub fn update_lease(&mut self, mac_addr: &str, lease: NetavarkLease) -> Result<(), io::Error> {
let cache = &mut self.mem;
// write to the memory cache
cache.insert(mac_addr.to_string(), vec![lease]);
// write updated memory cache to the file system
self.save_memory_to_fs()
}
/// When a singular container is taken down. Remove that lease from the cache memory and fs
///
/// # Arguments
///
/// * `mac_addr`: Mac address of the container
pub fn remove_lease(&mut self, mac_addr: &str) -> Result<Lease, io::Error> {
debug!("remove lease: {:?}", mac_addr);
let mem = &mut self.mem;
// Check and see if the lease exists, if not create an empty one
let lease = match mem.get(mac_addr) {
None => Lease {
t1: 0,
t2: 0,
lease_time: 0,
mtu: 0,
domain_name: "".to_string(),
mac_address: "".to_string(),
is_v6: false,
siaddr: "".to_string(),
yiaddr: "".to_string(),
srv_id: "".to_string(),
subnet_mask: "".to_string(),
broadcast_addr: "".to_string(),
dns_servers: vec![],
gateways: vec![],
ntp_servers: vec![],
host_name: "".to_string(),
},
Some(l) => l[0].clone(),
};
// Try and remove the lease. If it doesnt exist, exit with the blank lease
if mem.remove(mac_addr).is_none() {
return Ok(lease);
}
// write updated memory cache to the file system
match self.save_memory_to_fs() {
Ok(_) => Ok(lease),
Err(e) => Err(e),
}
}
/// Clean up the memory and file system on tear down of the proxy server
pub fn teardown(&mut self) -> Result<(), io::Error> {
self.mem.clear();
self.save_memory_to_fs()
}
/// Save the memory contents to the file system. This will remove the contents in the file,
/// then write the memory map to the file. This method will be called any the lease memory cache
/// changes (new lease, remove lease, update lease)
fn save_memory_to_fs(&mut self) -> io::Result<()> {
let mem = &self.mem;
let writer = &mut self.writer;
// Clear the writer so we can add the old leases
match writer.clear() {
Ok(_) => {
serde_json::to_writer(writer.by_ref(), &mem)?;
writer.flush()
}
Err(e) => {
error!(
"Could not clear the writer. Not updating lease information: {:?}",
e
);
Ok(())
}
}
}
// rust validators require both len and is_empty if you define one
// of them
pub fn len(&self) -> usize {
self.mem.len()
}
pub fn is_empty(&self) -> bool {
if self.len() < 1 {
return true;
}
false
}
}
#[cfg(test)]
mod cache_tests {
use super::super::cache::LeaseCache;
use super::super::lib::g_rpc::{Lease as NetavarkLease, Lease};
use crate::network::core_utils;
use rand::{thread_rng, Rng};
use std::collections::HashMap;
use std::io::Cursor;
// Create a single random ipv4 addr
fn random_ipv4() -> String {
let mut rng = thread_rng();
format!(
"{:?}.{:?}.{:?}.{:?}.",
rng.gen_range(0..255),
rng.gen_range(0..255),
rng.gen_range(0..255),
rng.gen_range(0..255)
)
}
// Create a single random mac address
fn random_macaddr() -> String {
let mut rng = thread_rng();
let bytes = vec![
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
];
core_utils::CoreUtils::encode_address_to_hex(&bytes)
}
// Create a single random lease
fn random_lease(mac_address: &String) -> Lease {
Lease {
t1: 0,
t2: 3600,
lease_time: 0,
mtu: 0,
domain_name: "example.domain".to_string(),
mac_address: String::from(mac_address),
siaddr: random_ipv4(),
yiaddr: random_ipv4(),
srv_id: random_ipv4(),
subnet_mask: "".to_string(),
broadcast_addr: "".to_string(),
dns_servers: vec![],
gateways: vec![],
ntp_servers: vec![],
host_name: "example.host_name".to_string(),
is_v6: false,
}
}
// Shared information for all tests
struct CacheTestSetup {
cache: LeaseCache<Cursor<Vec<u8>>>,
macaddrs: Vec<String>,
range: u8,
}
impl CacheTestSetup {
fn new() -> Self {
// Use byte Cursor instead of file for testing
let buff = Cursor::new(Vec::new());
let cache = match LeaseCache::new(buff) {
Ok(cache) => cache,
Err(e) => panic!("Could not create leases cache: {e:?}"),
};
// Create a random amount of randomized leases
let macaddrs = Vec::new();
let mut rng = thread_rng();
// Make a random amount of leases
let range: u8 = rng.gen_range(0..10);
CacheTestSetup {
cache,
macaddrs,
range,
}
}
}
#[test]
fn add_leases() {
let setup = CacheTestSetup::new();
let mut cache = setup.cache;
let mut macaddrs = setup.macaddrs;
let range = setup.range;
for i in 0..range {
// Create a random mac address to create a random lease of that mac address
let mac_address = random_macaddr();
macaddrs.push(mac_address.clone());
let lease = random_lease(&mac_address);
// Add the lease to the cache
cache
.add_lease(&mac_address, &lease)
.expect("could not add lease to cache");
// Deserialize the written bytes to compare
let lease_bytes = cache.writer.get_ref().as_slice();
let s: HashMap<String, Vec<NetavarkLease>> = match serde_json::from_slice(lease_bytes) {
Ok(s) => s,
Err(e) => panic!("Error: {e:?}"),
};
// Get the mac address of the lease
let macaddr = macaddrs
.get(i as usize)
.expect("Could not get the mac address of the lease added");
// Find the lease in the set of deserialized leases
let deserialized_lease = s
.get(macaddr)
.expect("Could not get the mac address from the map")
.get(0)
.expect("Could not get lease from set of mac addresses")
.clone();
// Assure that the amount of leases added is correct amount
assert_eq!(s.len(), (i + 1) as usize);
// Assure that the lease added was correct
assert_eq!(lease, deserialized_lease);
}
}
#[test]
fn remove_leases() {
let setup = CacheTestSetup::new();
let mut cache = setup.cache;
let mut macaddrs = setup.macaddrs;
let range = setup.range;
for i in 0..range {
// Create a random mac address to create a random lease of that mac address
let mac_address = random_macaddr();
macaddrs.push(mac_address.clone());
let lease = random_lease(&mac_address);
// Add the lease to the cache
cache
.add_lease(&mac_address, &lease)
.expect("could not add lease to cache");
// Deserialize the written bytes to compare
let lease_bytes = cache.writer.get_ref().as_slice();
let s: HashMap<String, Vec<NetavarkLease>> = match serde_json::from_slice(lease_bytes) {
Ok(s) => s,
Err(e) => panic!("Error: {e:?}"),
};
// Get the mac address of the lease
let macaddr = macaddrs
.get(i as usize)
.expect("Could not get the mac address of the lease added");
// Find the lease in the set of deserialized leases
let deserialized_lease = s
.get(macaddr)
.expect("Could not get the mac address from the map")
.get(0)
.expect("Could not get lease from set of mac addresses")
.clone();
// Assure that the amount of leases added is correct amount
assert_eq!(s.len(), (i + 1) as usize);
// Assure that the lease added was correct
assert_eq!(lease, deserialized_lease);
}
for i in 0..range {
// Deserialize the written bytes to compare
let lease_bytes = cache.writer.get_ref().as_slice();
let s: HashMap<String, Vec<NetavarkLease>> = match serde_json::from_slice(lease_bytes) {
Ok(s) => s,
Err(e) => panic!("Error: {e:?}"),
};
let macaddr = macaddrs
.get(i as usize)
.expect("Could not get the mac address of the lease added");
let deserialized_lease = s
.get(macaddr)
.expect("Could not get the mac address from the map")
.get(0)
.expect("Could not get lease from set of mac addresses")
.clone();
let removed_lease = cache
.remove_lease(macaddr)
.unwrap_or_else(|_| panic!("Could not remove {macaddr:?} from leases"));
// Assure the lease is no longer in memory
assert_eq!(deserialized_lease, removed_lease);
assert_eq!(s.len(), (range - i) as usize);
// Deserialize the cache again to assure the lease is not in the writer
let lease_bytes = cache.writer.get_ref().as_slice();
let s: HashMap<String, Vec<NetavarkLease>> = match serde_json::from_slice(lease_bytes) {
Ok(s) => s,
Err(e) => panic!("Error: {e:?}"),
};
// There should be no lease under that mac address if the lease was removed
let no_lease = s.get(macaddr);
assert_eq!(no_lease, None);
// Remove a lease that does not exist
let removed_lease = cache
.remove_lease(macaddr)
.expect("Could not remove the lease successfully");
// The returned lease should be a blank one
assert_eq!(removed_lease.mac_address, "".to_string());
}
}
#[test]
fn update_leases() {
let setup = CacheTestSetup::new();
let mut cache = setup.cache;
let mut macaddrs = setup.macaddrs;
let range = setup.range;
for i in 0..range {
// Create a random mac address to create a random lease of that mac address
let mac_address = random_macaddr();
macaddrs.push(mac_address.clone());
let lease = random_lease(&mac_address);
// Add the lease to the cache
cache
.add_lease(&mac_address, &lease)
.expect("could not add lease to cache");
// Deserialize the written bytes to compare
let lease_bytes = cache.writer.get_ref().as_slice();
let s: HashMap<String, Vec<NetavarkLease>> = match serde_json::from_slice(lease_bytes) {
Ok(s) => s,
Err(e) => panic!("Error: {e:?}"),
};
// Get the mac address of the lease
let macaddr = macaddrs
.get(i as usize)
.expect("Could not get the mac address of the lease added");
// Find the lease in the set of deserialized leases
let deserialized_lease = s
.get(macaddr)
.expect("Could not get the mac address from the map")
.get(0)
.expect("Could not get lease from set of mac addresses")
.clone();
// Assure that the amount of leases added is correct amount
assert_eq!(s.len(), (i + 1) as usize);
// Assure that the lease added was correct
assert_eq!(lease, deserialized_lease);
}
// Update all of the leases
for i in 0..range {
// Deserialize the written bytes to compare
let macaddr = macaddrs
.get(i as usize)
.expect("Could not get the mac address of the lease added");
// Create a new random lease with the same mac address
let new_lease = random_lease(macaddr);
cache
.update_lease(macaddr, new_lease.clone())
.expect("Could not update the lease");
// Deserialize the cache again to assure the lease is not in the writer
let lease_bytes = cache.writer.get_ref().as_slice();
let s: HashMap<String, Vec<NetavarkLease>> = match serde_json::from_slice(lease_bytes) {
Ok(s) => s,
Err(e) => panic!("Error: {e:?}"),
};
// There should be no lease under that mac address if the lease was removed
let deserialized_updated_lease = s
.get(macaddr)
.expect("Could not get lease from deserialized map")
.get(0)
.expect("Could not find lease in set of multi-homing leases");
assert_eq!(deserialized_updated_lease, &new_lease);
}
}
}