Skip to main content

packet_formats/
lib.rs

1// Copyright 2018 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//! Serialization and deserialization of wire formats.
6//!
7//! This module provides efficient serialization and deserialization of the
8//! various wire formats used by this program. Where possible, it uses lifetimes
9//! and immutability to allow for safe zero-copy parsing.
10//!
11//! # Endianness
12//!
13//! All values exposed or consumed by this crate are in host byte order, so the
14//! caller does not need to worry about it. Any necessary conversions are
15//! performed under the hood.
16
17#![cfg_attr(not(test), no_std)]
18// TODO(joshlf): Move into debug_err! and debug_err_fn! definitions once
19// attributes are allowed on expressions
20// (https://github.com/rust-lang/rust/issues/15701).
21#![allow(clippy::blocks_in_conditions)]
22#![deny(missing_docs, unreachable_patterns)]
23
24extern crate alloc;
25
26/// Emit a debug message and return an error.
27///
28/// Invoke the `debug!` macro on all but the first argument. A call to
29/// `debug_err!(err, ...)` is an expression whose value is the expression `err`.
30macro_rules! debug_err {
31    ($err:expr, $($arg:tt)*) => (
32        // TODO(joshlf): Uncomment once attributes are allowed on expressions
33        // #[cfg_attr(feature = "cargo-clippy", allow(block_in_if_condition_stmt))]
34        {
35            use ::log::debug;
36            debug!($($arg)*);
37            $err
38        }
39    )
40}
41
42/// Create a closure which emits a debug message and returns an error.
43///
44/// Create a closure which, when called, invokes the `debug!` macro on all but
45/// the first argument, and returns the first argument.
46macro_rules! debug_err_fn {
47    ($err:expr, $($arg:tt)*) => (
48        // TODO(joshlf): Uncomment once attributes are allowed on expressions
49        // #[cfg_attr(feature = "cargo-clippy", allow(block_in_if_condition_stmt))]
50        || {
51            use ::log::debug;
52            debug!($($arg)*);
53            $err
54        }
55    )
56}
57
58#[macro_use]
59mod macros;
60pub mod arp;
61pub mod error;
62pub mod ethernet;
63pub mod gmp;
64pub mod icmp;
65pub mod igmp;
66pub mod ip;
67pub mod ipv4;
68pub mod ipv6;
69pub mod tcp;
70pub mod testdata;
71pub mod testutil;
72pub mod udp;
73pub mod utils;
74
75use core::num::TryFromIntError;
76
77use byteorder::{ByteOrder, NetworkEndian};
78use internet_checksum::Checksum;
79use net_types::ip::{Ip, IpAddress, IpInvariant as IpInv, Ipv6Addr};
80use packet::{FragmentedBytesMut, SerializeTarget};
81
82// The "sealed trait" pattern.
83//
84// https://rust-lang.github.io/api-guidelines/future-proofing.html
85mod private {
86    pub trait Sealed {}
87}
88
89/// The maximum size of a transport-level header.
90pub const TRANSPORT_HEADER_MAX_SIZE: usize = crate::tcp::MAX_HDR_LEN;
91
92/// The checksumming action that should be performed during serialization based
93/// on available checksum offloading capabilities.
94#[derive(Debug, Copy, Clone, PartialEq)]
95pub enum TransportChecksumAction {
96    /// A full checksum should be computed.
97    ComputeFull,
98    /// A partial checksum over the IP pseudo-header should be computed.
99    ComputePartial,
100}
101
102/// Calls `f` with the serialized IP pseudo-header bytes for IP version `I`.
103fn with_pseudo_header_bytes<I: Ip, R>(
104    src_ip: I::Addr,
105    dst_ip: I::Addr,
106    proto: u8,
107    transport_len: usize,
108    f: impl FnOnce(&[u8]) -> R,
109) -> Result<R, TryFromIntError> {
110    I::map_ip_in(
111        (src_ip, dst_ip, IpInv(proto), IpInv(transport_len), IpInv(f)),
112        |(src_ip, dst_ip, IpInv(proto), IpInv(transport_len), IpInv(f))| {
113            // 4 bytes for src_ip + 4 bytes for dst_ip + 1 byte of zeros + 1
114            // byte for protocol + 2 bytes for total_len
115            let mut pseudo_header = [0u8; 12];
116            (&mut pseudo_header[..4]).copy_from_slice(src_ip.bytes());
117            (&mut pseudo_header[4..8]).copy_from_slice(dst_ip.bytes());
118            pseudo_header[9] = proto;
119            NetworkEndian::write_u16(&mut pseudo_header[10..12], transport_len.try_into()?);
120            Ok(f(&pseudo_header))
121        },
122        |(src_ip, dst_ip, IpInv(proto), IpInv(transport_len), IpInv(f))| {
123            // 16 bytes for src_ip + 16 bytes for dst_ip + 4 bytes for
124            // total_len + 3 bytes of zeroes + 1 byte for next header
125            let mut pseudo_header = [0u8; 40];
126            (&mut pseudo_header[..16]).copy_from_slice(src_ip.bytes());
127            (&mut pseudo_header[16..32]).copy_from_slice(dst_ip.bytes());
128            NetworkEndian::write_u32(&mut pseudo_header[32..36], transport_len.try_into()?);
129            pseudo_header[39] = proto;
130            Ok(f(&pseudo_header))
131        },
132    )
133}
134
135/// Updates `checksum` with the transport pseudo-header for IP version `I`.
136pub fn add_transport_pseudo_header_checksum<I: Ip>(
137    checksum: &mut Checksum,
138    src_ip: I::Addr,
139    dst_ip: I::Addr,
140    proto: u8,
141    transport_len: usize,
142) -> Result<(), TryFromIntError> {
143    with_pseudo_header_bytes::<I, _>(src_ip, dst_ip, proto, transport_len, |pseudo_header| {
144        // add_bytes contains some branching logic at the beginning which is
145        // a bit more expensive than the main loop of the algorithm. In
146        // order to make sure we go through that logic as few times as
147        // possible, we construct the entire pseudo-header first, and then
148        // add it to the checksum all at once.
149        checksum.add_bytes(pseudo_header)
150    })
151}
152
153/// Returns `checksum` with the transport pseudo-header for IP version `I`
154/// subtracted out.
155pub fn remove_transport_pseudo_header_checksum<I: Ip>(
156    checksum: [u8; 2],
157    src_ip: I::Addr,
158    dst_ip: I::Addr,
159    proto: u8,
160    transport_len: usize,
161) -> Result<[u8; 2], TryFromIntError> {
162    with_pseudo_header_bytes::<I, _>(src_ip, dst_ip, proto, transport_len, |pseudo_header| {
163        // add_bytes contains some branching logic at the beginning which is
164        // a bit more expensive than the main loop of the algorithm. In
165        // order to make sure we go through that logic as few times as
166        // possible, we construct the entire pseudo-header first, and then
167        // add it to the checksum all at once.
168        internet_checksum::remove(checksum, pseudo_header)
169    })
170}
171
172/// Compute the checksum used by TCP and UDP.
173///
174/// `compute_transport_checksum` computes the checksum used by TCP and UDP. For
175/// IPv4, the total packet length `transport_len` must fit in a `u16`, and for
176/// IPv6, a `u32`. If the provided packet is too big,
177/// `compute_transport_checksum` returns `None`.
178fn compute_transport_checksum_parts<'a, A: IpAddress, P>(
179    src_ip: A,
180    dst_ip: A,
181    proto: u8,
182    parts: P,
183) -> Option<[u8; 2]>
184where
185    P: Iterator<Item = &'a &'a [u8]> + Clone,
186{
187    // See for details:
188    // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
189    let mut checksum = Checksum::new();
190    let transport_len = parts.clone().map(|b| b.len()).sum();
191    add_transport_pseudo_header_checksum::<A::Version>(
192        &mut checksum,
193        src_ip,
194        dst_ip,
195        proto,
196        transport_len,
197    )
198    .ok()?;
199    for p in parts {
200        checksum.add_bytes(p);
201    }
202    Some(checksum.checksum())
203}
204
205/// Compute the checksum used by TCP and UDP.
206///
207/// Same as [`compute_transport_checksum_parts`] but gets the parts from a
208/// `SerializeTarget`.
209fn compute_transport_checksum_serialize<A: IpAddress>(
210    src_ip: A,
211    dst_ip: A,
212    proto: u8,
213    target: &SerializeTarget<'_>,
214    body: FragmentedBytesMut<'_, '_>,
215) -> Option<[u8; 2]> {
216    // See for details:
217    // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
218    let mut checksum = Checksum::new();
219    let transport_len = target.header.len() + body.len() + target.footer.len();
220    add_transport_pseudo_header_checksum::<A::Version>(
221        &mut checksum,
222        src_ip,
223        dst_ip,
224        proto,
225        transport_len,
226    )
227    .ok()?;
228
229    checksum.add_bytes(target.header);
230    for p in body.iter_fragments() {
231        checksum.add_bytes(p);
232    }
233    checksum.add_bytes(target.footer);
234    Some(checksum.checksum())
235}
236
237/// Computes just the pseudo-header portion of a TCP or UDP checksum.
238///
239/// Returns the one's complement sum, as expected by hardware offloading engines.
240fn compute_transport_pseudo_header_partial_checksum<A: IpAddress>(
241    src_ip: A,
242    dst_ip: A,
243    proto: u8,
244    target: &SerializeTarget<'_>,
245    body: FragmentedBytesMut<'_, '_>,
246) -> Option<[u8; 2]> {
247    // See for details:
248    // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
249    let mut checksum = Checksum::new();
250    let transport_len = target.header.len() + body.len() + target.footer.len();
251    add_transport_pseudo_header_checksum::<A::Version>(
252        &mut checksum,
253        src_ip,
254        dst_ip,
255        proto,
256        transport_len,
257    )
258    .ok()?;
259    checksum.partial_checksum()
260}
261
262/// Compute the checksum used by TCP and UDP.
263///
264/// Same as [`compute_transport_checksum_parts`] but with a single part.
265#[cfg(test)]
266fn compute_transport_checksum<A: IpAddress>(
267    src_ip: A,
268    dst_ip: A,
269    proto: u8,
270    packet: &[u8],
271) -> Option<[u8; 2]> {
272    let mut checksum = Checksum::new();
273    add_transport_pseudo_header_checksum::<A::Version>(
274        &mut checksum,
275        src_ip,
276        dst_ip,
277        proto,
278        packet.len(),
279    )
280    .ok()?;
281    checksum.add_bytes(packet);
282    Some(checksum.checksum())
283}