2016-02-05 15:58:32 +00:00
|
|
|
// VpnCloud - Peer-to-Peer VPN
|
2020-05-28 07:03:48 +00:00
|
|
|
// Copyright (C) 2015-2020 Dennis Schwerdel
|
2016-02-05 15:58:32 +00:00
|
|
|
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
|
|
|
|
2019-12-04 08:32:35 +00:00
|
|
|
use std::{
|
|
|
|
collections::{hash_map::Entry, HashMap},
|
|
|
|
hash::BuildHasherDefault,
|
|
|
|
io::{self, Write},
|
|
|
|
marker::PhantomData,
|
|
|
|
net::SocketAddr
|
|
|
|
};
|
2016-03-29 08:45:54 +00:00
|
|
|
|
|
|
|
use fnv::FnvHasher;
|
2015-11-19 15:34:20 +00:00
|
|
|
|
2019-12-04 08:32:35 +00:00
|
|
|
use super::{
|
|
|
|
types::{Address, Error, Protocol, Table},
|
|
|
|
util::{Duration, Time, TimeSource}
|
|
|
|
};
|
2015-11-22 16:28:04 +00:00
|
|
|
|
2016-06-26 17:18:38 +00:00
|
|
|
/// An ethernet frame dissector
|
|
|
|
///
|
|
|
|
/// This dissector is able to extract the source and destination addresses of ethernet frames.
|
|
|
|
///
|
|
|
|
/// If the ethernet frame contains a VLAN tag, both addresses will be prefixed with that tag,
|
|
|
|
/// resulting in 8-byte addresses. Additional nested tags will be ignored.
|
2015-11-22 17:05:15 +00:00
|
|
|
pub struct Frame;
|
2015-11-21 17:09:13 +00:00
|
|
|
|
2015-11-22 17:05:15 +00:00
|
|
|
impl Protocol for Frame {
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Parses an ethernet frame and extracts the source and destination addresses
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
/// This method will fail when the given data is not a valid ethernet frame.
|
2015-11-22 23:49:58 +00:00
|
|
|
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
2015-11-22 15:48:01 +00:00
|
|
|
if data.len() < 14 {
|
2019-12-04 08:32:35 +00:00
|
|
|
return Err(Error::Parse("Frame is too short"))
|
2015-11-22 15:48:01 +00:00
|
|
|
}
|
|
|
|
let mut pos = 0;
|
2019-12-04 08:32:35 +00:00
|
|
|
let dst_data = &data[pos..pos + 6];
|
2015-11-22 23:49:58 +00:00
|
|
|
pos += 6;
|
2019-12-04 08:32:35 +00:00
|
|
|
let src_data = &data[pos..pos + 6];
|
2015-11-22 23:49:58 +00:00
|
|
|
pos += 6;
|
2019-12-04 08:32:35 +00:00
|
|
|
if data[pos] == 0x81 && data[pos + 1] == 0x00 {
|
2015-11-22 15:48:01 +00:00
|
|
|
pos += 2;
|
|
|
|
if data.len() < pos + 2 {
|
2019-12-04 08:32:35 +00:00
|
|
|
return Err(Error::Parse("Vlan frame is too short"))
|
2015-11-22 15:48:01 +00:00
|
|
|
}
|
2015-11-25 20:05:11 +00:00
|
|
|
let mut src = [0; 16];
|
|
|
|
let mut dst = [0; 16];
|
2019-12-04 08:32:35 +00:00
|
|
|
src[0] = data[pos];
|
|
|
|
src[1] = data[pos + 1];
|
|
|
|
dst[0] = data[pos];
|
|
|
|
dst[1] = data[pos + 1];
|
2016-07-06 16:48:58 +00:00
|
|
|
src[2..8].copy_from_slice(src_data);
|
|
|
|
dst[2..8].copy_from_slice(dst_data);
|
2019-12-04 08:32:35 +00:00
|
|
|
Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 }))
|
2015-11-22 23:49:58 +00:00
|
|
|
} else {
|
2019-03-01 22:12:19 +00:00
|
|
|
let src = Address::read_from_fixed(src_data, 6)?;
|
|
|
|
let dst = Address::read_from_fixed(dst_data, 6)?;
|
2015-11-26 09:45:25 +00:00
|
|
|
Ok((src, dst))
|
2015-11-22 15:48:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-22 16:28:04 +00:00
|
|
|
|
2015-11-23 00:40:47 +00:00
|
|
|
struct SwitchTableValue {
|
2015-11-21 15:50:50 +00:00
|
|
|
address: SocketAddr,
|
2015-11-25 20:55:30 +00:00
|
|
|
timeout: Time
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
|
|
|
|
2016-03-29 08:45:54 +00:00
|
|
|
type Hash = BuildHasherDefault<FnvHasher>;
|
|
|
|
|
2016-06-26 17:18:38 +00:00
|
|
|
|
|
|
|
/// A table used to implement a learning switch
|
|
|
|
///
|
|
|
|
/// This table is a simple hash map between an address and the destination peer. It learns
|
|
|
|
/// addresses as they are seen and forgets them after some time.
|
2019-02-24 19:01:32 +00:00
|
|
|
pub struct SwitchTable<TS> {
|
2016-06-26 17:18:38 +00:00
|
|
|
/// The table storing the actual mapping
|
2016-03-29 08:45:54 +00:00
|
|
|
table: HashMap<Address, SwitchTableValue, Hash>,
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Timeout period for forgetting learnt addresses
|
2016-11-28 11:14:56 +00:00
|
|
|
timeout: Duration,
|
|
|
|
// Timeout period for not overwriting learnt addresses
|
|
|
|
protection_period: Duration,
|
2019-02-24 19:01:32 +00:00
|
|
|
_dummy_ts: PhantomData<TS>
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
|
|
|
|
2019-02-24 19:01:32 +00:00
|
|
|
impl<TS: TimeSource> SwitchTable<TS> {
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Creates a new switch table
|
2016-11-28 11:14:56 +00:00
|
|
|
pub fn new(timeout: Duration, protection_period: Duration) -> Self {
|
2019-12-04 08:32:35 +00:00
|
|
|
Self { table: HashMap::default(), timeout, protection_period, _dummy_ts: PhantomData }
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
2015-11-21 17:09:13 +00:00
|
|
|
}
|
|
|
|
|
2019-02-24 19:01:32 +00:00
|
|
|
impl<TS: TimeSource> Table for SwitchTable<TS> {
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Forget addresses that have not been seen for the configured timeout
|
2015-11-21 17:09:13 +00:00
|
|
|
fn housekeep(&mut self) {
|
2019-02-24 19:01:32 +00:00
|
|
|
let now = TS::now();
|
2015-11-22 23:49:58 +00:00
|
|
|
let mut del: Vec<Address> = Vec::new();
|
|
|
|
for (key, val) in &self.table {
|
2015-11-21 15:50:50 +00:00
|
|
|
if val.timeout < now {
|
2016-06-11 14:08:57 +00:00
|
|
|
del.push(*key);
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for key in del {
|
2015-11-26 21:16:51 +00:00
|
|
|
info!("Forgot address {}", key);
|
2015-11-21 15:50:50 +00:00
|
|
|
self.table.remove(&key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-09 16:45:12 +00:00
|
|
|
/// Write out the table
|
|
|
|
fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
|
2019-02-24 19:01:32 +00:00
|
|
|
let now = TS::now();
|
2019-03-01 22:12:19 +00:00
|
|
|
writeln!(out, "Switch table:")?;
|
2019-01-09 16:45:12 +00:00
|
|
|
for (addr, val) in &self.table {
|
2019-03-01 22:12:19 +00:00
|
|
|
writeln!(out, " - {} => {} (ttl: {} s)", addr, val.address, val.timeout - now)?;
|
2019-01-09 16:45:12 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Learns the given address, inserting it in the hash map
|
2015-11-25 20:05:11 +00:00
|
|
|
#[inline]
|
2015-11-22 23:49:58 +00:00
|
|
|
fn learn(&mut self, key: Address, _prefix_len: Option<u8>, addr: SocketAddr) {
|
2019-02-24 19:01:32 +00:00
|
|
|
let deadline = TS::now() + Time::from(self.timeout);
|
2016-08-30 06:52:22 +00:00
|
|
|
match self.table.entry(key) {
|
|
|
|
Entry::Vacant(entry) => {
|
2019-12-04 08:32:35 +00:00
|
|
|
entry.insert(SwitchTableValue { address: addr, timeout: deadline });
|
2016-08-30 06:52:22 +00:00
|
|
|
info!("Learned address {} => {}", key, addr);
|
2019-12-04 08:32:35 +00:00
|
|
|
}
|
2016-08-30 06:52:22 +00:00
|
|
|
Entry::Occupied(mut entry) => {
|
|
|
|
let mut entry = entry.get_mut();
|
2019-01-01 23:35:14 +00:00
|
|
|
if entry.timeout + Time::from(self.protection_period) > deadline {
|
2016-08-30 06:52:22 +00:00
|
|
|
// Do not override recently learnt entries
|
|
|
|
return
|
|
|
|
}
|
|
|
|
entry.timeout = deadline;
|
|
|
|
entry.address = addr;
|
|
|
|
}
|
2015-11-25 18:23:25 +00:00
|
|
|
}
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
|
|
|
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Retrieves a peer for an address if it is inside the hash map
|
2015-11-25 20:05:11 +00:00
|
|
|
#[inline]
|
2015-11-25 18:23:25 +00:00
|
|
|
fn lookup(&mut self, key: &Address) -> Option<SocketAddr> {
|
|
|
|
match self.table.get(key) {
|
|
|
|
Some(value) => Some(value.address),
|
|
|
|
None => None
|
|
|
|
}
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
2015-11-22 21:00:34 +00:00
|
|
|
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Removes an address from the map and returns whether something has been removed
|
2016-03-29 11:54:28 +00:00
|
|
|
#[inline]
|
|
|
|
fn remove(&mut self, key: &Address) -> bool {
|
|
|
|
self.table.remove(key).is_some()
|
|
|
|
}
|
|
|
|
|
2016-06-26 17:18:38 +00:00
|
|
|
/// Removed all addresses associated with a certain peer
|
2016-03-29 11:54:28 +00:00
|
|
|
fn remove_all(&mut self, addr: &SocketAddr) {
|
|
|
|
let mut remove = Vec::new();
|
2016-06-11 14:08:57 +00:00
|
|
|
for (key, val) in &self.table {
|
2016-03-29 11:54:28 +00:00
|
|
|
if &val.address == addr {
|
2016-06-11 14:08:57 +00:00
|
|
|
remove.push(*key);
|
2016-03-29 11:54:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for key in remove {
|
|
|
|
self.table.remove(&key);
|
|
|
|
}
|
2015-11-22 21:00:34 +00:00
|
|
|
}
|
2015-11-21 15:50:50 +00:00
|
|
|
}
|
2019-02-19 17:42:50 +00:00
|
|
|
|
|
|
|
|
2019-03-01 15:36:18 +00:00
|
|
|
#[cfg(test)] use super::util::MockTimeSource;
|
2019-12-04 08:32:35 +00:00
|
|
|
#[cfg(test)] use std::net::ToSocketAddrs;
|
|
|
|
#[cfg(test)] use std::str::FromStr;
|
2019-02-19 17:42:50 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn decode_frame_without_vlan() {
|
2019-12-04 08:32:35 +00:00
|
|
|
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8];
|
2019-02-19 17:42:50 +00:00
|
|
|
let (src, dst) = Frame::parse(&data).unwrap();
|
2019-12-04 08:32:35 +00:00
|
|
|
assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 6 });
|
|
|
|
assert_eq!(dst, Address { data: [6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 6 });
|
2019-02-19 17:42:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn decode_frame_with_vlan() {
|
2019-12-04 08:32:35 +00:00
|
|
|
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0, 4, 210, 1, 2, 3, 4, 5, 6, 7, 8];
|
2019-02-19 17:42:50 +00:00
|
|
|
let (src, dst) = Frame::parse(&data).unwrap();
|
2019-12-04 08:32:35 +00:00
|
|
|
assert_eq!(src, Address { data: [4, 210, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0], len: 8 });
|
|
|
|
assert_eq!(dst, Address { data: [4, 210, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], len: 8 });
|
2019-02-19 17:42:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn decode_invalid_frame() {
|
2019-12-04 08:32:35 +00:00
|
|
|
assert!(Frame::parse(&[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]).is_ok());
|
2019-02-19 17:42:50 +00:00
|
|
|
// truncated frame
|
|
|
|
assert!(Frame::parse(&[]).is_err());
|
|
|
|
// truncated vlan frame
|
2019-12-04 08:32:35 +00:00
|
|
|
assert!(Frame::parse(&[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0x00]).is_err());
|
2019-02-19 17:42:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn switch() {
|
2019-02-24 19:01:32 +00:00
|
|
|
MockTimeSource::set_time(1000);
|
|
|
|
let mut table = SwitchTable::<MockTimeSource>::new(10, 1);
|
2019-02-19 17:42:50 +00:00
|
|
|
let addr = Address::from_str("12:34:56:78:90:ab").unwrap();
|
|
|
|
let peer = "1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
let peer2 = "1.2.3.5:7890".to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
assert!(table.lookup(&addr).is_none());
|
2019-02-24 19:01:32 +00:00
|
|
|
MockTimeSource::set_time(1000);
|
2020-05-28 06:57:49 +00:00
|
|
|
table.learn(addr, None, peer);
|
2019-02-19 17:42:50 +00:00
|
|
|
assert_eq!(table.lookup(&addr), Some(peer));
|
2019-02-24 19:01:32 +00:00
|
|
|
MockTimeSource::set_time(1000);
|
2020-05-28 06:57:49 +00:00
|
|
|
table.learn(addr, None, peer2);
|
2019-02-19 17:42:50 +00:00
|
|
|
assert_eq!(table.lookup(&addr), Some(peer));
|
2019-02-24 19:01:32 +00:00
|
|
|
MockTimeSource::set_time(1010);
|
2020-05-28 06:57:49 +00:00
|
|
|
table.learn(addr, None, peer2);
|
2019-02-19 17:42:50 +00:00
|
|
|
assert_eq!(table.lookup(&addr), Some(peer2));
|
|
|
|
}
|