use std::net::Ipv6Addr;
pub const ETH_HEADER_LEN: usize = 14;
pub const IPV6_HEADER_LEN: usize = 40;
pub const ICMPV6_HEADER_LEN: usize = 4;
pub const ND_BODY_LEN: usize = 20;
pub const ETHERTYPE_IPV6: u16 = 0x86dd;
pub const IPPROTO_ICMPV6: u8 = 58;
pub const ICMPV6_NEIGHBOR_SOLICITATION: u8 = 135;
pub const ICMPV6_NEIGHBOR_ADVERTISEMENT: u8 = 136;
pub const NDP_OPT_SOURCE_LL_ADDR: u8 = 1;
pub const NDP_OPT_TARGET_LL_ADDR: u8 = 2;
#[derive(Debug, Clone)]
pub struct EthernetFrame {
pub dst_mac: [u8; 6],
pub src_mac: [u8; 6],
pub ethertype: u16,
pub payload_offset: usize,
}
impl EthernetFrame {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < ETH_HEADER_LEN {
return None;
}
let dst_mac: [u8; 6] = data[0..6].try_into().ok()?;
let src_mac: [u8; 6] = data[6..12].try_into().ok()?;
let ethertype = u16::from_be_bytes([data[12], data[13]]);
Some(Self {
dst_mac,
src_mac,
ethertype,
payload_offset: ETH_HEADER_LEN,
})
}
pub fn encode(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.dst_mac);
buf.extend_from_slice(&self.src_mac);
buf.extend_from_slice(&self.ethertype.to_be_bytes());
}
}
#[derive(Debug, Clone)]
pub struct Ipv6Header {
pub traffic_class: u8,
pub flow_label: u32,
pub payload_len: u16,
pub next_header: u8,
pub hop_limit: u8,
pub src_addr: Ipv6Addr,
pub dst_addr: Ipv6Addr,
}
impl Ipv6Header {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < IPV6_HEADER_LEN {
return None;
}
let version = (data[0] >> 4) & 0x0f;
if version != 6 {
return None;
}
let traffic_class = ((data[0] & 0x0f) << 4) | ((data[1] >> 4) & 0x0f);
let flow_label =
((data[1] as u32 & 0x0f) << 16) | ((data[2] as u32) << 8) | (data[3] as u32);
let payload_len = u16::from_be_bytes([data[4], data[5]]);
let next_header = data[6];
let hop_limit = data[7];
let src_bytes: [u8; 16] = data[8..24].try_into().ok()?;
let dst_bytes: [u8; 16] = data[24..40].try_into().ok()?;
Some(Self {
traffic_class,
flow_label,
payload_len,
next_header,
hop_limit,
src_addr: Ipv6Addr::from(src_bytes),
dst_addr: Ipv6Addr::from(dst_bytes),
})
}
pub fn encode(&self, buf: &mut Vec<u8>) {
buf.push(0x60 | ((self.traffic_class >> 4) & 0x0f));
buf.push(((self.traffic_class & 0x0f) << 4) | ((self.flow_label >> 16) as u8 & 0x0f));
buf.push((self.flow_label >> 8) as u8);
buf.push(self.flow_label as u8);
buf.extend_from_slice(&self.payload_len.to_be_bytes());
buf.push(self.next_header);
buf.push(self.hop_limit);
buf.extend_from_slice(&self.src_addr.octets());
buf.extend_from_slice(&self.dst_addr.octets());
}
}
#[derive(Debug, Clone)]
pub struct NeighborSolicitation {
pub target_addr: Ipv6Addr,
pub source_ll_addr: Option<[u8; 6]>,
}
impl NeighborSolicitation {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < ICMPV6_HEADER_LEN + ND_BODY_LEN {
return None;
}
let icmp_type = data[0];
if icmp_type != ICMPV6_NEIGHBOR_SOLICITATION {
return None;
}
let target_bytes: [u8; 16] = data[8..24].try_into().ok()?;
let target_addr = Ipv6Addr::from(target_bytes);
let mut source_ll_addr = None;
let mut offset = 24;
while offset + 2 <= data.len() {
let opt_type = data[offset];
let opt_len = data[offset + 1] as usize * 8;
if opt_len == 0 {
break; }
if offset + opt_len > data.len() {
break; }
if opt_type == NDP_OPT_SOURCE_LL_ADDR && opt_len >= 8 {
source_ll_addr = Some(data[offset + 2..offset + 8].try_into().ok()?);
}
offset += opt_len;
}
Some(Self {
target_addr,
source_ll_addr,
})
}
}
#[derive(Debug, Clone)]
pub struct NeighborAdvertisement {
pub target_addr: Ipv6Addr,
pub target_ll_addr: [u8; 6],
pub is_router: bool,
pub is_solicited: bool,
pub is_override: bool,
}
impl NeighborAdvertisement {
pub fn new(target_addr: Ipv6Addr, target_ll_addr: [u8; 6]) -> Self {
Self {
target_addr,
target_ll_addr,
is_router: false,
is_solicited: true,
is_override: true,
}
}
pub fn router(mut self, is_router: bool) -> Self {
self.is_router = is_router;
self
}
pub fn solicited(mut self, is_solicited: bool) -> Self {
self.is_solicited = is_solicited;
self
}
pub fn override_flag(mut self, is_override: bool) -> Self {
self.is_override = is_override;
self
}
pub fn encode_icmpv6(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(32);
buf.push(ICMPV6_NEIGHBOR_ADVERTISEMENT);
buf.push(0);
buf.push(0);
buf.push(0);
let mut flags: u32 = 0;
if self.is_router {
flags |= 0x8000_0000;
}
if self.is_solicited {
flags |= 0x4000_0000;
}
if self.is_override {
flags |= 0x2000_0000;
}
buf.extend_from_slice(&flags.to_be_bytes());
buf.extend_from_slice(&self.target_addr.octets());
buf.push(NDP_OPT_TARGET_LL_ADDR); buf.push(1); buf.extend_from_slice(&self.target_ll_addr);
buf
}
}
pub fn icmpv6_checksum(src: &Ipv6Addr, dst: &Ipv6Addr, icmpv6_data: &[u8]) -> 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;
}
sum += icmpv6_data.len() as u32;
sum += IPPROTO_ICMPV6 as u32;
let mut i = 0;
while i + 1 < icmpv6_data.len() {
sum += u16::from_be_bytes([icmpv6_data[i], icmpv6_data[i + 1]]) as u32;
i += 2;
}
if i < icmpv6_data.len() {
sum += (icmpv6_data[i] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!sum as u16
}
#[allow(clippy::missing_panics_doc)]
pub fn build_na_reply(
ns_eth: &EthernetFrame,
ns_ipv6: &Ipv6Header,
ns: &NeighborSolicitation,
our_mac: [u8; 6],
our_ipv6: Ipv6Addr,
) -> Vec<u8> {
let dst_mac = ns.source_ll_addr.unwrap_or(ns_eth.src_mac);
let dst_ipv6 = if ns_ipv6.src_addr.is_unspecified() {
"ff02::1".parse().unwrap()
} else {
ns_ipv6.src_addr
};
let na = NeighborAdvertisement::new(our_ipv6, our_mac)
.solicited(!ns_ipv6.src_addr.is_unspecified());
let mut icmpv6_data = na.encode_icmpv6();
let checksum = icmpv6_checksum(&our_ipv6, &dst_ipv6, &icmpv6_data);
icmpv6_data[2] = (checksum >> 8) as u8;
icmpv6_data[3] = checksum as u8;
let ipv6 = Ipv6Header {
traffic_class: 0,
flow_label: 0,
payload_len: icmpv6_data.len() as u16,
next_header: IPPROTO_ICMPV6,
hop_limit: 255,
src_addr: our_ipv6,
dst_addr: dst_ipv6,
};
let eth = EthernetFrame {
dst_mac,
src_mac: our_mac,
ethertype: ETHERTYPE_IPV6,
payload_offset: 0,
};
let mut packet = Vec::with_capacity(ETH_HEADER_LEN + IPV6_HEADER_LEN + icmpv6_data.len());
eth.encode(&mut packet);
ipv6.encode(&mut packet);
packet.extend_from_slice(&icmpv6_data);
packet
}
pub fn parse_neighbor_solicitation(
data: &[u8],
) -> Option<(EthernetFrame, Ipv6Header, NeighborSolicitation)> {
let eth = EthernetFrame::parse(data)?;
if eth.ethertype != ETHERTYPE_IPV6 {
return None;
}
let ipv6 = Ipv6Header::parse(&data[eth.payload_offset..])?;
if ipv6.next_header != IPPROTO_ICMPV6 {
return None;
}
let icmpv6_offset = eth.payload_offset + IPV6_HEADER_LEN;
let ns = NeighborSolicitation::parse(&data[icmpv6_offset..])?;
Some((eth, ipv6, ns))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_ethernet_frame() {
let data = [
0x33, 0x33, 0xff, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x86, 0xdd, 0x00, ];
let eth = EthernetFrame::parse(&data).unwrap();
assert_eq!(eth.ethertype, ETHERTYPE_IPV6);
assert_eq!(eth.src_mac, [0x02, 0x00, 0x00, 0x00, 0x01, 0x00]);
}
#[test]
fn icmpv6_checksum_calculation() {
let src: Ipv6Addr = "fe80::1".parse().unwrap();
let dst: Ipv6Addr = "fe80::2".parse().unwrap();
let data = [136, 0, 0, 0, 0x60, 0, 0, 0];
let cksum = icmpv6_checksum(&src, &dst, &data);
assert_ne!(cksum, 0);
}
#[test]
fn build_neighbor_advertisement() {
let na = NeighborAdvertisement::new(
"fd00::100".parse().unwrap(),
[0x02, 0x00, 0x00, 0x00, 0x99, 0x00],
);
let icmpv6 = na.encode_icmpv6();
assert_eq!(icmpv6[0], ICMPV6_NEIGHBOR_ADVERTISEMENT);
assert_eq!(icmpv6.len(), 32); }
}