Make IpPrefix a fixed-size value with no allocation behind it

A prefix was a Vec of its bytes: a heap allocation for every prefix parsed,
stored or copied, and a variable-length key to hash. It is now twenty
bytes, Copy, with private fields and one representation; `bytes()` gives
the wire form.

On the full-table benchmark this alone takes decoding from 9.3 to 10.7
million routes a second and inserting into the Adj-RIB-In from 2.9 to 3.6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-01 23:38:39 +02:00
1 parent 64df419b3a
commit 7e488e31aa
15 files changed
+122 -129

No files matched your search

+3 -3
View File
@@ -294,10 +294,10 @@ fn floor(prefixes: &Prefixes, per_peer: &[Vec<UpdateMessage>], routes: usize) {
fn key(prefix: &IpPrefix) -> u128 { fn key(prefix: &IpPrefix) -> u128 {
let mut address = [0u8; 16]; let mut address = [0u8; 16];
address[..prefix.prefix.len()].copy_from_slice(&prefix.prefix); address[..prefix.bytes().len()].copy_from_slice(prefix.bytes());
// The address in the high bits, the length in the low eight. An IPv6 // The address in the high bits, the length in the low eight. An IPv6
// prefix longer than /120 would not fit; the table has none. // prefix longer than /120 would not fit; the table has none.
(u128::from_be_bytes(address) & !0xff) | u128::from(prefix.length) (u128::from_be_bytes(address) & !0xff) | u128::from(prefix.length())
} }
println!("\n== Straw man: packed prefix -> attribute set number, no actor"); println!("\n== Straw man: packed prefix -> attribute set number, no actor");
@@ -310,7 +310,7 @@ fn floor(prefixes: &Prefixes, per_peer: &[Vec<UpdateMessage>], routes: usize) {
for (number, update) in updates.iter().enumerate() { for (number, update) in updates.iter().enumerate() {
for prefix in &update.announced_routes { for prefix in &update.announced_routes {
v4.insert( v4.insert(
(key(prefix) >> 64) as u64 | u64::from(prefix.length), (key(prefix) >> 64) as u64 | u64::from(prefix.length()),
number as u32, number as u32,
); );
} }
+4
View File
@@ -0,0 +1,4 @@
# Table dumps downloaded for benchmarks; see README.md.
*
!.gitignore
!README.md
+6 -14
View File
@@ -130,13 +130,9 @@ impl Prefixes {
let length = rng.pick(&v4_lengths); let length = rng.pick(&v4_lengths);
// Unicast space, 1.0.0.0 to 223.255.255.255. // Unicast space, 1.0.0.0 to 223.255.255.255.
let address = 0x0100_0000 + (rng.next_u64() as u32) % 0xdf00_0000; let address = 0x0100_0000 + (rng.next_u64() as u32) % 0xdf00_0000;
let prefix = IpPrefix::new( let prefix = IpPrefix::new(AddressFamilyId::Ipv4, address.to_be_bytes(), length)
AddressFamilyId::Ipv4, .expect("four bytes cover any IPv4 prefix");
address.to_be_bytes().to_vec(), if seen.insert(prefix) {
length,
)
.expect("four bytes cover any IPv4 prefix");
if seen.insert(prefix.clone()) {
ipv4.push(prefix); ipv4.push(prefix);
} }
} }
@@ -145,13 +141,9 @@ impl Prefixes {
let length = rng.pick(&v6_lengths); let length = rng.pick(&v6_lengths);
// Global unicast, 2000::/3. // Global unicast, 2000::/3.
let address = (0x2000_u128 << 112) | (u128::from(rng.next_u64()) << 61 >> 3); let address = (0x2000_u128 << 112) | (u128::from(rng.next_u64()) << 61 >> 3);
let prefix = IpPrefix::new( let prefix = IpPrefix::new(AddressFamilyId::Ipv6, address.to_be_bytes(), length)
AddressFamilyId::Ipv6, .expect("sixteen bytes cover any IPv6 prefix");
address.to_be_bytes().to_vec(), if seen.insert(prefix) {
length,
)
.expect("sixteen bytes cover any IPv6 prefix");
if seen.insert(prefix.clone()) {
ipv6.push(prefix); ipv6.push(prefix);
} }
} }
@@ -88,7 +88,7 @@ async fn run(args: &Args, mut peer: TestPeer, addr: std::net::SocketAddr) -> Res
for p in &args.announce { for p in &args.announce {
let prefix = IpPrefix::try_from(p.as_str())?; let prefix = IpPrefix::try_from(p.as_str())?;
let update = announce(prefix.clone(), vec![args.asn], vec![(args.asn as u16, 1)]); let update = announce(prefix, vec![args.asn], vec![(args.asn as u16, 1)]);
peer.send(&Message::Update(update)).await?; peer.send(&Message::Update(update)).await?;
println!("announced {prefix}"); println!("announced {prefix}");
} }
@@ -157,7 +157,7 @@ async fn echo_back(peer: &mut TestPeer, u: &UpdateMessage, asn: u32) -> Result<(
} }
for prefix in &u.announced_routes { for prefix in &u.announced_routes {
peer.send(&Message::Update(announce( peer.send(&Message::Update(announce(
prefix.clone(), *prefix,
as_path.clone(), as_path.clone(),
communities.clone(), communities.clone(),
))) )))
@@ -218,7 +218,7 @@ async fn test_message_split_across_a_keepalive_tick_is_still_one_message() -> Re
Message::Update(UpdateMessage { Message::Update(UpdateMessage {
withdrawn_routes: vec![], withdrawn_routes: vec![],
path_attributes: mandatory_attributes(), path_attributes: mandatory_attributes(),
announced_routes: vec![prefix.clone()], announced_routes: vec![prefix],
}) })
.to_wire(&peer.ctx, &mut update)?; .to_wire(&peer.ctx, &mut update)?;
@@ -403,7 +403,7 @@ async fn test_rib_records_ipv4_announce() -> Result<()> {
peer.send(&Message::Update(UpdateMessage { peer.send(&Message::Update(UpdateMessage {
withdrawn_routes: vec![], withdrawn_routes: vec![],
path_attributes: mandatory_attributes(), path_attributes: mandatory_attributes(),
announced_routes: vec![prefix.clone()], announced_routes: vec![prefix],
})) }))
.await?; .await?;
@@ -437,14 +437,14 @@ async fn test_rib_records_ipv4_withdraw() -> Result<()> {
peer.send(&Message::Update(UpdateMessage { peer.send(&Message::Update(UpdateMessage {
withdrawn_routes: vec![], withdrawn_routes: vec![],
path_attributes: mandatory_attributes(), path_attributes: mandatory_attributes(),
announced_routes: vec![prefix.clone()], announced_routes: vec![prefix],
})) }))
.await?; .await?;
tokio::time::timeout(Duration::from_secs(1), events.recv()).await??; tokio::time::timeout(Duration::from_secs(1), events.recv()).await??;
// Now withdraw. // Now withdraw.
peer.send(&Message::Update(UpdateMessage { peer.send(&Message::Update(UpdateMessage {
withdrawn_routes: vec![prefix.clone()], withdrawn_routes: vec![prefix],
path_attributes: vec![], path_attributes: vec![],
announced_routes: vec![], announced_routes: vec![],
})) }))
@@ -482,7 +482,7 @@ async fn test_rib_records_ipv6_announce() -> Result<()> {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
next_hop: NlriNextHop::Ipv6("2001:db8::1".parse().unwrap()), next_hop: NlriNextHop::Ipv6("2001:db8::1".parse().unwrap()),
prefixes: vec![prefix.clone()], prefixes: vec![prefix],
}, },
)), )),
announced_routes: vec![], announced_routes: vec![],
@@ -523,7 +523,7 @@ async fn test_rib_records_ipv6_withdraw() -> Result<()> {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
next_hop: NlriNextHop::Ipv6("2001:db8::1".parse().unwrap()), next_hop: NlriNextHop::Ipv6("2001:db8::1".parse().unwrap()),
prefixes: vec![prefix.clone()], prefixes: vec![prefix],
}, },
)), )),
announced_routes: vec![], announced_routes: vec![],
@@ -537,7 +537,7 @@ async fn test_rib_records_ipv6_withdraw() -> Result<()> {
path_attributes: vec![PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute { path_attributes: vec![PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
prefixes: vec![prefix.clone()], prefixes: vec![prefix],
})], })],
announced_routes: vec![], announced_routes: vec![],
})) }))
+21 -29
View File
@@ -302,8 +302,8 @@ impl UpdateMessage {
let withdrawn_len = prefix_list_wire_len(&self.withdrawn_routes)?; let withdrawn_len = prefix_list_wire_len(&self.withdrawn_routes)?;
out.put_u16(withdrawn_len); out.put_u16(withdrawn_len);
for prefix in &self.withdrawn_routes { for prefix in &self.withdrawn_routes {
out.put_u8(prefix.length); out.put_u8(prefix.length());
out.put(prefix.prefix.as_slice()); out.put(prefix.bytes());
} }
// Path attributes. // Path attributes.
@@ -321,8 +321,8 @@ impl UpdateMessage {
// NLRI (consumes the remainder of the message). // NLRI (consumes the remainder of the message).
for prefix in &self.announced_routes { for prefix in &self.announced_routes {
out.put_u8(prefix.length); out.put_u8(prefix.length());
out.put(prefix.prefix.as_slice()); out.put(prefix.bytes());
} }
Ok(()) Ok(())
@@ -333,13 +333,13 @@ impl UpdateMessage {
// attribute length field. // attribute length field.
let mut total: u32 = 4; let mut total: u32 = 4;
for prefix in &self.withdrawn_routes { for prefix in &self.withdrawn_routes {
total += 1 + prefix.prefix.len() as u32; total += 1 + prefix.bytes().len() as u32;
} }
for pa in &self.path_attributes { for pa in &self.path_attributes {
total += pa.wire_len(ctx)? as u32; total += pa.wire_len(ctx)? as u32;
} }
for prefix in &self.announced_routes { for prefix in &self.announced_routes {
total += 1 + prefix.prefix.len() as u32; total += 1 + prefix.bytes().len() as u32;
} }
u16::try_from(total).map_err(|_| too_long("UPDATE message", total)) u16::try_from(total).map_err(|_| too_long("UPDATE message", total))
} }
@@ -349,7 +349,7 @@ impl UpdateMessage {
/// appear in the withdrawn or NLRI sections of an UPDATE message (each prefix /// appear in the withdrawn or NLRI sections of an UPDATE message (each prefix
/// is encoded as a 1-byte length followed by `ceil(length/8)` prefix bytes). /// is encoded as a 1-byte length followed by `ceil(length/8)` prefix bytes).
fn prefix_list_wire_len(prefixes: &[IpPrefix]) -> Result<u16, EncodeError> { fn prefix_list_wire_len(prefixes: &[IpPrefix]) -> Result<u16, EncodeError> {
let total: u32 = prefixes.iter().map(|p| 1 + p.prefix.len() as u32).sum(); let total: u32 = prefixes.iter().map(|p| 1 + p.bytes().len() as u32).sum();
u16::try_from(total).map_err(|_| too_long("prefix list", total)) u16::try_from(total).map_err(|_| too_long("prefix list", total))
} }
@@ -1472,7 +1472,7 @@ pub(crate) fn parse_prefix(
let (buf, prefix_len) = be_u8(buf)?; let (buf, prefix_len) = be_u8(buf)?;
let byte_len = prefix_len.div_ceil(8); let byte_len = prefix_len.div_ceil(8);
let (buf, prefix_bytes) = nom::bytes::take(byte_len as usize).parse(buf)?; let (buf, prefix_bytes) = nom::bytes::take(byte_len as usize).parse(buf)?;
let prefix = IpPrefix::new(afi, prefix_bytes.to_vec(), prefix_len) let prefix = IpPrefix::new(afi, prefix_bytes, prefix_len)
.map_err(|e| Failure(BgpParserError::Malformed(e.to_string())))?; .map_err(|e| Failure(BgpParserError::Malformed(e.to_string())))?;
Ok((buf, prefix)) Ok((buf, prefix))
} }
@@ -1574,8 +1574,8 @@ impl MpReachNlriPathAttribute {
self.next_hop.to_wire(ctx, out)?; self.next_hop.to_wire(ctx, out)?;
out.put_u8(0); out.put_u8(0);
for prefix in &self.prefixes { for prefix in &self.prefixes {
out.put_u8(prefix.length); out.put_u8(prefix.length());
out.put(&prefix.prefix[..]); out.put(prefix.bytes());
} }
Ok(()) Ok(())
} }
@@ -1587,7 +1587,7 @@ impl MpReachNlriPathAttribute {
+ self + self
.prefixes .prefixes
.iter() .iter()
.map(|p| 1 + p.prefix.len() as u16) .map(|p| 1 + p.bytes().len() as u16)
.sum::<u16>()) .sum::<u16>())
} }
} }
@@ -1628,8 +1628,8 @@ impl MpUnreachNlriPathAttribute {
out.put_u16(self.afi.0); out.put_u16(self.afi.0);
out.put_u8(self.safi.0); out.put_u8(self.safi.0);
for prefix in &self.prefixes { for prefix in &self.prefixes {
out.put_u8(prefix.length); out.put_u8(prefix.length());
out.put(&prefix.prefix[..]); out.put(prefix.bytes());
} }
Ok(()) Ok(())
} }
@@ -1638,7 +1638,7 @@ impl MpUnreachNlriPathAttribute {
Ok(3 + self Ok(3 + self
.prefixes .prefixes
.iter() .iter()
.map(|p| 1 + p.prefix.len() as u16) .map(|p| 1 + p.bytes().len() as u16)
.sum::<u16>()) .sum::<u16>())
} }
} }
@@ -1861,11 +1861,9 @@ mod tests {
global: ipv6!("2001:db8::1"), global: ipv6!("2001:db8::1"),
link_local: ipv6!("fe80::12"), link_local: ipv6!("fe80::12"),
}, },
prefixes: vec![IpPrefix { prefixes: vec![
address_family: AddressFamilyId::Ipv6, IpPrefix::new(AddressFamilyId::Ipv6, [0x20, 0x01, 0x0d, 0xb8], 32).unwrap()
prefix: vec![0x20, 0x01, 0x0d, 0xb8], ]
length: 32,
}]
}) })
); );
@@ -1878,11 +1876,9 @@ mod tests {
PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute { PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
prefixes: vec![IpPrefix { prefixes: vec![
address_family: AddressFamilyId::Ipv6, IpPrefix::new(AddressFamilyId::Ipv6, [0x20, 0x01, 0x0d, 0xb8], 32).unwrap()
prefix: vec![0x20, 0x01, 0x0d, 0xb8], ],
length: 32,
}],
}) })
); );
@@ -1973,11 +1969,7 @@ mod tests {
// Wrapper to generate a IPv4 prefix used in tests below. // Wrapper to generate a IPv4 prefix used in tests below.
fn v4_prefix(length: u8, bytes: &[u8]) -> IpPrefix { fn v4_prefix(length: u8, bytes: &[u8]) -> IpPrefix {
IpPrefix { IpPrefix::new(AddressFamilyId::Ipv4, bytes, length).unwrap()
address_family: AddressFamilyId::Ipv4,
prefix: bytes.to_vec(),
length,
}
} }
/// A small set of path attributes that commonly accompany an IPv4 unicast /// A small set of path attributes that commonly accompany an IPv4 unicast
+1 -1
View File
@@ -841,7 +841,7 @@ mod tests {
[PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute { [PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
prefixes: vec![v6.clone()], prefixes: vec![v6],
})], })],
"{case}" "{case}"
); );
+1 -1
View File
@@ -69,7 +69,7 @@ fn prefix(version: AddressFamilyId) -> impl Strategy<Value = IpPrefix> {
AddressFamilyId::Ipv6 => 128, AddressFamilyId::Ipv6 => 128,
}; };
(0..=max_len, any::<[u8; 16]>()).prop_map(move |(length, bytes)| { (0..=max_len, any::<[u8; 16]>()).prop_map(move |(length, bytes)| {
IpPrefix::new(version, bytes.to_vec(), length).expect("16 bytes are enough for any prefix") IpPrefix::new(version, bytes, length).expect("16 bytes are enough for any prefix")
}) })
} }
+62 -57
View File
@@ -26,25 +26,35 @@ pub enum PrefixError {
}, },
} }
/// IpPrefix represents some IP address prefix, for a specific AddressFamilyId. /// An IPv4 or IPv6 prefix.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] ///
/// It is a small value with no allocation behind it, cheap to copy, compare
/// and hash: a routing table holds millions of them and looks them up by
/// value. A prefix has one representation: the bits past its length are
/// always zero, so two prefixes are equal exactly when they are the same
/// prefix.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
pub struct IpPrefix { pub struct IpPrefix {
pub address_family: AddressFamilyId, address_family: AddressFamilyId,
pub prefix: Vec<u8>, length: u8,
pub length: u8, /// The address, left-aligned, zero past `length` bits.
address: [u8; 16],
} }
impl IpPrefix { impl IpPrefix {
/// Makes a prefix from the leading bytes of its address. `prefix` must
/// hold at least the bytes that `length` bits cover; anything past
/// those bits is ignored (RFC 4271 §4.3: such bits are irrelevant).
pub fn new( pub fn new(
address_family: AddressFamilyId, address_family: AddressFamilyId,
prefix: Vec<u8>, prefix: impl AsRef<[u8]>,
length: u8, length: u8,
) -> Result<Self, PrefixError> { ) -> Result<Self, PrefixError> {
let prefix = prefix.as_ref();
let max = address_family.bits(); let max = address_family.bits();
if length > max { if length > max {
return Err(PrefixError::LengthTooLong { length, max }); return Err(PrefixError::LengthTooLong { length, max });
} }
// Ensure that the prefix we are given contains the right number of bytes corresponding to the prefix length.
let needed = usize::from(length.div_ceil(8)); let needed = usize::from(length.div_ceil(8));
if prefix.len() < needed { if prefix.len() < needed {
return Err(PrefixError::TooFewBytes { return Err(PrefixError::TooFewBytes {
@@ -53,26 +63,31 @@ impl IpPrefix {
got: prefix.len(), got: prefix.len(),
}); });
} }
Ok(Self::normalised(address_family, prefix, length)) let mut address = [0u8; 16];
} address[..needed].copy_from_slice(&prefix[..needed]);
if let (Some(last), rem @ 1..) = (address[..needed].last_mut(), length % 8) {
/// Builds the one representation of a prefix: exactly the bytes its
/// length covers, with the bits past the length cleared. Those bits mean
/// nothing (RFC 4271 §4.3), so two prefixes that differ only there are
/// the same prefix and must compare equal.
///
/// `length` must fit the address family and `prefix` must have at least
/// the bytes it covers.
fn normalised(address_family: AddressFamilyId, mut prefix: Vec<u8>, length: u8) -> Self {
prefix.truncate(usize::from(length.div_ceil(8)));
if let (Some(last), rem @ 1..) = (prefix.last_mut(), length % 8) {
*last &= u8::MAX << (8 - rem); *last &= u8::MAX << (8 - rem);
} }
Self { Ok(Self {
address_family, address_family,
prefix,
length, length,
} address,
})
}
pub fn address_family(&self) -> AddressFamilyId {
self.address_family
}
/// The prefix length in bits.
pub fn length(&self) -> u8 {
self.length
}
/// The bytes the prefix length covers, as they go on the wire: none for
/// a default route, three for a /24.
pub fn bytes(&self) -> &[u8] {
&self.address[..usize::from(self.length.div_ceil(8))]
} }
/// Returns true if `other` is equal to or more specific than `self`, i.e. /// Returns true if `other` is equal to or more specific than `self`, i.e.
@@ -82,8 +97,8 @@ impl IpPrefix {
if self.address_family != other.address_family || other.length < self.length { if self.address_family != other.address_family || other.length < self.length {
return false; return false;
} }
let full_bytes = (self.length / 8) as usize; let full_bytes = usize::from(self.length / 8);
if self.prefix[..full_bytes] != other.prefix[..full_bytes] { if self.address[..full_bytes] != other.address[..full_bytes] {
return false; return false;
} }
let rem_bits = self.length % 8; let rem_bits = self.length % 8;
@@ -91,28 +106,19 @@ impl IpPrefix {
return true; return true;
} }
let mask = u8::MAX << (8 - rem_bits); let mask = u8::MAX << (8 - rem_bits);
(self.prefix[full_bytes] & mask) == (other.prefix[full_bytes] & mask) (self.address[full_bytes] & mask) == (other.address[full_bytes] & mask)
}
/// The prefix as a whole address, zero where the prefix says nothing.
/// The fields are public, so this cannot assume `prefix` has a sensible
/// length: showing a prefix must never fail, whatever is in it.
fn padded<const N: usize>(&self) -> [u8; N] {
let mut address = [0u8; N];
let len = self.prefix.len().min(N);
address[..len].copy_from_slice(&self.prefix[..len]);
address
} }
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> {
let bytes = self.bytes();
// Verify that there is enough space to write the IpPrefix. // Verify that there is enough space to write the IpPrefix.
if out.remaining() < (self.prefix.len() + 1) { if out.remaining() < (bytes.len() + 1) {
return Err(EncodeError::OutBufferOverflow); return Err(EncodeError::OutBufferOverflow);
} }
// Write length and prefix. // Write length and prefix.
out.put_u8(self.length); out.put_u8(self.length);
out.put(self.prefix.as_slice()); out.put(bytes);
Ok(()) Ok(())
} }
@@ -142,10 +148,11 @@ impl Display for IpPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.address_family { match self.address_family {
AddressFamilyId::Ipv4 => { AddressFamilyId::Ipv4 => {
write!(f, "{}/{}", Ipv4Addr::from(self.padded()), self.length) let [a, b, c, d, ..] = self.address;
write!(f, "{}/{}", Ipv4Addr::new(a, b, c, d), self.length)
} }
AddressFamilyId::Ipv6 => { AddressFamilyId::Ipv6 => {
write!(f, "{}/{}", Ipv6Addr::from(self.padded()), self.length) write!(f, "{}/{}", Ipv6Addr::from(self.address), self.length)
} }
} }
} }
@@ -165,9 +172,9 @@ mod tests {
fn $name() { fn $name() {
let ip_prefix = IpPrefix::try_from($prefix_str).unwrap(); let ip_prefix = IpPrefix::try_from($prefix_str).unwrap();
assert_eq!(ip_prefix.address_family, $afi); assert_eq!(ip_prefix.address_family(), $afi);
assert_eq!(ip_prefix.prefix, $bytes); assert_eq!(ip_prefix.bytes(), $bytes);
assert_eq!(ip_prefix.length, $length); assert_eq!(ip_prefix.length(), $length);
let to_str: &str = &ip_prefix.to_string(); let to_str: &str = &ip_prefix.to_string();
assert_eq!(IpPrefix::try_from(to_str).unwrap(), ip_prefix); assert_eq!(IpPrefix::try_from(to_str).unwrap(), ip_prefix);
@@ -268,7 +275,7 @@ mod tests {
// Extra bytes and stray bits past the length are dropped. // Extra bytes and stray bits past the length are dropped.
let noisy = IpPrefix::new(AddressFamilyId::Ipv4, vec![10, 0xff, 3, 4], 12).unwrap(); let noisy = IpPrefix::new(AddressFamilyId::Ipv4, vec![10, 0xff, 3, 4], 12).unwrap();
assert_eq!(noisy, IpPrefix::try_from("10.240.0.0/12").unwrap()); assert_eq!(noisy, IpPrefix::try_from("10.240.0.0/12").unwrap());
assert_eq!(noisy.prefix, vec![10, 0xf0]); assert_eq!(noisy.bytes(), [10, 0xf0]);
assert_eq!( assert_eq!(
IpPrefix::new(AddressFamilyId::Ipv4, vec![0xff; 13], 99), IpPrefix::new(AddressFamilyId::Ipv4, vec![0xff; 13], 99),
Err(PrefixError::LengthTooLong { Err(PrefixError::LengthTooLong {
@@ -278,16 +285,14 @@ mod tests {
); );
} }
/// The fields are public, so a prefix can be put together wrongly.
/// Showing it must still not fail: prefixes are shown in log lines.
#[test] #[test]
fn showing_a_prefix_never_fails() { fn a_prefix_is_a_small_value() {
let wrong = IpPrefix { assert!(size_of::<IpPrefix>() <= 20);
address_family: AddressFamilyId::Ipv4, let prefix = IpPrefix::try_from("10.1.2.0/24").unwrap();
prefix: vec![1, 2, 3, 4, 5, 6], let copy = prefix;
length: 200, assert_eq!(copy, prefix);
}; assert_eq!(prefix.bytes(), [10, 1, 2]);
assert_eq!(wrong.to_string(), "1.2.3.4/200"); assert_eq!(IpPrefix::try_from("0.0.0.0/0").unwrap().bytes(), [0u8; 0]);
} }
proptest! { proptest! {
@@ -295,9 +300,9 @@ mod tests {
fn text_round_trips(version: bool, bytes: [u8; 16], length in 0u8..=128) { fn text_round_trips(version: bool, bytes: [u8; 16], length in 0u8..=128) {
let version = if version { AddressFamilyId::Ipv6 } else { AddressFamilyId::Ipv4 }; let version = if version { AddressFamilyId::Ipv6 } else { AddressFamilyId::Ipv4 };
let length = length.min(version.bits()); let length = length.min(version.bits());
let prefix = IpPrefix::new(version, bytes.to_vec(), length).unwrap(); let prefix = IpPrefix::new(version, bytes, length).unwrap();
let text = prefix.to_string(); let text = prefix.to_string();
prop_assert_eq!(IpPrefix::try_from(text.as_str()), Ok(prefix.clone())); prop_assert_eq!(IpPrefix::try_from(text.as_str()), Ok(prefix));
prop_assert!(prefix.contains(&prefix)); prop_assert!(prefix.contains(&prefix));
} }
@@ -314,8 +319,8 @@ mod tests {
fn contains_is_a_comparison_of_leading_bits( fn contains_is_a_comparison_of_leading_bits(
a: [u8; 4], a_len in 0u8..=32, b: [u8; 4], b_len in 0u8..=32, a: [u8; 4], a_len in 0u8..=32, b: [u8; 4], b_len in 0u8..=32,
) { ) {
let outer = IpPrefix::new(AddressFamilyId::Ipv4, a.to_vec(), a_len).unwrap(); let outer = IpPrefix::new(AddressFamilyId::Ipv4, a, a_len).unwrap();
let inner = IpPrefix::new(AddressFamilyId::Ipv4, b.to_vec(), b_len).unwrap(); let inner = IpPrefix::new(AddressFamilyId::Ipv4, b, b_len).unwrap();
let mask = if a_len == 0 { 0 } else { u32::MAX << (32 - a_len) }; let mask = if a_len == 0 { 0 } else { u32::MAX << (32 - a_len) };
let expected = b_len >= a_len let expected = b_len >= a_len
&& (u32::from_be_bytes(a) & mask) == (u32::from_be_bytes(b) & mask); && (u32::from_be_bytes(a) & mask) == (u32::from_be_bytes(b) & mask);
+2 -2
View File
@@ -143,7 +143,7 @@ async fn run_actor(mut rx: mpsc::Receiver<RibCommand>, events: broadcast::Sender
} }
} }
for (prefix, entry) in update.announced { for (prefix, entry) in update.announced {
routes.insert(prefix.clone(), entry.clone()); routes.insert(prefix, entry.clone());
let _ = events.send(RibEvent::Announced { let _ = events.send(RibEvent::Announced {
peer, peer,
prefix, prefix,
@@ -168,7 +168,7 @@ async fn run_actor(mut rx: mpsc::Receiver<RibCommand>, events: broadcast::Sender
.flat_map(|(peer, routes)| { .flat_map(|(peer, routes)| {
routes.iter().map(|(prefix, entry)| SnapshotEntry { routes.iter().map(|(prefix, entry)| SnapshotEntry {
peer: *peer, peer: *peer,
prefix: prefix.clone(), prefix: *prefix,
entry: entry.clone(), entry: entry.clone(),
}) })
}) })
+3 -3
View File
@@ -83,14 +83,14 @@ impl ReceivedUpdate {
let mut announced: Vec<(IpPrefix, RibEntry)> = update let mut announced: Vec<(IpPrefix, RibEntry)> = update
.announced_routes .announced_routes
.iter() .iter()
.map(|prefix| (prefix.clone(), entry(None))) .map(|prefix| (*prefix, entry(None)))
.collect(); .collect();
for attr in &update.path_attributes { for attr in &update.path_attributes {
match attr { match attr {
PathAttribute::MpReachNlri(mp) => announced.extend( PathAttribute::MpReachNlri(mp) => announced.extend(
mp.prefixes mp.prefixes
.iter() .iter()
.map(|prefix| (prefix.clone(), entry(Some(mp.next_hop.clone())))), .map(|prefix| (*prefix, entry(Some(mp.next_hop.clone())))),
), ),
PathAttribute::MpUnreachNlri(mp) => withdrawn.extend(mp.prefixes.iter().cloned()), PathAttribute::MpUnreachNlri(mp) => withdrawn.extend(mp.prefixes.iter().cloned()),
_ => {} _ => {}
@@ -184,7 +184,7 @@ mod tests {
let announced: Vec<_> = received let announced: Vec<_> = received
.announced .announced
.iter() .iter()
.map(|(p, e)| (p.clone(), e.next_hop.is_some())) .map(|(p, e)| (*p, e.next_hop.is_some()))
.collect(); .collect();
assert_eq!( assert_eq!(
announced, announced,
+1 -1
View File
@@ -171,7 +171,7 @@ async fn run_actor(mut rx: mpsc::Receiver<Command>, events: broadcast::Sender<Lo
while let Some(cmd) = rx.recv().await { while let Some(cmd) = rx.recv().await {
match cmd { match cmd {
Command::Announce { route, reply } => { Command::Announce { route, reply } => {
table.insert(route.prefix.clone(), route.clone()); table.insert(route.prefix, route.clone());
let _ = events.send(LocalRibEvent::Announced(route)); let _ = events.send(LocalRibEvent::Announced(route));
let _ = reply.send(()); let _ = reply.send(());
} }
+7 -7
View File
@@ -178,10 +178,10 @@ pub fn build_announce(
]; ];
let mut announced_routes = vec![]; let mut announced_routes = vec![];
match (route.prefix.address_family, next_hop) { match (route.prefix.address_family(), next_hop) {
(AddressFamilyId::Ipv4, IpAddr::V4(nh)) => { (AddressFamilyId::Ipv4, IpAddr::V4(nh)) => {
path_attributes.push(PathAttribute::NextHop(NextHopPathAttribute(nh))); path_attributes.push(PathAttribute::NextHop(NextHopPathAttribute(nh)));
announced_routes.push(route.prefix.clone()); announced_routes.push(route.prefix);
} }
(AddressFamilyId::Ipv6, IpAddr::V6(_)) => {} (AddressFamilyId::Ipv6, IpAddr::V6(_)) => {}
_ => return None, _ => return None,
@@ -197,12 +197,12 @@ pub fn build_announce(
attrs.communities.clone(), attrs.communities.clone(),
))); )));
} }
if let (AddressFamilyId::Ipv6, IpAddr::V6(nh)) = (route.prefix.address_family, next_hop) { if let (AddressFamilyId::Ipv6, IpAddr::V6(nh)) = (route.prefix.address_family(), next_hop) {
path_attributes.push(PathAttribute::MpReachNlri(MpReachNlriPathAttribute { path_attributes.push(PathAttribute::MpReachNlri(MpReachNlriPathAttribute {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
next_hop: NlriNextHop::Ipv6(nh), next_hop: NlriNextHop::Ipv6(nh),
prefixes: vec![route.prefix.clone()], prefixes: vec![route.prefix],
})); }));
} }
path_attributes.extend(as4_path); path_attributes.extend(as4_path);
@@ -223,9 +223,9 @@ pub fn build_announce(
/// Builds the UPDATE withdrawing `prefix`. /// Builds the UPDATE withdrawing `prefix`.
pub fn build_withdraw(prefix: &IpPrefix) -> UpdateMessage { pub fn build_withdraw(prefix: &IpPrefix) -> UpdateMessage {
match prefix.address_family { match prefix.address_family() {
AddressFamilyId::Ipv4 => UpdateMessage { AddressFamilyId::Ipv4 => UpdateMessage {
withdrawn_routes: vec![prefix.clone()], withdrawn_routes: vec![*prefix],
path_attributes: vec![], path_attributes: vec![],
announced_routes: vec![], announced_routes: vec![],
}, },
@@ -234,7 +234,7 @@ pub fn build_withdraw(prefix: &IpPrefix) -> UpdateMessage {
path_attributes: vec![PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute { path_attributes: vec![PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
prefixes: vec![prefix.clone()], prefixes: vec![*prefix],
})], })],
announced_routes: vec![], announced_routes: vec![],
}, },
+1 -1
View File
@@ -43,7 +43,7 @@ impl FamilySet {
/// Whether a unicast route for `prefix` may be exchanged. /// Whether a unicast route for `prefix` may be exchanged.
pub fn carries(&self, prefix: &IpPrefix) -> bool { pub fn carries(&self, prefix: &IpPrefix) -> bool {
self.contains(AfiSafi::unicast(prefix.address_family)) self.contains(AfiSafi::unicast(prefix.address_family()))
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
+1 -1
View File
@@ -625,7 +625,7 @@ mod tests {
next_hop: NlriNextHop::Ipv6(std::net::Ipv6Addr::LOCALHOST), next_hop: NlriNextHop::Ipv6(std::net::Ipv6Addr::LOCALHOST),
prefixes: vec![IpPrefix::try_from("2001:db8::/32").unwrap()], prefixes: vec![IpPrefix::try_from("2001:db8::/32").unwrap()],
})], })],
announced_routes: vec![v4.clone()], announced_routes: vec![v4],
}; };
let actions = fsm.handle(Event::Message(Message::Update(update))); let actions = fsm.handle(Event::Message(Message::Update(update)));