use std::net::Ipv6Addr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Icmpv6Type {
DestinationUnreachable = 1,
PacketTooBig = 2,
TimeExceeded = 3,
ParameterProblem = 4,
EchoRequest = 128,
EchoReply = 129,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DestUnreachableCode {
NoRoute = 0,
AdminProhibited = 1,
BeyondScope = 2,
AddressUnreachable = 3,
PortUnreachable = 4,
SourcePolicy = 5,
RejectRoute = 6,
}
pub const IPPROTO_ICMPV6: u8 = 58;
const MIN_IPV6_MTU: usize = 1280;
const IPV6_HEADER_LEN: usize = 40;
const ICMPV6_HEADER_LEN: usize = 8;
const MAX_ORIGINAL_PACKET: usize = MIN_IPV6_MTU - IPV6_HEADER_LEN - ICMPV6_HEADER_LEN;
pub const FIPS_OVERHEAD: u16 = 16 + 16 + 5 + 35 + 12 + 6 + 16;
pub const FIPS_IPV6_OVERHEAD: u16 = 77;
pub fn effective_ipv6_mtu(transport_mtu: u16) -> u16 {
transport_mtu.saturating_sub(FIPS_IPV6_OVERHEAD)
}
pub fn should_send_icmp_error(packet: &[u8]) -> bool {
if packet.len() < IPV6_HEADER_LEN {
return false;
}
let version = packet[0] >> 4;
if version != 6 {
return false;
}
let src = Ipv6Addr::from(<[u8; 16]>::try_from(&packet[8..24]).unwrap());
if src.is_unspecified() {
return false;
}
if src.octets()[0] == 0xff {
return false;
}
let dst = Ipv6Addr::from(<[u8; 16]>::try_from(&packet[24..40]).unwrap());
if dst.octets()[0] == 0xff {
return false;
}
let next_header = packet[6];
if next_header == IPPROTO_ICMPV6 && packet.len() > IPV6_HEADER_LEN {
let icmp_type = packet[IPV6_HEADER_LEN];
if icmp_type < 128 {
return false;
}
}
true
}
pub fn build_dest_unreachable(
original_packet: &[u8],
code: DestUnreachableCode,
our_addr: Ipv6Addr,
) -> Option<Vec<u8>> {
if original_packet.len() < IPV6_HEADER_LEN {
return None;
}
let dest_addr = Ipv6Addr::from(<[u8; 16]>::try_from(&original_packet[8..24]).unwrap());
let original_len = original_packet.len().min(MAX_ORIGINAL_PACKET);
let icmpv6_len = ICMPV6_HEADER_LEN + original_len;
let total_len = IPV6_HEADER_LEN + icmpv6_len;
let mut response = vec![0u8; total_len];
response[0] = 0x60;
let payload_len = icmpv6_len as u16;
response[4..6].copy_from_slice(&payload_len.to_be_bytes());
response[6] = IPPROTO_ICMPV6;
response[7] = 64;
response[8..24].copy_from_slice(&our_addr.octets());
response[24..40].copy_from_slice(&dest_addr.octets());
let icmp_start = IPV6_HEADER_LEN;
response[icmp_start] = Icmpv6Type::DestinationUnreachable as u8;
response[icmp_start + 1] = code as u8;
response[icmp_start + ICMPV6_HEADER_LEN..].copy_from_slice(&original_packet[..original_len]);
let checksum = icmpv6_checksum(&response[icmp_start..], &our_addr, &dest_addr);
response[icmp_start + 2..icmp_start + 4].copy_from_slice(&checksum.to_be_bytes());
Some(response)
}
pub fn build_packet_too_big(
original_packet: &[u8],
mtu: u32,
our_addr: Ipv6Addr,
) -> Option<Vec<u8>> {
if original_packet.len() < IPV6_HEADER_LEN {
return None;
}
let version = original_packet[0] >> 4;
if version != 6 {
return None;
}
let src_addr = Ipv6Addr::from(<[u8; 16]>::try_from(&original_packet[8..24]).unwrap());
if src_addr.is_unspecified() || src_addr.octets()[0] == 0xff {
return None;
}
let next_header = original_packet[6];
if next_header == IPPROTO_ICMPV6 && original_packet.len() > IPV6_HEADER_LEN {
let icmp_type = original_packet[IPV6_HEADER_LEN];
if icmp_type < 128 {
return None;
}
}
let original_len = original_packet.len().min(MAX_ORIGINAL_PACKET);
let icmpv6_len = ICMPV6_HEADER_LEN + original_len;
let total_len = IPV6_HEADER_LEN + icmpv6_len;
let mut response = vec![0u8; total_len];
response[0] = 0x60;
let payload_len = icmpv6_len as u16;
response[4..6].copy_from_slice(&payload_len.to_be_bytes());
response[6] = IPPROTO_ICMPV6;
response[7] = 64;
response[8..24].copy_from_slice(&our_addr.octets());
response[24..40].copy_from_slice(&src_addr.octets());
let icmp_start = IPV6_HEADER_LEN;
response[icmp_start] = Icmpv6Type::PacketTooBig as u8;
response[icmp_start + 1] = 0;
response[icmp_start + 4..icmp_start + 8].copy_from_slice(&mtu.to_be_bytes());
response[icmp_start + ICMPV6_HEADER_LEN..].copy_from_slice(&original_packet[..original_len]);
let checksum = icmpv6_checksum(&response[icmp_start..], &our_addr, &src_addr);
response[icmp_start + 2..icmp_start + 4].copy_from_slice(&checksum.to_be_bytes());
Some(response)
}
fn icmpv6_checksum(icmpv6_message: &[u8], src: &Ipv6Addr, dst: &Ipv6Addr) -> u16 {
let mut sum: u32 = 0;
for chunk in src.octets().chunks(2) {
sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
}
for chunk in dst.octets().chunks(2) {
sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
}
let len = icmpv6_message.len() as u32;
sum += len >> 16;
sum += len & 0xffff;
sum += IPPROTO_ICMPV6 as u32;
let mut i = 0;
while i + 1 < icmpv6_message.len() {
if i == 2 {
i += 2;
continue;
}
sum += u16::from_be_bytes([icmpv6_message[i], icmpv6_message[i + 1]]) as u32;
i += 2;
}
if i < icmpv6_message.len() {
sum += (icmpv6_message[i] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!(sum as u16)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ipv6_packet(src: Ipv6Addr, dst: Ipv6Addr, next_header: u8, payload: &[u8]) -> Vec<u8> {
let mut packet = vec![0u8; IPV6_HEADER_LEN + payload.len()];
packet[0] = 0x60;
let len = payload.len() as u16;
packet[4..6].copy_from_slice(&len.to_be_bytes());
packet[6] = next_header;
packet[7] = 64;
packet[8..24].copy_from_slice(&src.octets());
packet[24..40].copy_from_slice(&dst.octets());
packet[IPV6_HEADER_LEN..].copy_from_slice(payload);
packet
}
#[test]
fn test_should_send_error_valid_packet() {
let src = "fd00::1".parse().unwrap();
let dst = "fd00::2".parse().unwrap();
let packet = make_ipv6_packet(src, dst, 17, &[0u8; 8]);
assert!(should_send_icmp_error(&packet));
}
#[test]
fn test_should_not_send_error_unspecified_source() {
let src = Ipv6Addr::UNSPECIFIED;
let dst = "fd00::2".parse().unwrap();
let packet = make_ipv6_packet(src, dst, 17, &[0u8; 8]);
assert!(!should_send_icmp_error(&packet));
}
#[test]
fn test_should_not_send_error_multicast_source() {
let src = "ff02::1".parse().unwrap();
let dst = "fd00::2".parse().unwrap();
let packet = make_ipv6_packet(src, dst, 17, &[0u8; 8]);
assert!(!should_send_icmp_error(&packet));
}
#[test]
fn test_should_not_send_error_multicast_destination() {
let src = "fe80::1".parse().unwrap();
let dst = "ff02::2".parse().unwrap(); let packet = make_ipv6_packet(src, dst, 17, &[0u8; 8]);
assert!(!should_send_icmp_error(&packet));
}
#[test]
fn test_should_not_send_error_for_icmp_error() {
let src = "fd00::1".parse().unwrap();
let dst = "fd00::2".parse().unwrap();
let icmp_payload = [1u8, 0, 0, 0, 0, 0, 0, 0];
let packet = make_ipv6_packet(src, dst, IPPROTO_ICMPV6, &icmp_payload);
assert!(!should_send_icmp_error(&packet));
}
#[test]
fn test_should_send_error_for_icmp_echo() {
let src = "fd00::1".parse().unwrap();
let dst = "fd00::2".parse().unwrap();
let icmp_payload = [128u8, 0, 0, 0, 0, 0, 0, 0];
let packet = make_ipv6_packet(src, dst, IPPROTO_ICMPV6, &icmp_payload);
assert!(should_send_icmp_error(&packet));
}
#[test]
fn test_should_not_send_error_short_packet() {
let packet = vec![0u8; 20]; assert!(!should_send_icmp_error(&packet));
}
#[test]
fn test_build_dest_unreachable() {
let src: Ipv6Addr = "fd00::1".parse().unwrap();
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let original = make_ipv6_packet(src, dst, 17, &[0u8; 8]);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_dest_unreachable(&original, DestUnreachableCode::NoRoute, our_addr);
assert!(response.is_some());
let response = response.unwrap();
assert_eq!(response[0] >> 4, 6); assert_eq!(response[6], IPPROTO_ICMPV6);
let resp_src = Ipv6Addr::from(<[u8; 16]>::try_from(&response[8..24]).unwrap());
assert_eq!(resp_src, our_addr);
let resp_dst = Ipv6Addr::from(<[u8; 16]>::try_from(&response[24..40]).unwrap());
assert_eq!(resp_dst, src);
assert_eq!(response[IPV6_HEADER_LEN], 1); assert_eq!(response[IPV6_HEADER_LEN + 1], 0); }
#[test]
fn test_build_dest_unreachable_invalid_input() {
let short_packet = vec![0u8; 20];
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response =
build_dest_unreachable(&short_packet, DestUnreachableCode::NoRoute, our_addr);
assert!(response.is_none());
}
#[test]
fn test_build_dest_unreachable_truncates_large_packet() {
let src: Ipv6Addr = "fd00::1".parse().unwrap();
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let original = make_ipv6_packet(src, dst, 17, &[0u8; 2000]);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_dest_unreachable(&original, DestUnreachableCode::NoRoute, our_addr);
assert!(response.is_some());
let response = response.unwrap();
assert!(response.len() <= MIN_IPV6_MTU);
}
#[test]
fn test_build_packet_too_big() {
let src: Ipv6Addr = "fd00::1".parse().unwrap();
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let original = make_ipv6_packet(src, dst, 17, &[0u8; 1200]);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let mtu = 1070u32;
let response = build_packet_too_big(&original, mtu, our_addr);
assert!(response.is_some());
let response = response.unwrap();
assert_eq!(response[0] >> 4, 6); assert_eq!(response[6], IPPROTO_ICMPV6);
let resp_src = Ipv6Addr::from(<[u8; 16]>::try_from(&response[8..24]).unwrap());
assert_eq!(resp_src, our_addr);
let resp_dst = Ipv6Addr::from(<[u8; 16]>::try_from(&response[24..40]).unwrap());
assert_eq!(resp_dst, src);
assert_eq!(response[IPV6_HEADER_LEN], 2); assert_eq!(response[IPV6_HEADER_LEN + 1], 0);
let reported_mtu = u32::from_be_bytes([
response[IPV6_HEADER_LEN + 4],
response[IPV6_HEADER_LEN + 5],
response[IPV6_HEADER_LEN + 6],
response[IPV6_HEADER_LEN + 7],
]);
assert_eq!(reported_mtu, mtu);
assert!(response.len() <= MIN_IPV6_MTU);
}
#[test]
fn test_build_packet_too_big_invalid_input() {
let short_packet = vec![0u8; 20];
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_packet_too_big(&short_packet, 1280, our_addr);
assert!(response.is_none());
}
#[test]
fn test_build_packet_too_big_multicast_source() {
let src: Ipv6Addr = "ff02::1".parse().unwrap(); let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let original = make_ipv6_packet(src, dst, 17, &[0u8; 1200]);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_packet_too_big(&original, 1280, our_addr);
assert!(response.is_none());
}
#[test]
fn test_build_packet_too_big_unspecified_source() {
let src = Ipv6Addr::UNSPECIFIED;
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let original = make_ipv6_packet(src, dst, 17, &[0u8; 1200]);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_packet_too_big(&original, 1280, our_addr);
assert!(response.is_none());
}
#[test]
fn test_build_packet_too_big_for_icmp_error() {
let src: Ipv6Addr = "fd00::1".parse().unwrap();
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let icmp_payload = [1u8, 0, 0, 0, 0, 0, 0, 0];
let original = make_ipv6_packet(src, dst, IPPROTO_ICMPV6, &icmp_payload);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_packet_too_big(&original, 1280, our_addr);
assert!(response.is_none());
}
#[test]
fn test_build_packet_too_big_for_icmp_echo() {
let src: Ipv6Addr = "fd00::1".parse().unwrap();
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let icmp_payload = [128u8, 0, 0, 0, 0, 0, 0, 0];
let original = make_ipv6_packet(src, dst, IPPROTO_ICMPV6, &icmp_payload);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_packet_too_big(&original, 1280, our_addr);
assert!(response.is_some());
}
#[test]
fn test_build_packet_too_big_truncates_large_packet() {
let src: Ipv6Addr = "fd00::1".parse().unwrap();
let dst: Ipv6Addr = "fd00::2".parse().unwrap();
let original = make_ipv6_packet(src, dst, 17, &[0u8; 2000]);
let our_addr: Ipv6Addr = "fd00::ffff".parse().unwrap();
let response = build_packet_too_big(&original, 1070, our_addr);
assert!(response.is_some());
let response = response.unwrap();
assert!(response.len() <= MIN_IPV6_MTU);
}
#[test]
fn test_build_packet_too_big_remote_source_for_pmtud() {
let local_addr: Ipv6Addr = "fd41::1".parse().unwrap();
let remote_addr: Ipv6Addr = "fddf::2".parse().unwrap();
let original = make_ipv6_packet(local_addr, remote_addr, 6, &[0u8; 1200]);
let response = build_packet_too_big(&original, 1203, remote_addr);
assert!(response.is_some());
let response = response.unwrap();
let ptb_src = Ipv6Addr::from(<[u8; 16]>::try_from(&response[8..24]).unwrap());
assert_eq!(
ptb_src, remote_addr,
"PTB source must be remote peer address"
);
let ptb_dst = Ipv6Addr::from(<[u8; 16]>::try_from(&response[24..40]).unwrap());
assert_eq!(
ptb_dst, local_addr,
"PTB destination must be original sender"
);
assert_eq!(response[IPV6_HEADER_LEN], 2); assert_eq!(response[IPV6_HEADER_LEN + 1], 0);
let reported_mtu = u32::from_be_bytes([
response[IPV6_HEADER_LEN + 4],
response[IPV6_HEADER_LEN + 5],
response[IPV6_HEADER_LEN + 6],
response[IPV6_HEADER_LEN + 7],
]);
assert_eq!(reported_mtu, 1203);
let stored_checksum =
u16::from_be_bytes([response[IPV6_HEADER_LEN + 2], response[IPV6_HEADER_LEN + 3]]);
let recomputed = icmpv6_checksum(&response[IPV6_HEADER_LEN..], &remote_addr, &local_addr);
assert_eq!(stored_checksum, recomputed, "ICMPv6 checksum must be valid");
}
}