netstack3_ip/raw/
checksum.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Declares types and functionality related to checksums for raw IP sockets.
6
7use log::error;
8use net_types::ip::{GenericOverIp, Ip, IpInvariant, Ipv4Addr, Ipv6Addr};
9use netstack3_base::IpExt;
10use packet::{Buf, BufferViewMut, ParsablePacket as _, ParseBuffer as _};
11use packet_formats::icmp::{IcmpParseArgs, Icmpv6Packet, Icmpv6PacketRaw};
12use packet_formats::ip::{IpPacket, IpProto, Ipv4Proto, Ipv6Proto};
13use packet_formats::ipv4::Ipv4Packet;
14use packet_formats::ipv6::Ipv6Packet;
15use zerocopy::SplitByteSlice;
16
17/// Errors that may occur while validating/generating a checksum.
18#[derive(GenericOverIp)]
19#[generic_over_ip()]
20pub(super) enum ChecksumError {
21    /// Checksum support has not yet been added for the protocol.
22    ProtocolNotSupported,
23    /// The checksum couldn't be validated/generated.
24    ChecksumFailed,
25}
26
27/// Returns true if the given [`IpPacket`] has a valid checksum.
28pub(super) fn has_valid_checksum<I: IpExt, B: SplitByteSlice>(packet: &I::Packet<B>) -> bool {
29    match I::map_ip(
30        packet,
31        |packet| validate_ipv4_checksum(packet),
32        |packet| validate_ipv6_checksum(packet),
33    ) {
34        Ok(()) => true,
35        Err(ChecksumError::ChecksumFailed) => false,
36        Err(ChecksumError::ProtocolNotSupported) => {
37            // TODO(https://fxbug.dev/343672830): Add checksum validation for
38            // other protocols beyond ICMPv6.
39            error!(
40                "raw IP sockets asked to validate checksum for unsupported protocol: {:?}",
41                packet.proto()
42            );
43            false
44        }
45    }
46}
47
48/// Returns `Ok(())` if the given [`Ipv4Packet`] has a valid checksum.
49fn validate_ipv4_checksum<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Result<(), ChecksumError> {
50    match packet.proto() {
51        Ipv4Proto::Icmp
52        | Ipv4Proto::Igmp
53        | Ipv4Proto::Proto(IpProto::Udp)
54        | Ipv4Proto::Proto(IpProto::Tcp)
55        | Ipv4Proto::Proto(IpProto::Reserved)
56        | Ipv4Proto::Other(_) => Err(ChecksumError::ProtocolNotSupported),
57    }
58}
59
60/// Returns `Ok(())` if the given [`Ipv4Packet`] has a valid checksum.
61fn validate_ipv6_checksum<B: SplitByteSlice>(packet: &Ipv6Packet<B>) -> Result<(), ChecksumError> {
62    match packet.proto() {
63        Ipv6Proto::Icmpv6 => {
64            // Parsing validates the checksum.
65            let mut buffer = Buf::new(packet.body(), ..);
66            match buffer.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(
67                packet.src_ip(),
68                packet.dst_ip(),
69            )) {
70                Ok(_packet) => Ok(()),
71                Err(_) => Err(ChecksumError::ChecksumFailed),
72            }
73        }
74        Ipv6Proto::NoNextHeader
75        | Ipv6Proto::Proto(IpProto::Udp)
76        | Ipv6Proto::Proto(IpProto::Tcp)
77        | Ipv6Proto::Proto(IpProto::Reserved)
78        | Ipv6Proto::Other(_) => Err(ChecksumError::ProtocolNotSupported),
79    }
80}
81
82/// Populates a checksum in the provided `body`.
83///
84/// True if the checksum was successfully populated. False, if the checksum,
85/// could not be populated (in which case the provided body is unmodified).
86pub(super) fn populate_checksum<'a, I: IpExt, B: BufferViewMut<&'a mut [u8]>>(
87    src_ip: I::Addr,
88    dst_ip: I::Addr,
89    proto: I::Proto,
90    body: B,
91) -> bool {
92    match I::map_ip(
93        (src_ip, dst_ip, proto, IpInvariant(body)),
94        |(src_ip, dst_ip, proto, IpInvariant(body))| {
95            populate_ipv4_checksum(src_ip, dst_ip, proto, body)
96        },
97        |(src_ip, dst_ip, proto, IpInvariant(body))| {
98            populate_ipv6_checksum(src_ip, dst_ip, proto, body)
99        },
100    ) {
101        Ok(()) => true,
102        Err(ChecksumError::ChecksumFailed) => false,
103        Err(ChecksumError::ProtocolNotSupported) => {
104            // TODO(https://fxbug.dev/343672830): Add checksum validation for
105            // other protocols beyond ICMPv6.
106            error!("raw IP sockets asked to generate checksum for unsupported protocol: {proto:?}");
107            false
108        }
109    }
110}
111
112/// Returns Ok(()) if the checksum was successfully written into `body`.
113fn populate_ipv4_checksum<'a, B: BufferViewMut<&'a mut [u8]>>(
114    _src_ip: Ipv4Addr,
115    _dst_ip: Ipv4Addr,
116    proto: Ipv4Proto,
117    _body: B,
118) -> Result<(), ChecksumError> {
119    match proto {
120        Ipv4Proto::Icmp
121        | Ipv4Proto::Igmp
122        | Ipv4Proto::Proto(IpProto::Udp)
123        | Ipv4Proto::Proto(IpProto::Tcp)
124        | Ipv4Proto::Proto(IpProto::Reserved)
125        | Ipv4Proto::Other(_) => Err(ChecksumError::ProtocolNotSupported),
126    }
127}
128
129/// Returns Ok(()) if the checksum was successfully written into `body`.
130fn populate_ipv6_checksum<'a, B: BufferViewMut<&'a mut [u8]>>(
131    src_ip: Ipv6Addr,
132    dst_ip: Ipv6Addr,
133    proto: Ipv6Proto,
134    body: B,
135) -> Result<(), ChecksumError> {
136    match proto {
137        Ipv6Proto::Icmpv6 => {
138            match Icmpv6PacketRaw::parse_mut(body, ()) {
139                Ok(mut packet) => {
140                    if packet.try_write_checksum(src_ip, dst_ip) {
141                        Ok(())
142                    } else {
143                        Err(ChecksumError::ChecksumFailed)
144                    }
145                }
146                Err(_) => {
147                    // The body doesn't have all of the parts required of an
148                    // ICMPv6 message; we won't be able to compute a
149                    // checksum.
150                    Err(ChecksumError::ChecksumFailed)
151                }
152            }
153        }
154        Ipv6Proto::NoNextHeader
155        | Ipv6Proto::Proto(IpProto::Udp)
156        | Ipv6Proto::Proto(IpProto::Tcp)
157        | Ipv6Proto::Proto(IpProto::Reserved)
158        | Ipv6Proto::Other(_) => Err(ChecksumError::ProtocolNotSupported),
159    }
160}