use std::{
net::{IpAddr, Ipv4Addr},
time::Duration,
};
use tokio::time::advance;
use crate::{
ban_list::BanList,
constants::{BAN_DURATION, MAX_BANNED_IPS},
};
#[test]
fn bans_apply_to_the_peer_group() {
let _init_guard = zebra_test::init();
let mut bans = BanList::default();
let banned_v6: IpAddr = "2001:db8::1".parse().unwrap();
bans.ban(banned_v6);
assert!(bans.is_banned(banned_v6), "the banned address is banned");
assert!(
bans.is_banned("2001:db8::2".parse().unwrap()),
"another address in the banned /64 is banned"
);
assert!(
!bans.is_banned("2001:db9::1".parse().unwrap()),
"an address in a different /64 is not banned"
);
let banned_v4: IpAddr = "192.0.2.10".parse().unwrap();
bans.ban(banned_v4);
assert!(
bans.is_banned(banned_v4),
"the banned IPv4 address is banned"
);
assert!(
!bans.is_banned("192.0.2.11".parse().unwrap()),
"a neighbouring IPv4 address in the same /24 is not banned"
);
}
#[test]
fn bans_cover_both_ipv4_spellings() {
let _init_guard = zebra_test::init();
let plain: IpAddr = "192.0.2.10".parse().unwrap();
let mapped: IpAddr = "::ffff:192.0.2.10".parse().unwrap();
let mut bans = BanList::default();
bans.ban(plain);
assert!(
bans.is_banned(mapped),
"the IPv4-mapped spelling should be covered by the plain IPv4 ban"
);
let mut bans = BanList::default();
bans.ban(mapped);
assert!(
bans.is_banned(plain),
"the plain spelling should be covered by the IPv4-mapped ban"
);
}
#[tokio::test(start_paused = true)]
async fn bans_lapse_and_are_pruned() {
let _init_guard = zebra_test::init();
let mut bans = BanList::default();
let lapsed: IpAddr = "2001:db8::1".parse().unwrap();
bans.ban(lapsed);
advance(BAN_DURATION - Duration::from_nanos(1)).await;
assert!(
bans.is_banned(lapsed),
"a ban should be enforced right up to BAN_DURATION"
);
advance(Duration::from_nanos(1)).await;
assert!(
!bans.is_banned(lapsed),
"a ban should lapse at exactly BAN_DURATION"
);
assert_eq!(
bans.len(),
1,
"the lapsed entry is still present until pruned"
);
let active: IpAddr = "2001:db8:1::1".parse().unwrap();
bans.ban(active);
assert!(bans.is_banned(active), "the new ban is enforced");
assert_eq!(bans.len(), 1, "the lapsed ban should have been pruned");
assert!(!bans.is_banned(lapsed));
}
#[tokio::test(start_paused = true)]
async fn re_banning_refreshes_the_ban() {
let _init_guard = zebra_test::init();
let mut bans = BanList::default();
let banned: IpAddr = "2001:db8::1".parse().unwrap();
bans.ban(banned);
let remaining = Duration::from_secs(60);
advance(BAN_DURATION - remaining).await;
bans.ban("2001:db8::2".parse().unwrap());
assert_eq!(bans.len(), 1, "the sibling shares the group's entry");
assert!(bans.is_banned(banned), "the refreshed ban is enforced");
assert!(
bans.is_banned("2001:db8::3".parse().unwrap()),
"every address in the group stays banned"
);
let time_after_first_lapse = Duration::from_nanos(1);
advance(remaining + time_after_first_lapse).await;
assert!(
bans.is_banned(banned),
"a refreshed ban must outlive the original ban's deadline"
);
advance(BAN_DURATION - remaining - time_after_first_lapse - Duration::from_nanos(1)).await;
assert!(
bans.is_banned(banned),
"the refreshed ban should be enforced right up to its new deadline"
);
advance(Duration::from_nanos(1) + time_after_first_lapse).await;
assert!(
!bans.is_banned(banned),
"the refreshed ban should lapse BAN_DURATION after the re-ban"
);
}
#[tokio::test(start_paused = true)]
async fn snapshots_stop_enforcing_lapsed_bans() {
let _init_guard = zebra_test::init();
let mut bans = BanList::default();
let banned: IpAddr = "2001:db8::1".parse().unwrap();
bans.ban(banned);
let snapshot = bans.clone();
assert!(
snapshot.is_banned(banned),
"the snapshot enforces the active ban"
);
advance(BAN_DURATION).await;
assert!(
!snapshot.is_banned(banned),
"a snapshot must not enforce a ban that has lapsed"
);
assert_eq!(
snapshot.len(),
1,
"the snapshot still holds the entry: it checks the age on each query \
rather than being pruned"
);
}
#[tokio::test(start_paused = true)]
async fn a_full_ban_list_evicts_the_oldest_ban() {
let _init_guard = zebra_test::init();
let mut bans = BanList::default();
let time_between_bans = Duration::from_nanos(1) + Duration::from_nanos(1);
let oldest: IpAddr = "192.0.2.1".parse().unwrap();
bans.ban(oldest);
let refreshed: IpAddr = "192.0.2.2".parse().unwrap();
advance(time_between_bans).await;
bans.ban(refreshed);
advance(time_between_bans).await;
let filler = (0..MAX_BANNED_IPS - 2)
.map(|i| u32::try_from(i).expect("MAX_BANNED_IPS fits in a u32"))
.map(|i| IpAddr::from(Ipv4Addr::from(u32::from(Ipv4Addr::new(10, 0, 0, 0)) + i)));
for ip in filler {
bans.ban(ip);
}
assert_eq!(
bans.len(),
MAX_BANNED_IPS,
"the list should be exactly full"
);
advance(time_between_bans).await;
bans.ban(refreshed);
assert_eq!(
bans.len(),
MAX_BANNED_IPS,
"refreshing must not add an entry"
);
advance(time_between_bans).await;
let newest: IpAddr = "192.0.2.3".parse().unwrap();
bans.ban(newest);
assert_eq!(
bans.len(),
MAX_BANNED_IPS,
"the list must stay at the limit"
);
assert!(!bans.is_banned(oldest), "the oldest ban should be evicted");
assert!(
bans.is_banned(refreshed),
"a refreshed ban must not be evicted as if it kept its original age"
);
assert!(bans.is_banned(newest), "the new ban is enforced");
}
#[test]
fn empty_ban_list_bans_nothing() {
let _init_guard = zebra_test::init();
let bans = BanList::default();
assert!(bans.is_empty());
assert_eq!(bans.len(), 0);
assert!(!bans.is_banned("192.0.2.1".parse().unwrap()));
assert!(!bans.is_banned("2001:db8::1".parse().unwrap()));
}