pub mod auth;
pub mod constants;
pub mod entry;
pub mod message;
pub mod registry;
pub mod ripng;
pub use auth::{
verify, RipAuth, RipAuthPayload, RipAuthVerification, RipDigestAlgorithm, RipKeyedDigestHeader,
};
pub use constants::*;
pub use entry::RipEntry;
pub use message::RipCommand;
pub use registry::{RipAddressFamily, RipAuthType, RipCommandMeta, RipCommandStatus};
use core::any::Any;
use core::ops::Div;
use crate::error::{CrafterError, Result};
use crate::field::Field;
use crate::packet::{IntoPacket, Layer, LayerContext, Packet};
macro_rules! impl_layer_object {
($type:ty) => {
fn clone_layer(&self) -> Box<dyn Layer> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
};
}
macro_rules! impl_layer_div {
($type:ty) => {
impl<R> Div<R> for $type
where
R: IntoPacket,
{
type Output = Packet;
fn div(self, rhs: R) -> Self::Output {
Packet::from_layer(self).concat(rhs)
}
}
};
}
#[derive(Debug, Clone)]
pub struct Rip {
pub command: Field<u8>,
pub version: Field<u8>,
pub reserved: Field<u16>,
pub entries: Vec<RipEntry>,
pub auth: Option<RipAuth>,
pub auth_key: Vec<u8>,
}
impl Rip {
pub fn new() -> Self {
Self {
command: Field::defaulted(RIP_COMMAND_RESPONSE),
version: Field::defaulted(RIP_VERSION_2),
reserved: Field::defaulted(0),
entries: Vec::new(),
auth: None,
auth_key: Vec::new(),
}
}
pub fn request() -> Self {
Self::new()
.with_command(RipCommand::Request)
.version(RIP_VERSION_2)
}
pub fn response() -> Self {
Self::new()
.with_command(RipCommand::Response)
.version(RIP_VERSION_2)
}
pub fn update_request() -> Self {
Self::new()
.with_command(RipCommand::UpdateRequest)
.version(RIP_VERSION_2)
}
pub fn update_response() -> Self {
Self::new()
.with_command(RipCommand::UpdateResponse)
.version(RIP_VERSION_2)
}
pub fn update_acknowledge() -> Self {
Self::new()
.with_command(RipCommand::UpdateAcknowledge)
.version(RIP_VERSION_2)
}
pub fn with_command(mut self, command: RipCommand) -> Self {
self.command.set_user(command.code());
self
}
pub fn command_code(mut self, code: u8) -> Self {
self.command.set_user(code);
self
}
pub fn version(mut self, version: u8) -> Self {
self.version.set_user(version);
self
}
pub fn reserved(mut self, reserved: u16) -> Self {
self.reserved.set_user(reserved);
self
}
pub fn entry(mut self, entry: RipEntry) -> Self {
self.entries.push(entry);
self
}
pub fn with_entries(mut self, entries: impl Into<Vec<RipEntry>>) -> Self {
self.entries = entries.into();
self
}
pub fn auth(mut self, auth: RipAuth, key: impl Into<Vec<u8>>) -> Self {
self.auth = Some(auth);
self.auth_key = key.into();
self
}
pub fn demand_sequence(mut self, sequence: u16) -> Rip {
self.reserved.set_user(sequence);
self
}
pub fn demand_sequence_value(&self) -> Option<u16> {
match self.command() {
RipCommand::UpdateRequest
| RipCommand::UpdateResponse
| RipCommand::UpdateAcknowledge => Some(self.reserved_value()),
_ => None,
}
}
pub fn command_value(&self) -> u8 {
self.command
.value()
.copied()
.unwrap_or(RIP_COMMAND_RESPONSE)
}
pub fn command(&self) -> RipCommand {
RipCommand::from_code(self.command_value())
}
pub fn version_value(&self) -> u8 {
self.version.value().copied().unwrap_or(RIP_VERSION_2)
}
pub fn reserved_value(&self) -> u16 {
self.reserved.value().copied().unwrap_or(0)
}
pub fn entries(&self) -> &[RipEntry] {
&self.entries
}
pub fn auth_config(&self) -> Option<&RipAuth> {
self.auth.as_ref()
}
}
impl Default for Rip {
fn default() -> Self {
Self::new()
}
}
pub fn decode(bytes: &[u8]) -> Result<Rip> {
if bytes.len() < RIP_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"RIP header",
RIP_HEADER_LEN,
bytes.len(),
));
}
let command = bytes[0];
let version = bytes[1];
let reserved = u16::from_be_bytes([bytes[2], bytes[3]]);
let mut rip = Rip::new();
rip.command.set_user(command);
rip.version.set_user(version);
rip.reserved.set_user(reserved);
let mut rest = &bytes[RIP_HEADER_LEN..];
while !rest.is_empty() {
let entry = RipEntry::decode(rest)?;
rip.entries.push(entry);
rest = &rest[RIP_ENTRY_LEN..];
}
if let Some(first) = rip.entries.first() {
if first.is_auth_marker() {
rip.auth = auth::decode_auth_entry(first);
}
}
Ok(rip)
}
pub fn rip_v2_multicast_response(
source: std::net::Ipv4Addr,
entries: impl Into<Vec<RipEntry>>,
) -> Packet {
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
Ipv4::new().src(source).dst(RIP_V2_MULTICAST)
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Rip::response().version(RIP_VERSION_2).with_entries(entries)
}
pub fn rip_v1_whole_table_request(
source: std::net::Ipv4Addr,
destination: std::net::Ipv4Addr,
) -> Packet {
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
Ipv4::new().src(source).dst(destination)
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Rip::request()
.version(RIP_VERSION_1)
.entry(RipEntry::whole_table_request())
}
pub fn rip_v2_whole_table_request(source: std::net::Ipv4Addr) -> Packet {
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
Ipv4::new().src(source).dst(RIP_V2_MULTICAST)
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Rip::request()
.version(RIP_VERSION_2)
.entry(RipEntry::whole_table_request())
}
pub fn rip_update_request(
source: std::net::Ipv4Addr,
destination: std::net::Ipv4Addr,
sequence: u16,
) -> Packet {
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
Ipv4::new().src(source).dst(destination)
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Rip::update_request().demand_sequence(sequence)
}
pub fn rip_update_response(
source: std::net::Ipv4Addr,
destination: std::net::Ipv4Addr,
sequence: u16,
entries: impl Into<Vec<RipEntry>>,
) -> Packet {
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
Ipv4::new().src(source).dst(destination)
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Rip::update_response()
.demand_sequence(sequence)
.with_entries(entries)
}
pub fn rip_update_acknowledge(
source: std::net::Ipv4Addr,
destination: std::net::Ipv4Addr,
sequence: u16,
) -> Packet {
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
Ipv4::new().src(source).dst(destination)
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Rip::update_acknowledge().demand_sequence(sequence)
}
pub(crate) fn append_rip_packet(packet: Packet, bytes: &[u8]) -> Result<Packet> {
Ok(packet.push(decode(bytes)?))
}
pub(crate) fn looks_like_rip_payload(bytes: &[u8]) -> bool {
if bytes.len() < RIP_HEADER_LEN {
return false;
}
let command = bytes[0];
let version = bytes[1];
matches!(command, 1 | 2 | 9 | 10 | 11)
&& matches!(version, RIP_VERSION_1 | RIP_VERSION_2)
&& (bytes.len() - RIP_HEADER_LEN) % RIP_ENTRY_LEN == 0
}
impl Layer for Rip {
fn name(&self) -> &'static str {
"Rip"
}
fn encoded_len(&self) -> usize {
let mut len = RIP_HEADER_LEN + self.entries.len() * RIP_ENTRY_LEN;
if let Some(auth) = &self.auth {
len += RIP_ENTRY_LEN;
if let RipAuthPayload::KeyedDigest(header) = &auth.payload {
len += 4 + header.algorithm.digest_len();
}
}
len
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
out.reserve(self.encoded_len());
out.push(self.command_value());
out.push(self.version_value());
out.extend_from_slice(&self.reserved_value().to_be_bytes());
match &self.auth {
Some(auth) => match &auth.payload {
auth::RipAuthPayload::SimplePassword(_) => {
auth.as_entry().encode(out);
for entry in &self.entries {
entry.encode(out);
}
}
auth::RipAuthPayload::KeyedDigest(header) => {
auth.keyed_digest_header_entry().encode(out);
for entry in &self.entries {
entry.encode(out);
}
out.extend_from_slice(&RIP_AFI_AUTH.to_be_bytes());
out.extend_from_slice(&auth::RIP_AUTH_TRAILER_MARKER.to_be_bytes());
if let Some(pinned) = header.digest {
out.extend_from_slice(&pinned);
} else {
let digest = match header.algorithm {
RipDigestAlgorithm::KeyedMd5 => {
let mut message = out.clone();
message.extend_from_slice(&[0u8; auth::RIP_MD5_DIGEST_LEN]);
auth::compute_md5_digest(&message, &self.auth_key).to_vec()
}
RipDigestAlgorithm::HmacSha1 | RipDigestAlgorithm::HmacSha256 => {
auth::compute_hmac_digest(header.algorithm, out, &self.auth_key)
}
};
out.extend_from_slice(&digest);
}
}
},
None => {
for entry in &self.entries {
entry.encode(out);
}
}
}
Ok(())
}
fn summary(&self) -> String {
let count = self.entries.len();
let plural = if count == 1 { "entry" } else { "entries" };
format!(
"Rip v{} {} ({} {})",
self.version_value(),
self.command().name(),
count,
plural
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
("command", self.command().name().to_string()),
("version", self.version_value().to_string()),
("reserved", self.reserved_value().to_string()),
("entries", self.entries.len().to_string()),
]
}
impl_layer_object!(Rip);
}
impl_layer_div!(Rip);
#[cfg(test)]
mod rip_layer_builder {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn rip_layer_builder_sets_header_and_entries() {
let request = Rip::request();
assert_eq!(request.command(), RipCommand::Request);
assert_eq!(request.command_value(), RIP_COMMAND_REQUEST);
assert_eq!(request.version_value(), RIP_VERSION_2);
assert!(request.entries().is_empty());
let default = Rip::new();
assert_eq!(default.command(), RipCommand::Response);
assert_eq!(default.version_value(), RIP_VERSION_2);
let route = RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
3,
);
let with_one = Rip::response().entry(route.clone());
assert_eq!(with_one.entries().len(), 1);
assert_eq!(with_one.entries()[0], route);
let second = RipEntry::ipv1_route(Ipv4Addr::new(198, 51, 100, 1), 5);
let with_two = Rip::response().with_entries(vec![route.clone(), second.clone()]);
assert_eq!(with_two.entries().len(), 2);
assert_eq!(with_two.entries()[1], second);
}
}
#[cfg(test)]
mod rip_layer_compiles {
use super::*;
use crate::packet::LayerContext;
use crate::protocols::transport::Udp;
use std::net::Ipv4Addr;
#[test]
fn rip_layer_compiles_header_and_entries() {
let rip = Rip::response().entry(RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
));
let packet = Packet::from_layer(rip.clone());
let ctx = LayerContext::new(&packet, 0);
let mut out = Vec::new();
rip.compile(&ctx, &mut out).expect("rip compiles");
assert_eq!(&out[..RIP_HEADER_LEN], &[0x02, 0x02, 0x00, 0x00]);
assert_eq!(out.len(), RIP_HEADER_LEN + RIP_ENTRY_LEN);
assert_eq!(rip.encoded_len(), RIP_HEADER_LEN + RIP_ENTRY_LEN);
}
#[test]
fn rip_layer_div_composes_into_packet() {
let rip = Rip::response().entry(RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
));
let packet = Udp::new() / rip;
assert!(packet.layer::<Rip>().is_some());
}
}
#[cfg(test)]
mod rip_layer_summary {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn rip_layer_summary_mentions_command_and_count() {
let rip = Rip::response().with_entries(vec![
RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
),
RipEntry::ipv2_route(
Ipv4Addr::new(198, 51, 100, 0),
Ipv4Addr::new(255, 255, 255, 0),
2,
),
]);
let summary = rip.summary();
assert!(summary.contains("Response"), "summary: {summary}");
assert!(summary.contains("v2"), "summary: {summary}");
assert!(summary.contains('2'), "summary: {summary}");
}
#[test]
fn rip_layer_inspection_fields_present() {
let rip = Rip::response().entry(RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
));
let fields = rip.inspection_fields();
assert!(
fields.iter().any(|(key, _)| *key == "command"),
"expected a \"command\" field: {fields:?}"
);
assert!(
fields.iter().any(|(key, _)| *key == "entries"),
"expected an \"entries\" field: {fields:?}"
);
}
}
#[cfg(test)]
mod rip_decode_roundtrips_response {
use super::*;
use crate::packet::LayerContext;
use std::net::Ipv4Addr;
fn compile_bytes(rip: &Rip) -> Vec<u8> {
let packet = Packet::from_layer(rip.clone());
let ctx = LayerContext::new(&packet, 0);
let mut out = Vec::new();
rip.compile(&ctx, &mut out).expect("rip compiles");
out
}
#[test]
fn decode_reproduces_header_and_entries_and_recompiles_identically() {
let rip = Rip::response().with_entries(vec![
RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
),
RipEntry::ipv2_route(
Ipv4Addr::new(198, 51, 100, 0),
Ipv4Addr::new(255, 255, 255, 0),
2,
),
]);
let bytes = compile_bytes(&rip);
let decoded = decode(&bytes).expect("two-entry response decodes");
assert_eq!(decoded.command(), rip.command());
assert_eq!(decoded.command_value(), rip.command_value());
assert_eq!(decoded.version_value(), rip.version_value());
assert_eq!(decoded.reserved_value(), rip.reserved_value());
assert_eq!(decoded.entries().len(), rip.entries().len());
for (got, want) in decoded.entries().iter().zip(rip.entries()) {
assert_eq!(got.address_family_value(), want.address_family_value());
assert_eq!(got.route_tag_value(), want.route_tag_value());
assert_eq!(got.address_value(), want.address_value());
assert_eq!(got.subnet_mask_value(), want.subnet_mask_value());
assert_eq!(got.next_hop_value(), want.next_hop_value());
assert_eq!(got.metric_value(), want.metric_value());
}
let recompiled = compile_bytes(&decoded);
assert_eq!(recompiled, bytes);
}
}
#[cfg(test)]
mod rip_decode_partial_entry_is_error {
use super::*;
#[test]
fn header_plus_partial_entry_returns_structured_error_without_panic() {
let mut bytes = vec![RIP_COMMAND_RESPONSE, RIP_VERSION_2, 0x00, 0x00];
bytes.extend_from_slice(&[0u8; 10]);
let err = decode(&bytes).expect_err("partial trailing entry is an error");
match err {
CrafterError::BufferTooShort {
required,
available,
..
} => {
assert_eq!(required, RIP_ENTRY_LEN);
assert_eq!(available, 10);
}
other => panic!("expected BufferTooShort, got {other:?}"),
}
}
}
#[cfg(test)]
mod rip_udp_binding {
use super::*;
use crate::packet::{NetworkLayer, Raw};
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::transport::Udp;
use std::net::Ipv4Addr;
#[test]
fn rip_decodes_from_udp_520() {
let rip = Rip::response().entry(RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
));
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ rip;
let compiled = packet.compile().expect("rip stack compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())
.expect("ipv4/udp/rip decodes");
let decoded_rip = decoded
.layer::<Rip>()
.expect("decoded packet includes a Rip layer");
assert_eq!(decoded_rip.command(), RipCommand::Response);
assert_eq!(decoded_rip.entries().len(), 1);
assert!(decoded.layer::<Raw>().is_none());
}
#[test]
fn rip_non_rip_udp_520_stays_raw() {
let payload = vec![0xFFu8, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x03];
assert!(
!looks_like_rip_payload(&payload),
"fixture payload must not look like RIP"
);
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
/ Udp::new().sport(RIP_UDP_PORT).dport(RIP_UDP_PORT)
/ Raw::from_bytes(&payload);
let compiled = packet.compile().expect("udp/raw stack compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())
.expect("ipv4/udp/raw decodes");
assert!(
decoded.layer::<Rip>().is_none(),
"non-RIP port-520 payload must not decode as Rip"
);
assert!(
decoded.layer::<Raw>().is_some(),
"non-RIP port-520 payload must remain Raw"
);
}
}
#[cfg(test)]
mod rip_v2_multicast_response_helper {
use super::*;
use crate::packet::NetworkLayer;
use std::net::Ipv4Addr;
#[test]
fn rip_v2_multicast_response_targets_group() {
let packet = rip_v2_multicast_response(
Ipv4Addr::new(192, 0, 2, 1),
vec![RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
)],
);
let compiled = packet.compile().expect("multicast response compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())
.expect("ipv4/udp/rip multicast response decodes");
let ipv4 = decoded
.layer::<crate::protocols::ip::v4::Ipv4>()
.expect("decoded packet includes an Ipv4 layer");
assert_eq!(ipv4.destination(), RIP_V2_MULTICAST);
assert_eq!(ipv4.destination(), Ipv4Addr::new(224, 0, 0, 9));
let udp = decoded
.layer::<crate::protocols::transport::Udp>()
.expect("decoded packet includes a Udp layer");
assert_eq!(udp.destination_port_value(), RIP_UDP_PORT);
assert_eq!(udp.destination_port_value(), 520);
assert!(
decoded.layer::<Rip>().is_some(),
"decoded packet must include a Rip layer"
);
}
}
#[cfg(test)]
mod rip_whole_table_request_helpers {
use super::*;
use crate::packet::NetworkLayer;
use std::net::Ipv4Addr;
fn assert_whole_table_request(packet: Packet) -> Packet {
let compiled = packet.compile().expect("whole-table request compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())
.expect("ipv4/udp/rip whole-table request decodes");
let rip = decoded
.layer::<Rip>()
.expect("decoded packet includes a Rip layer");
assert_eq!(rip.command(), RipCommand::Request);
assert_eq!(rip.entries().len(), 1, "request carries a single entry");
assert!(
rip.entries()[0].is_whole_table_request(),
"the single entry is the whole-table sentinel"
);
decoded
}
#[test]
fn rip_whole_table_requests_build() {
let v1 =
rip_v1_whole_table_request(Ipv4Addr::new(192, 0, 2, 1), Ipv4Addr::new(192, 0, 2, 2));
let v1_decoded = assert_whole_table_request(v1);
let v1_rip = v1_decoded
.layer::<Rip>()
.expect("v1 decoded packet includes a Rip layer");
assert_eq!(v1_rip.version_value(), RIP_VERSION_1);
let v2 = rip_v2_whole_table_request(Ipv4Addr::new(192, 0, 2, 1));
let v2_decoded = assert_whole_table_request(v2);
let v2_rip = v2_decoded
.layer::<Rip>()
.expect("v2 decoded packet includes a Rip layer");
assert_eq!(v2_rip.version_value(), RIP_VERSION_2);
let v2_ipv4 = v2_decoded
.layer::<crate::protocols::ip::v4::Ipv4>()
.expect("v2 decoded packet includes an Ipv4 layer");
assert_eq!(v2_ipv4.destination(), RIP_V2_MULTICAST);
assert_eq!(v2_ipv4.destination(), Ipv4Addr::new(224, 0, 0, 9));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::LayerContext;
fn compile_bytes(rip: &Rip) -> Vec<u8> {
let packet = Packet::from_layer(rip.clone());
let ctx = LayerContext::new(&packet, 0);
let mut out = Vec::new();
rip.compile(&ctx, &mut out).expect("rip compiles");
out
}
#[test]
fn rip_demand_commands_roundtrip() {
let cases = [
(
Rip::update_request(),
RipCommand::UpdateRequest,
RIP_COMMAND_UPDATE_REQUEST,
9u8,
),
(
Rip::update_response(),
RipCommand::UpdateResponse,
RIP_COMMAND_UPDATE_RESPONSE,
10,
),
(
Rip::update_acknowledge(),
RipCommand::UpdateAcknowledge,
RIP_COMMAND_UPDATE_ACK,
11,
),
];
for (rip, want_variant, want_const, want_code) in cases {
assert_eq!(want_const, want_code, "constant matches RFC codepoint");
assert_eq!(rip.command(), want_variant);
assert_eq!(rip.command_value(), want_code);
assert_eq!(rip.version_value(), RIP_VERSION_2);
let bytes = compile_bytes(&rip);
assert_eq!(bytes[0], want_code, "header command octet");
assert_eq!(bytes[1], RIP_VERSION_2, "header version octet");
let decoded = decode(&bytes).expect("demand command decodes");
assert_eq!(decoded.command(), want_variant);
assert_eq!(decoded.command_value(), want_code);
assert_eq!(decoded.version_value(), RIP_VERSION_2);
assert_eq!(compile_bytes(&decoded), bytes);
}
}
}
#[cfg(test)]
mod rip_demand_sequence {
use super::*;
use crate::packet::LayerContext;
fn compile_bytes(rip: &Rip) -> Vec<u8> {
let packet = Packet::from_layer(rip.clone());
let ctx = LayerContext::new(&packet, 0);
let mut out = Vec::new();
rip.compile(&ctx, &mut out).expect("rip compiles");
out
}
#[test]
fn rip_demand_sequence_roundtrips() {
let rip = Rip::update_request().demand_sequence(0x0102);
assert_eq!(rip.demand_sequence_value(), Some(0x0102));
let bytes = compile_bytes(&rip);
assert_eq!(bytes[0], RIP_COMMAND_UPDATE_REQUEST, "command octet");
assert_eq!(bytes[1], RIP_VERSION_2, "version octet");
assert_eq!(&bytes[2..4], &0x0102u16.to_be_bytes(), "sequence octets");
let decoded = decode(&bytes).expect("demand update request decodes");
assert_eq!(decoded.command(), RipCommand::UpdateRequest);
assert_eq!(decoded.demand_sequence_value(), Some(0x0102));
assert_eq!(decoded.reserved_value(), 0x0102);
assert_eq!(compile_bytes(&decoded), bytes);
assert_eq!(Rip::request().demand_sequence_value(), None);
assert_eq!(Rip::response().demand_sequence_value(), None);
}
}
#[cfg(test)]
mod rip_layer_auth_integration {
use super::*;
use crate::packet::LayerContext;
use std::net::Ipv4Addr;
fn compile_bytes(rip: &Rip) -> Vec<u8> {
let packet = Packet::from_layer(rip.clone());
let ctx = LayerContext::new(&packet, 0);
let mut out = Vec::new();
rip.compile(&ctx, &mut out).expect("rip compiles");
out
}
#[test]
fn rip_layer_keyed_md5_autofills_and_verifies() {
let key = b"rip-md5-key";
let rip = Rip::response()
.entry(RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
))
.auth(
RipAuth::keyed_digest(1, auth::RIP_MD5_DIGEST_LEN as u8),
key.to_vec(),
);
let bytes = compile_bytes(&rip);
assert_eq!(
bytes.len(),
RIP_HEADER_LEN + RIP_ENTRY_LEN + RIP_ENTRY_LEN + 4 + auth::RIP_MD5_DIGEST_LEN
);
assert_eq!(rip.encoded_len(), bytes.len());
assert_eq!(&bytes[RIP_HEADER_LEN..RIP_HEADER_LEN + 2], &[0xFF, 0xFF]);
assert_eq!(
&bytes[RIP_HEADER_LEN + 2..RIP_HEADER_LEN + 4],
&[0x00, 0x03]
);
assert_eq!(verify(&bytes, key), RipAuthVerification::DigestOk);
assert_eq!(
verify(&bytes, b"wrong-key"),
RipAuthVerification::DigestMismatch
);
}
#[test]
fn rip_layer_pinned_digest_preserved() {
let pinned = [0xAB_u8; auth::RIP_MD5_DIGEST_LEN];
let mut keyed = RipAuth::keyed_digest(1, auth::RIP_MD5_DIGEST_LEN as u8);
match &mut keyed.payload {
RipAuthPayload::KeyedDigest(header) => {
header.digest = Some(pinned);
}
other => panic!("expected KeyedDigest payload, got {other:?}"),
}
let rip = Rip::response()
.entry(RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
))
.auth(keyed, b"rip-md5-key".to_vec());
let bytes = compile_bytes(&rip);
let digest_start = bytes.len() - auth::RIP_MD5_DIGEST_LEN;
assert_eq!(&bytes[digest_start..], &pinned);
assert_eq!(
verify(&bytes, b"rip-md5-key"),
RipAuthVerification::DigestMismatch
);
}
}
#[cfg(test)]
mod rip_demand_exchange_helpers {
use super::*;
use crate::packet::NetworkLayer;
use std::net::Ipv4Addr;
fn decode_demand(packet: Packet, destination: Ipv4Addr) -> Rip {
let compiled = packet.compile().expect("demand packet compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())
.expect("ipv4/udp/rip demand packet decodes");
let ipv4 = decoded
.layer::<crate::protocols::ip::v4::Ipv4>()
.expect("decoded packet includes an Ipv4 layer");
assert_eq!(ipv4.destination(), destination);
assert_ne!(ipv4.destination(), RIP_V2_MULTICAST);
let udp = decoded
.layer::<crate::protocols::transport::Udp>()
.expect("decoded packet includes a Udp layer");
assert_eq!(udp.destination_port_value(), RIP_UDP_PORT);
assert_eq!(udp.destination_port_value(), 520);
decoded
.layer::<Rip>()
.expect("decoded packet includes a Rip layer")
.clone()
}
#[test]
fn rip_demand_exchange_helpers_build() {
let source = Ipv4Addr::new(192, 0, 2, 1);
let destination = Ipv4Addr::new(198, 51, 100, 2);
let sequence = 0x1234u16;
let request = rip_update_request(source, destination, sequence);
let request_rip = decode_demand(request, destination);
assert_eq!(request_rip.command(), RipCommand::UpdateRequest);
assert_eq!(request_rip.demand_sequence_value(), Some(sequence));
assert!(
request_rip.entries().is_empty(),
"an update request carries no route entries"
);
let entries = vec![
RipEntry::ipv2_route(
Ipv4Addr::new(192, 0, 2, 0),
Ipv4Addr::new(255, 255, 255, 0),
1,
),
RipEntry::ipv2_route(
Ipv4Addr::new(198, 51, 100, 0),
Ipv4Addr::new(255, 255, 255, 0),
2,
),
];
let response = rip_update_response(source, destination, sequence, entries.clone());
let response_rip = decode_demand(response, destination);
assert_eq!(response_rip.command(), RipCommand::UpdateResponse);
assert_eq!(response_rip.demand_sequence_value(), Some(sequence));
assert_eq!(
response_rip.entries().len(),
entries.len(),
"response carries the supplied entries"
);
for (got, want) in response_rip.entries().iter().zip(&entries) {
assert_eq!(got.address_value(), want.address_value());
assert_eq!(got.subnet_mask_value(), want.subnet_mask_value());
assert_eq!(got.metric_value(), want.metric_value());
}
let acknowledge = rip_update_acknowledge(source, destination, sequence);
let acknowledge_rip = decode_demand(acknowledge, destination);
assert_eq!(acknowledge_rip.command(), RipCommand::UpdateAcknowledge);
assert_eq!(acknowledge_rip.demand_sequence_value(), Some(sequence));
assert!(
acknowledge_rip.entries().is_empty(),
"an update acknowledge carries no route entries"
);
}
}