use std::net::Ipv4Addr;
use std::sync::atomic::{AtomicU16, Ordering};
use super::{
ETH_HEADER_LEN,
checksum::{ipv4_header_checksum, udp_checksum},
};
pub const MAX_UDP_PAYLOAD: usize = 65_507;
static NEXT_IP_ID: AtomicU16 = AtomicU16::new(1);
#[must_use]
#[allow(clippy::too_many_arguments)] pub fn build_udp_ip_ethernet(
src_ip: Ipv4Addr,
dst_ip: Ipv4Addr,
src_port: u16,
dst_port: u16,
payload: &[u8],
src_mac: [u8; 6],
dst_mac: [u8; 6],
mtu: usize,
) -> Vec<Vec<u8>> {
assert!(mtu >= 36, "mtu {mtu} cannot carry an IPv4 fragment");
if payload.len() > MAX_UDP_PAYLOAD {
return Vec::new();
}
let udp_len = 8 + payload.len();
let mut datagram = vec![0u8; udp_len];
datagram[0..2].copy_from_slice(&src_port.to_be_bytes());
datagram[2..4].copy_from_slice(&dst_port.to_be_bytes());
datagram[4..6].copy_from_slice(&(udp_len as u16).to_be_bytes());
datagram[8..].copy_from_slice(payload);
let udp_cksum = udp_checksum(src_ip, dst_ip, &datagram);
datagram[6..8].copy_from_slice(&udp_cksum.to_be_bytes());
let ip_id = NEXT_IP_ID.fetch_add(1, Ordering::Relaxed);
if 20 + udp_len <= mtu {
return vec![build_frame(
src_ip, dst_ip, ip_id, 0, false, &datagram, src_mac, dst_mac,
)];
}
let chunk = (mtu - 20) & !7;
datagram
.chunks(chunk)
.enumerate()
.map(|(i, part)| {
let offset = i * chunk;
let more = offset + part.len() < udp_len;
build_frame(src_ip, dst_ip, ip_id, offset, more, part, src_mac, dst_mac)
})
.collect()
}
#[allow(clippy::too_many_arguments)] fn build_frame(
src_ip: Ipv4Addr,
dst_ip: Ipv4Addr,
ip_id: u16,
frag_offset_bytes: usize,
more_fragments: bool,
ip_payload: &[u8],
src_mac: [u8; 6],
dst_mac: [u8; 6],
) -> Vec<u8> {
let ip_total_len = 20 + ip_payload.len();
let mut frame = vec![0u8; ETH_HEADER_LEN + ip_total_len];
frame[0..6].copy_from_slice(&dst_mac);
frame[6..12].copy_from_slice(&src_mac);
frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
let ip = &mut frame[ETH_HEADER_LEN..];
ip[0] = 0x45; ip[2..4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
ip[4..6].copy_from_slice(&ip_id.to_be_bytes());
let flags_frag = (u16::from(more_fragments) << 13) | ((frag_offset_bytes / 8) as u16);
ip[6..8].copy_from_slice(&flags_frag.to_be_bytes());
ip[8] = 64; ip[9] = 17; ip[12..16].copy_from_slice(&src_ip.octets());
ip[16..20].copy_from_slice(&dst_ip.octets());
let ip_cksum = ipv4_header_checksum(&ip[..20]);
ip[10..12].copy_from_slice(&ip_cksum.to_be_bytes());
frame[ETH_HEADER_LEN + 20..].copy_from_slice(ip_payload);
frame
}