Skip to content

Commit

Permalink
fix(tcpdump): partial checksum
Browse files Browse the repository at this point in the history
In some cases, the checksum calculation is offloaded to hardware and
only the partial checksum is calculated (only the pseudo-header). The
tcpdump example was not taking this into account and was flagging some
packets with incorrect checksums.
  • Loading branch information
thvdveld committed Nov 29, 2024
1 parent d2d6470 commit dff1c20
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 5 deletions.
22 changes: 18 additions & 4 deletions src/wire/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,9 +779,17 @@ pub mod checksum {
}

// We use this in pretty printer implementations.
pub(crate) fn format_checksum(f: &mut fmt::Formatter, correct: bool) -> fmt::Result {
pub(crate) fn format_checksum(
f: &mut fmt::Formatter,
correct: bool,
partially_correct: bool,
) -> fmt::Result {
if !correct {
write!(f, " (checksum incorrect)")
if partially_correct {
write!(f, " (partial checksum correct)")
} else {
write!(f, " (checksum incorrect)")
}
} else {
Ok(())
}
Expand Down Expand Up @@ -833,7 +841,10 @@ pub fn pretty_print_ip_payload<T: Into<Repr>>(
)?;
let valid =
udp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr());
format_checksum(f, valid)
let partially_valid = udp_packet
.verify_partial_checksum(&repr.src_addr(), &repr.dst_addr());

format_checksum(f, valid, partially_valid)
}
}
}
Expand All @@ -855,7 +866,10 @@ pub fn pretty_print_ip_payload<T: Into<Repr>>(
write!(f, "{indent}{tcp_repr}")?;
let valid =
tcp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr());
format_checksum(f, valid)
let partially_valid = tcp_packet
.verify_partial_checksum(&repr.src_addr(), &repr.dst_addr());

format_checksum(f, valid, partially_valid)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/wire/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
return Ok(());
} else {
write!(f, "{indent}{ip_repr}")?;
format_checksum(f, ip_packet.verify_checksum())?;
format_checksum(f, ip_packet.verify_checksum(), false)?;
(ip_repr, ip_packet.payload())
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/wire/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,25 @@ impl<T: AsRef<[u8]>> Packet<T> {
Ok([None, None, None])
}

/// Validate the partial checksum.
///
/// # Panics
/// This function panics unless `src_addr` and `dst_addr` belong to the same family,
/// and that family is IPv4 or IPv6.
///
/// # Fuzzing
/// This function always returns `true` when fuzzing.
pub fn verify_partial_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
if cfg!(fuzzing) {
return true;
}

let data = self.buffer.as_ref();

checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32)
== self.checksum()
}

/// Validate the packet checksum.
///
/// # Panics
Expand Down
17 changes: 17 additions & 0 deletions src/wire/udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,23 @@ impl<T: AsRef<[u8]>> Packet<T> {
NetworkEndian::read_u16(&data[field::CHECKSUM])
}

/// Validate the partial packet checksum.
///
/// # Panics
/// This function panics unless `src_addr` and `dst_addr` belong to the same family,
/// and that family is IPv4 or IPv6.
///
/// # Fuzzing
/// This function always returns `true` when fuzzing.
pub fn verify_partial_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
if cfg!(fuzzing) {
return true;
}

checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32)
== self.checksum()
}

/// Validate the packet checksum.
///
/// # Panics
Expand Down

0 comments on commit dff1c20

Please sign in to comment.