blob: 9ac3a15c02bf501ff8ce1faacb7c312b1b74fc64 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use byteorder::{BigEndian, ByteOrder};
use zerocopy::{AsBytes, ByteSlice, FromBytes, Unaligned};
use ip::{Ipv4Addr, Ipv4Option};
use wire::util::packet;
use wire::util::packet::{PacketWithOptions, PacketWithOptionsParseErr};
use wire::util::{Checksum, OptionParseErr, Options, PacketFormat};
use self::options::Ipv4OptionImpl;
const HEADER_PREFIX_SIZE: usize = 20;
// HeaderPrefix has the same memory layout (thanks to repr(C, packed)) as an
// IPv4 header. Thus, we can simply reinterpret the bytes of the IPv4 header as
// a HeaderPrefix and then safely access its fields. Note, however, that it is
// *not* safe to have the types of any of the fields be anything other than u8
// or [u8; x] since network byte order (big endian) may not be the same as the
// endianness of the computer we're running on, and since repr(packed) is only
// safe with values with no alignment requirements.
#[repr(C, packed)]
struct HeaderPrefix {
version_ihl: u8,
dscp_ecn: u8,
total_len: [u8; 2],
id: [u8; 2],
flags_frag_off: [u8; 2],
ttl: u8,
proto: u8,
hdr_checksum: [u8; 2],
src_ip: [u8; 4],
dst_ip: [u8; 4],
}
unsafe impl FromBytes for HeaderPrefix {}
unsafe impl AsBytes for HeaderPrefix {}
unsafe impl Unaligned for HeaderPrefix {}
impl HeaderPrefix {
fn version(&self) -> u8 {
self.version_ihl >> 4
}
fn ihl(&self) -> u8 {
self.version_ihl & 0xF
}
}
impl packet::Header for HeaderPrefix {
type Error = ();
fn total_packet_len(&self) -> Result<Option<usize>, ()> {
Ok(Some(BigEndian::read_u16(&self.total_len[..]) as usize))
}
}
impl packet::HeaderPrefix for HeaderPrefix {
fn total_header_len(&self) -> Result<usize, ()> {
Ok(self.ihl() as usize * 4)
}
}
/// An IPv4 packet.
///
/// An `Ipv4Packet` shares its underlying memory with the byte slice it was
/// parsed from or serialized to, meaning that no copying or extra allocation is
/// necessary.
// pub struct Ipv4Packet<B> {
// hdr_prefix: LayoutVerified<B, HeaderPrefix>,
// options: Options<B, Ipv4OptionImpl>,
// body: B,
// }
pub struct Ipv4Packet<B>(PacketWithOptions<B, HeaderPrefix, Options<B, Ipv4OptionImpl>>);
impl<B> PacketFormat for Ipv4Packet<B> {
const MAX_HEADER_BYTES: usize = 60;
const MAX_FOOTER_BYTES: usize = 0;
}
pub enum Ipv4ParseErr {
Packet(PacketWithOptionsParseErr<(), OptionParseErr<()>>),
Version,
Checksum,
}
impl<B: ByteSlice> Ipv4Packet<B> {
/// Parse an IPv4 packet.
///
/// `parse` parses `bytes` as an IPv4 packet and validates the checksum.
#[cfg_attr(feature = "clippy", allow(needless_pass_by_value))]
pub fn parse(bytes: B) -> Result<Ipv4Packet<B>, Ipv4ParseErr> {
// See for details: https://en.wikipedia.org/wiki/IPv4#Header
let packet: PacketWithOptions<B, HeaderPrefix, _> =
PacketWithOptions::parse(bytes).map_err(Ipv4ParseErr::Packet)?;
let packet = Ipv4Packet(packet);
if packet.0.header_prefix().version() != 4 {
return Err(Ipv4ParseErr::Version);
}
if packet.compute_header_checksum() != 0 {
return Err(Ipv4ParseErr::Checksum);
}
Ok(packet)
}
pub fn iter_options<'a>(&'a self) -> impl 'a + Iterator<Item = Ipv4Option> {
self.0.options().iter()
}
}
impl<B: ByteSlice> Ipv4Packet<B> {
fn compute_header_checksum(&self) -> u16 {
let mut c = Checksum::new();
c.add_bytes(self.0.header_prefix_bytes());
c.add_bytes(self.0.options().bytes());
c.sum()
}
pub fn body(&self) -> &[u8] {
self.0.body()
}
pub fn version(&self) -> u8 {
self.0.header_prefix().version()
}
pub fn ihl(&self) -> u8 {
self.0.header_prefix().ihl()
}
pub fn dscp(&self) -> u8 {
self.0.header_prefix().dscp_ecn >> 2
}
pub fn ecn(&self) -> u8 {
self.0.header_prefix().dscp_ecn & 3
}
pub fn total_length(&self) -> u16 {
BigEndian::read_u16(&self.0.header_prefix().total_len)
}
pub fn id(&self) -> u16 {
BigEndian::read_u16(&self.0.header_prefix().id)
}
pub fn flags(&self) -> u8 {
self.0.header_prefix().flags_frag_off[0] >> 5
}
pub fn fragment_offset(&self) -> u16 {
((u16::from(self.0.header_prefix().flags_frag_off[0] & 0x1F)) << 8)
| u16::from(self.0.header_prefix().flags_frag_off[1])
}
pub fn ttl(&self) -> u8 {
self.0.header_prefix().ttl
}
pub fn proto(&self) -> u8 {
self.0.header_prefix().proto
}
pub fn hdr_checksum(&self) -> u16 {
BigEndian::read_u16(&self.0.header_prefix().hdr_checksum)
}
pub fn src_ip(&self) -> Ipv4Addr {
Ipv4Addr::new(self.0.header_prefix().src_ip)
}
pub fn dst_ip(&self) -> Ipv4Addr {
Ipv4Addr::new(self.0.header_prefix().dst_ip)
}
}
impl<'a> Ipv4Packet<&'a mut [u8]> {
pub fn set_id(&mut self, id: u16) {
BigEndian::write_u16(&mut self.0.header_prefix_mut().id, id);
}
pub fn set_ttl(&mut self, ttl: u8) {
self.0.header_prefix_mut().ttl = ttl;
}
pub fn set_proto(&mut self, proto: u8) {
self.0.header_prefix_mut().proto = proto;
}
pub fn set_src_ip(&mut self, src_ip: Ipv4Addr) {
self.0.header_prefix_mut().src_ip = src_ip.ipv4_bytes();
}
pub fn set_dst_ip(&mut self, dst_ip: Ipv4Addr) {
self.0.header_prefix_mut().dst_ip = dst_ip.ipv4_bytes();
}
/// Compute and set the header checksum.
///
/// Compute the header checksum from the current header state, and set it in
/// the header.
pub fn set_checksum(&mut self) {
self.0.header_prefix_mut().hdr_checksum = [0, 0];
let c = self.compute_header_checksum();
BigEndian::write_u16(&mut self.0.header_prefix_mut().hdr_checksum, c);
}
}
mod options {
use ip::{Ipv4Option, Ipv4OptionInner};
use wire::util::OptionImpl;
const OPTION_KIND_EOL: u8 = 0;
const OPTION_KIND_NOP: u8 = 1;
pub struct Ipv4OptionImpl;
impl OptionImpl for Ipv4OptionImpl {
type Output = Ipv4Option;
type Error = ();
fn parse(kind: u8, data: &[u8]) -> Result<Option<Ipv4Option>, ()> {
let copied = kind & (1 << 7) > 0;
match kind {
self::OPTION_KIND_EOL | self::OPTION_KIND_NOP => {
unreachable!("wire::util::Options promises to handle EOL and NOP")
}
kind => if data.len() > 38 {
Err(())
} else {
let len = data.len();
let mut d = [0u8; 38];
(&mut d[..len]).copy_from_slice(data);
Ok(Some(Ipv4Option {
copied,
inner: Ipv4OptionInner::Unrecognized {
kind,
len: len as u8,
data: d,
},
}))
},
}
}
}
}