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
102fn update_transport_checksum_pseudo_header<I: Ip>(
103 checksum: &mut Checksum,
104 src_ip: I::Addr,
105 dst_ip: I::Addr,
106 proto: u8,
107 transport_len: usize,
108) -> Result<(), TryFromIntError> {
109 I::map_ip_in(
110 (IpInv(checksum), src_ip, dst_ip, IpInv(proto), IpInv(transport_len)),
111 |(IpInv(checksum), src_ip, dst_ip, IpInv(proto), IpInv(transport_len))| {
112 let pseudo_header = {
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 pseudo_header
121 };
122 // add_bytes contains some branching logic at the beginning which is
123 // a bit more expensive than the main loop of the algorithm. In
124 // order to make sure we go through that logic as few times as
125 // possible, we construct the entire pseudo-header first, and then
126 // add it to the checksum all at once.
127 checksum.add_bytes(&pseudo_header[..]);
128 Ok(())
129 },
130 |(IpInv(checksum), src_ip, dst_ip, IpInv(proto), IpInv(transport_len))| {
131 let pseudo_header = {
132 // 16 bytes for src_ip + 16 bytes for dst_ip + 4 bytes for
133 // total_len + 3 bytes of zeroes + 1 byte for next header
134 let mut pseudo_header = [0u8; 40];
135 (&mut pseudo_header[..16]).copy_from_slice(src_ip.bytes());
136 (&mut pseudo_header[16..32]).copy_from_slice(dst_ip.bytes());
137 NetworkEndian::write_u32(&mut pseudo_header[32..36], transport_len.try_into()?);
138 pseudo_header[39] = proto;
139 pseudo_header
140 };
141 // add_bytes contains some branching logic at the beginning which is
142 // a bit more expensive than the main loop of the algorithm. In
143 // order to make sure we go through that logic as few times as
144 // possible, we construct the entire pseudo-header first, and then
145 // add it to the checksum all at once.
146 checksum.add_bytes(&pseudo_header[..]);
147 Ok(())
148 },
149 )
150}
151
152/// Compute the checksum used by TCP and UDP.
153///
154/// `compute_transport_checksum` computes the checksum used by TCP and UDP. For
155/// IPv4, the total packet length `transport_len` must fit in a `u16`, and for
156/// IPv6, a `u32`. If the provided packet is too big,
157/// `compute_transport_checksum` returns `None`.
158fn compute_transport_checksum_parts<'a, A: IpAddress, P>(
159 src_ip: A,
160 dst_ip: A,
161 proto: u8,
162 parts: P,
163) -> Option<[u8; 2]>
164where
165 P: Iterator<Item = &'a &'a [u8]> + Clone,
166{
167 // See for details:
168 // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
169 let mut checksum = Checksum::new();
170 let transport_len = parts.clone().map(|b| b.len()).sum();
171 update_transport_checksum_pseudo_header::<A::Version>(
172 &mut checksum,
173 src_ip,
174 dst_ip,
175 proto,
176 transport_len,
177 )
178 .ok()?;
179 for p in parts {
180 checksum.add_bytes(p);
181 }
182 Some(checksum.checksum())
183}
184
185/// Compute the checksum used by TCP and UDP.
186///
187/// Same as [`compute_transport_checksum_parts`] but gets the parts from a
188/// `SerializeTarget`.
189fn compute_transport_checksum_serialize<A: IpAddress>(
190 src_ip: A,
191 dst_ip: A,
192 proto: u8,
193 target: &SerializeTarget<'_>,
194 body: FragmentedBytesMut<'_, '_>,
195) -> Option<[u8; 2]> {
196 // See for details:
197 // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
198 let mut checksum = Checksum::new();
199 let transport_len = target.header.len() + body.len() + target.footer.len();
200 update_transport_checksum_pseudo_header::<A::Version>(
201 &mut checksum,
202 src_ip,
203 dst_ip,
204 proto,
205 transport_len,
206 )
207 .ok()?;
208
209 checksum.add_bytes(target.header);
210 for p in body.iter_fragments() {
211 checksum.add_bytes(p);
212 }
213 checksum.add_bytes(target.footer);
214 Some(checksum.checksum())
215}
216
217/// Computes just the pseudo-header portion of a TCP or UDP checksum.
218///
219/// Returns the one's complement sum, as expected by hardware offloading engines.
220fn compute_transport_pseudo_header_partial_checksum<A: IpAddress>(
221 src_ip: A,
222 dst_ip: A,
223 proto: u8,
224 target: &SerializeTarget<'_>,
225 body: FragmentedBytesMut<'_, '_>,
226) -> Option<[u8; 2]> {
227 // See for details:
228 // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
229 let mut checksum = Checksum::new();
230 let transport_len = target.header.len() + body.len() + target.footer.len();
231 update_transport_checksum_pseudo_header::<A::Version>(
232 &mut checksum,
233 src_ip,
234 dst_ip,
235 proto,
236 transport_len,
237 )
238 .ok()?;
239 checksum.partial_checksum()
240}
241
242/// Compute the checksum used by TCP and UDP.
243///
244/// Same as [`compute_transport_checksum_parts`] but with a single part.
245#[cfg(test)]
246fn compute_transport_checksum<A: IpAddress>(
247 src_ip: A,
248 dst_ip: A,
249 proto: u8,
250 packet: &[u8],
251) -> Option<[u8; 2]> {
252 let mut checksum = Checksum::new();
253 update_transport_checksum_pseudo_header::<A::Version>(
254 &mut checksum,
255 src_ip,
256 dst_ip,
257 proto,
258 packet.len(),
259 )
260 .ok()?;
261 checksum.add_bytes(packet);
262 Some(checksum.checksum())
263}