Skip to main content

net_wire/tcp/
checksum.rs

1//! TCP checksum integration for IP pseudoheaders.
2
3use core::fmt;
4
5use crate::internet_checksum;
6#[cfg(all(feature = "tcp", feature = "ipv4"))]
7use crate::internet_checksum::add_ipv4_pseudoheader;
8#[cfg(all(feature = "tcp", feature = "ipv6"))]
9use crate::internet_checksum::add_ipv6_pseudoheader;
10#[cfg(all(feature = "tcp", feature = "ipv4"))]
11use crate::ipv4::address::Ipv4Address;
12#[cfg(all(feature = "tcp", feature = "ipv6"))]
13use crate::ipv6::address::Ipv6Address;
14use crate::tcp::segment::{TcpSegment, TcpSegmentMut};
15
16const TCP_PROTOCOL: u8 = 6;
17
18/// Failure to represent a TCP segment length in an IP pseudoheader.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum TcpChecksumError {
21    /// The message length cannot be represented by the pseudoheader's length field.
22    LengthTooLarge {
23        /// Largest representable message length on this platform.
24        maximum: usize,
25        /// Actual message length.
26        actual: usize,
27    },
28}
29
30impl fmt::Display for TcpChecksumError {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::LengthTooLarge { maximum, actual } => write!(
34                formatter,
35                "transport message length {actual} exceeds pseudoheader maximum {maximum}"
36            ),
37        }
38    }
39}
40
41#[cfg(all(feature = "tcp", feature = "ipv4"))]
42impl TcpSegment<'_> {
43    /// Checks this TCP segment against its IPv4 pseudoheader.
44    pub fn checksum_is_valid_ipv4(
45        &self,
46        source: Ipv4Address,
47        destination: Ipv4Address,
48    ) -> Result<bool, TcpChecksumError> {
49        Ok(tcp_ipv4_sum(self.as_bytes(), source, destination)? == 0xffff)
50    }
51}
52
53#[cfg(all(feature = "tcp", feature = "ipv4"))]
54impl TcpSegmentMut<'_> {
55    /// Checks this TCP segment against its IPv4 pseudoheader.
56    pub fn checksum_is_valid_ipv4(
57        &self,
58        source: Ipv4Address,
59        destination: Ipv4Address,
60    ) -> Result<bool, TcpChecksumError> {
61        Ok(tcp_ipv4_sum(self.as_bytes(), source, destination)? == 0xffff)
62    }
63
64    /// Recomputes this TCP segment's IPv4 pseudoheader checksum.
65    pub fn update_checksum_ipv4(
66        &mut self,
67        source: Ipv4Address,
68        destination: Ipv4Address,
69    ) -> Result<(), TcpChecksumError> {
70        let sum = tcp_ipv4_sum_with_zero_checksum(self.as_bytes(), source, destination)?;
71        let value = internet_checksum::checksum(sum);
72        self.as_bytes_mut()[16..18].copy_from_slice(&value.to_be_bytes());
73        Ok(())
74    }
75}
76
77#[cfg(all(feature = "tcp", feature = "ipv4"))]
78fn tcp_ipv4_sum(
79    bytes: &[u8],
80    source: Ipv4Address,
81    destination: Ipv4Address,
82) -> Result<u32, TcpChecksumError> {
83    let length = tcp_ipv4_length(bytes)?;
84    Ok(internet_checksum::add_bytes(
85        add_ipv4_pseudoheader(
86            0,
87            source.octets(),
88            destination.octets(),
89            TCP_PROTOCOL,
90            length,
91        ),
92        bytes,
93    ))
94}
95
96#[cfg(all(feature = "tcp", feature = "ipv4"))]
97fn tcp_ipv4_sum_with_zero_checksum(
98    bytes: &[u8],
99    source: Ipv4Address,
100    destination: Ipv4Address,
101) -> Result<u32, TcpChecksumError> {
102    let length = tcp_ipv4_length(bytes)?;
103    let sum = add_ipv4_pseudoheader(
104        0,
105        source.octets(),
106        destination.octets(),
107        TCP_PROTOCOL,
108        length,
109    );
110    let sum = internet_checksum::add_bytes(sum, &bytes[..16]);
111    let sum = internet_checksum::add_bytes(sum, &[0, 0]);
112    Ok(internet_checksum::add_bytes(sum, &bytes[18..]))
113}
114
115#[cfg(all(feature = "tcp", feature = "ipv4"))]
116fn tcp_ipv4_length(bytes: &[u8]) -> Result<u16, TcpChecksumError> {
117    u16::try_from(bytes.len()).map_err(|_| TcpChecksumError::LengthTooLarge {
118        maximum: usize::from(u16::MAX),
119        actual: bytes.len(),
120    })
121}
122
123#[cfg(all(feature = "tcp", feature = "ipv6"))]
124impl TcpSegment<'_> {
125    /// Checks this TCP segment against its IPv6 pseudoheader.
126    pub fn checksum_is_valid_ipv6(
127        &self,
128        source: Ipv6Address,
129        destination: Ipv6Address,
130    ) -> Result<bool, TcpChecksumError> {
131        let length = tcp_ipv6_length(self.as_bytes())?;
132        Ok(tcp_ipv6_sum(self.as_bytes(), source, destination, length) == 0xffff)
133    }
134}
135
136#[cfg(all(feature = "tcp", feature = "ipv6"))]
137impl TcpSegmentMut<'_> {
138    /// Checks this TCP segment against its IPv6 pseudoheader.
139    pub fn checksum_is_valid_ipv6(
140        &self,
141        source: Ipv6Address,
142        destination: Ipv6Address,
143    ) -> Result<bool, TcpChecksumError> {
144        let length = tcp_ipv6_length(self.as_bytes())?;
145        Ok(tcp_ipv6_sum(self.as_bytes(), source, destination, length) == 0xffff)
146    }
147
148    /// Recomputes this TCP segment's IPv6 pseudoheader checksum.
149    pub fn update_checksum_ipv6(
150        &mut self,
151        source: Ipv6Address,
152        destination: Ipv6Address,
153    ) -> Result<(), TcpChecksumError> {
154        let length = tcp_ipv6_length(self.as_bytes())?;
155        let bytes = self.as_bytes();
156        let sum = tcp_ipv6_sum_with_zero_checksum(bytes, source, destination, length);
157        let value = internet_checksum::checksum(sum);
158        self.as_bytes_mut()[16..18].copy_from_slice(&value.to_be_bytes());
159        Ok(())
160    }
161}
162
163#[cfg(all(feature = "tcp", feature = "ipv6"))]
164fn tcp_ipv6_length(bytes: &[u8]) -> Result<u32, TcpChecksumError> {
165    let maximum = usize::try_from(u32::MAX).unwrap_or(usize::MAX);
166    u32::try_from(bytes.len()).map_err(|_| TcpChecksumError::LengthTooLarge {
167        maximum,
168        actual: bytes.len(),
169    })
170}
171
172#[cfg(all(feature = "tcp", feature = "ipv6"))]
173fn tcp_ipv6_sum(bytes: &[u8], source: Ipv6Address, destination: Ipv6Address, length: u32) -> u32 {
174    internet_checksum::add_bytes(
175        add_ipv6_pseudoheader(
176            0,
177            source.octets(),
178            destination.octets(),
179            TCP_PROTOCOL,
180            length,
181        ),
182        bytes,
183    )
184}
185
186#[cfg(all(feature = "tcp", feature = "ipv6"))]
187fn tcp_ipv6_sum_with_zero_checksum(
188    bytes: &[u8],
189    source: Ipv6Address,
190    destination: Ipv6Address,
191    length: u32,
192) -> u32 {
193    let sum = add_ipv6_pseudoheader(
194        0,
195        source.octets(),
196        destination.octets(),
197        TCP_PROTOCOL,
198        length,
199    );
200    let sum = internet_checksum::add_bytes(sum, &bytes[..16]);
201    let sum = internet_checksum::add_bytes(sum, &[0, 0]);
202    internet_checksum::add_bytes(sum, &bytes[18..])
203}