blob: d88a3001b4b7d62a1631d5a0bf5204bc236f0c8b [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 std::num::NonZeroU16;
use byteorder::{BigEndian, ByteOrder};
use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified};
use ip::{Ip, IpAddr, IpProto, IpVersion};
use transport::tcp::TcpOption;
use wire::util::{Checksum, Options, PacketFormat};
use self::options::TcpOptionImpl;
const HEADER_PREFIX_SIZE: usize = 20;
// zerocopy!(struct HeaderPrefix {
// src_port: u16,
// dst_port: u16,
// seq_num: u32,
// ack_num: u32,
// data_off: u4,
// reserved: u3,
// ns: bool,
// cwr: bool,
// ece: bool,
// urg: bool,
// ack: bool,
// psh: bool,
// rst: bool,
// syn: bool,
// fin: bool,
// window_size: u16,
// checksum: u16,
// urg_ptr: u16,
// });
// HeaderPrefix has the same memory layout (thanks to repr(C, packed)) as a TCP
// header. Thus, we can simply reinterpret the bytes of the TCP 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 {
src_port: [u8; 2],
dst_port: [u8; 2],
seq_num: [u8; 4],
ack: [u8; 4],
data_off_reserved_ns: u8,
flags: u8,
window_size: [u8; 2],
checksum: [u8; 2],
urg_ptr: [u8; 2],
}
unsafe impl FromBytes for HeaderPrefix {}
unsafe impl AsBytes for HeaderPrefix {}
impl HeaderPrefix {
pub fn src_port(&self) -> u16 {
BigEndian::read_u16(&self.src_port)
}
pub fn dst_port(&self) -> u16 {
BigEndian::read_u16(&self.dst_port)
}
fn data_off(&self) -> u8 {
self.data_off_reserved_ns >> 4
}
}
/// A TCP segment.
///
/// A `TcpSegment` 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 TcpSegment<B> {
hdr_prefix: LayoutVerified<B, HeaderPrefix>,
options: Options<B, TcpOptionImpl>,
body: B,
}
impl<B> PacketFormat for TcpSegment<B> {
const MAX_HEADER_BYTES: usize = 60;
const MAX_FOOTER_BYTES: usize = 0;
}
impl<B: ByteSlice> TcpSegment<B> {
/// Parse a TCP segment.
///
/// `parse` parses `bytes` as a TCP segment and validates the checksum.
#[cfg_attr(feature = "clippy", allow(needless_pass_by_value))]
pub fn parse<A: IpAddr>(bytes: B, src_ip: A, dst_ip: A) -> Result<TcpSegment<B>, ()> {
// See for details: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
let total_len = bytes.len();
let (hdr_prefix, rest) =
LayoutVerified::<B, HeaderPrefix>::new_from_prefix(bytes).ok_or(())?;
let hdr_bytes = (hdr_prefix.data_off() * 4) as usize;
if hdr_bytes > HEADER_PREFIX_SIZE + rest.len() {
return Err(());
}
let (options, body) = rest.split_at(hdr_bytes - HEADER_PREFIX_SIZE);
let options = Options::parse(options).map_err(|_| ())?;
let segment = TcpSegment {
hdr_prefix,
options,
body,
};
// For IPv4, the "TCP length" field in the pseudo-header used for
// calculating checksums is 16 bits. For IPv6, it's 32 bits. Verify that
// the length of the entire payload (including header) does not
// overflow. On 32-bit platforms for IPv6, we omit the check since '1 <<
// 32' would overflow usize.
if A::Version::VERSION == IpVersion::V4 && total_len >= 1 << 16
|| (!cfg!(target_pointer_width = "32") && total_len >= 1 << 32)
{
return Err(());
}
if segment.compute_checksum(src_ip, dst_ip) != 0 {
return Err(());
}
Ok(segment)
}
pub fn iter_options<'a>(&'a self) -> impl 'a + Iterator<Item = TcpOption> {
self.options.iter()
}
}
impl<B: ByteSlice> TcpSegment<B> {
fn compute_checksum<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> u16 {
// See for details: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation
let mut checksum = Checksum::new();
checksum.add_bytes(src_ip.bytes());
checksum.add_bytes(dst_ip.bytes());
let total_len =
self.hdr_prefix.bytes().len() + self.options.bytes().len() + self.body.len();
if A::Version::VERSION == IpVersion::V4 {
checksum.add_bytes(&[0]);
checksum.add_bytes(&[IpProto::Tcp as u8]);
// For IPv4, the "TCP length" field in the pseudo-header is 16 bits.
let mut l = [0; 2];
BigEndian::write_u16(&mut l, total_len as u16);
checksum.add_bytes(&l);
} else {
// For IPv6, the "TCP length" field in the pseudo-header is 32 bits.
let mut l = [0; 4];
BigEndian::write_u32(&mut l, total_len as u32);
checksum.add_bytes(&l);
checksum.add_bytes(&[0; 3]);
checksum.add_bytes(&[IpProto::Tcp as u8])
}
checksum.add_bytes(self.hdr_prefix.bytes());
checksum.add_bytes(self.options.bytes());
checksum.add_bytes(&self.body);
checksum.sum()
}
pub fn body(&self) -> &[u8] {
&self.body
}
pub fn src_port(&self) -> NonZeroU16 {
NonZeroU16::new(self.hdr_prefix.src_port()).unwrap()
}
pub fn dst_port(&self) -> NonZeroU16 {
NonZeroU16::new(self.hdr_prefix.dst_port()).unwrap()
}
pub fn seq_num(&self) -> u32 {
// self.hdr_prefix.get_seq_num()
BigEndian::read_u32(&self.hdr_prefix.seq_num)
}
fn get_flag(&self, mask: u8) -> bool {
self.hdr_prefix.flags & mask > 0
}
pub fn ack_num(&self) -> u32 {
// self.hdr_prefix.get_ack_num()
BigEndian::read_u32(&self.hdr_prefix.ack)
}
pub fn ack(&self) -> bool {
// self.hdr_prefix.get_ack()
self.get_flag(ACK_MASK)
}
pub fn rst(&self) -> bool {
// self.hdr_prefix.get_rst()
self.get_flag(RST_MASK)
}
pub fn syn(&self) -> bool {
// self.hdr_prefix.get_syn()
self.get_flag(SYN_MASK)
}
pub fn fin(&self) -> bool {
// self.hdr_prefix.get_fin()
self.get_flag(FIN_MASK)
}
pub fn window_size(&self) -> u16 {
// self.hdr_prefix.get_window_size()
BigEndian::read_u16(&self.hdr_prefix.window_size)
}
}
impl<'a> TcpSegment<&'a mut [u8]> {
pub fn set_src_port(&mut self, src_port: NonZeroU16) {
// self.hdr_prefix.set_src_port(src_port.get());
BigEndian::write_u16(&mut self.hdr_prefix.src_port, src_port.get());
}
pub fn set_dst_port(&mut self, dst_port: NonZeroU16) {
// self.hdr_prefix.set_src_port(dst_port.get());
BigEndian::write_u16(&mut self.hdr_prefix.dst_port, dst_port.get());
}
pub fn set_seq_num(&mut self, seq_num: u32) {
// self.hdr_prefix.set_seq_num(seq_num);
BigEndian::write_u32(&mut self.hdr_prefix.seq_num, seq_num);
}
pub fn set_ack_num(&mut self, ack_num: u32) {
// self.hdr_prefix.set_seq_num(ack_num);
BigEndian::write_u32(&mut self.hdr_prefix.ack, ack_num);
}
fn set_flag(&mut self, mask: u8, set: bool) {
if set {
self.hdr_prefix.flags |= mask;
} else {
self.hdr_prefix.flags &= 0xFF - mask;
}
}
pub fn set_ack(&mut self, ack: bool) {
// self.hdr_prefix.set_ack(ack);
self.set_flag(ACK_MASK, ack);
}
pub fn set_rst(&mut self, rst: bool) {
// self.hdr_prefix.set_rst(rst);
self.set_flag(RST_MASK, rst);
}
pub fn set_syn(&mut self, syn: bool) {
// self.hdr_prefix.set_syn(syn);
self.set_flag(SYN_MASK, syn);
}
pub fn set_fin(&mut self, fin: bool) {
// self.hdr_prefix.set_fin(fin);
self.set_flag(FIN_MASK, fin);
}
pub fn set_window_size(&mut self, window_size: u16) {
// self.hdr_prefix.set_window_size(window_size);
BigEndian::write_u16(&mut self.hdr_prefix.window_size, window_size);
}
/// Compute and set the TCP checksum.
///
/// Compute the TCP checksum from the current segment state, source IP, and
/// destination IP, and set it in the header.
pub fn set_checksum<A: IpAddr>(&mut self, src_ip: A, dst_ip: A) {
// self.hdr_prefix.set_checksum(0);
self.hdr_prefix.checksum = [0, 0];
let c = self.compute_checksum(src_ip, dst_ip);
// self.hdr_prefix.set_checksum(c);
BigEndian::write_u16(&mut self.hdr_prefix.checksum, c);
}
}
const ACK_MASK: u8 = 1u8 << 4;
const RST_MASK: u8 = 1u8 << 2;
const SYN_MASK: u8 = 1u8 << 1;
const FIN_MASK: u8 = 1u8;
mod options {
use std::mem;
use byteorder::{BigEndian, ByteOrder};
use transport::tcp::{TcpOption, TcpSackBlock};
use wire::util::OptionImpl;
fn parse_sack_block(bytes: &[u8]) -> TcpSackBlock {
TcpSackBlock {
left_edge: BigEndian::read_u32(bytes),
right_edge: BigEndian::read_u32(&bytes[4..]),
}
}
const OPTION_KIND_EOL: u8 = 0;
const OPTION_KIND_NOP: u8 = 1;
const OPTION_KIND_MSS: u8 = 2;
const OPTION_KIND_WINDOW_SCALE: u8 = 3;
const OPTION_KIND_SACK_PERMITTED: u8 = 4;
const OPTION_KIND_SACK: u8 = 5;
const OPTION_KIND_TIMESTAMP: u8 = 8;
pub struct TcpOptionImpl;
impl OptionImpl for TcpOptionImpl {
type Output = TcpOption;
type Error = ();
fn parse(kind: u8, data: &[u8]) -> Result<Option<TcpOption>, ()> {
match kind {
self::OPTION_KIND_EOL | self::OPTION_KIND_NOP => {
unreachable!("wire::util::Options promises to handle EOL and NOP")
}
self::OPTION_KIND_MSS => if data.len() != 2 {
Err(())
} else {
Ok(Some(TcpOption::Mss(BigEndian::read_u16(&data))))
},
self::OPTION_KIND_WINDOW_SCALE => if data.len() != 1 {
Err(())
} else {
Ok(Some(TcpOption::WindowScale(data[0])))
},
self::OPTION_KIND_SACK_PERMITTED => if !data.is_empty() {
Err(())
} else {
Ok(Some(TcpOption::SackPermitted))
},
self::OPTION_KIND_SACK => match data.len() {
8 | 16 | 24 | 32 => {
let num_blocks = data.len() / mem::size_of::<TcpSackBlock>();
let mut blocks = [TcpSackBlock::default(); 4];
for i in 0..num_blocks {
blocks[i] = parse_sack_block(&data[i * 8..]);
}
Ok(Some(TcpOption::Sack {
blocks,
num_blocks: num_blocks as u8,
}))
}
_ => Err(()),
},
self::OPTION_KIND_TIMESTAMP => if data.len() != 8 {
Err(())
} else {
let ts_val = BigEndian::read_u32(&data);
let ts_echo_reply = BigEndian::read_u32(&data[4..]);
Ok(Some(TcpOption::Timestamp {
ts_val,
ts_echo_reply,
}))
},
_ => Ok(None),
}
}
}
}