Browse Source

fix(tcpdump): partial checksum

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.
Thibaut Vandervelden 6 months ago
parent
commit
dff1c20896
4 changed files with 55 additions and 5 deletions
  1. 18 4
      src/wire/ip.rs
  2. 1 1
      src/wire/ipv4.rs
  3. 19 0
      src/wire/tcp.rs
  4. 17 0
      src/wire/udp.rs

+ 18 - 4
src/wire/ip.rs

@@ -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(())
         }
@@ -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)
                         }
                     }
                 }
@@ -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)
                         }
                     }
                 }

+ 1 - 1
src/wire/ipv4.rs

@@ -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())
                     }
                 }

+ 19 - 0
src/wire/tcp.rs

@@ -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

+ 17 - 0
src/wire/udp.rs

@@ -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