1use std::net::Ipv6Addr;
8
9pub const ETH_HEADER_LEN: usize = 14;
11pub const IPV6_HEADER_LEN: usize = 40;
13pub const ICMPV6_HEADER_LEN: usize = 4;
15pub const ND_BODY_LEN: usize = 20;
17
18pub const ETHERTYPE_IPV6: u16 = 0x86dd;
20pub const IPPROTO_ICMPV6: u8 = 58;
22pub const ICMPV6_NEIGHBOR_SOLICITATION: u8 = 135;
24pub const ICMPV6_NEIGHBOR_ADVERTISEMENT: u8 = 136;
26pub const NDP_OPT_SOURCE_LL_ADDR: u8 = 1;
28pub const NDP_OPT_TARGET_LL_ADDR: u8 = 2;
30
31#[derive(Debug, Clone)]
33pub struct EthernetFrame {
34 pub dst_mac: [u8; 6],
36 pub src_mac: [u8; 6],
38 pub ethertype: u16,
40 pub payload_offset: usize,
42}
43
44impl EthernetFrame {
45 pub fn parse(data: &[u8]) -> Option<Self> {
47 if data.len() < ETH_HEADER_LEN {
48 return None;
49 }
50
51 let dst_mac: [u8; 6] = data[0..6].try_into().ok()?;
52 let src_mac: [u8; 6] = data[6..12].try_into().ok()?;
53 let ethertype = u16::from_be_bytes([data[12], data[13]]);
54
55 Some(Self {
56 dst_mac,
57 src_mac,
58 ethertype,
59 payload_offset: ETH_HEADER_LEN,
60 })
61 }
62
63 pub fn encode(&self, buf: &mut Vec<u8>) {
65 buf.extend_from_slice(&self.dst_mac);
66 buf.extend_from_slice(&self.src_mac);
67 buf.extend_from_slice(&self.ethertype.to_be_bytes());
68 }
69}
70
71#[derive(Debug, Clone)]
73pub struct Ipv6Header {
74 pub traffic_class: u8,
76 pub flow_label: u32,
78 pub payload_len: u16,
80 pub next_header: u8,
82 pub hop_limit: u8,
84 pub src_addr: Ipv6Addr,
86 pub dst_addr: Ipv6Addr,
88}
89
90impl Ipv6Header {
91 pub fn parse(data: &[u8]) -> Option<Self> {
93 if data.len() < IPV6_HEADER_LEN {
94 return None;
95 }
96
97 let version = (data[0] >> 4) & 0x0f;
99 if version != 6 {
100 return None;
101 }
102
103 let traffic_class = ((data[0] & 0x0f) << 4) | ((data[1] >> 4) & 0x0f);
104 let flow_label =
105 ((data[1] as u32 & 0x0f) << 16) | ((data[2] as u32) << 8) | (data[3] as u32);
106 let payload_len = u16::from_be_bytes([data[4], data[5]]);
107 let next_header = data[6];
108 let hop_limit = data[7];
109
110 let src_bytes: [u8; 16] = data[8..24].try_into().ok()?;
111 let dst_bytes: [u8; 16] = data[24..40].try_into().ok()?;
112
113 Some(Self {
114 traffic_class,
115 flow_label,
116 payload_len,
117 next_header,
118 hop_limit,
119 src_addr: Ipv6Addr::from(src_bytes),
120 dst_addr: Ipv6Addr::from(dst_bytes),
121 })
122 }
123
124 pub fn encode(&self, buf: &mut Vec<u8>) {
126 buf.push(0x60 | ((self.traffic_class >> 4) & 0x0f));
128 buf.push(((self.traffic_class & 0x0f) << 4) | ((self.flow_label >> 16) as u8 & 0x0f));
130 buf.push((self.flow_label >> 8) as u8);
132 buf.push(self.flow_label as u8);
134 buf.extend_from_slice(&self.payload_len.to_be_bytes());
136 buf.push(self.next_header);
138 buf.push(self.hop_limit);
140 buf.extend_from_slice(&self.src_addr.octets());
142 buf.extend_from_slice(&self.dst_addr.octets());
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct NeighborSolicitation {
150 pub target_addr: Ipv6Addr,
152 pub source_ll_addr: Option<[u8; 6]>,
154}
155
156impl NeighborSolicitation {
157 pub fn parse(data: &[u8]) -> Option<Self> {
160 if data.len() < ICMPV6_HEADER_LEN + ND_BODY_LEN {
162 return None;
163 }
164
165 let icmp_type = data[0];
166 if icmp_type != ICMPV6_NEIGHBOR_SOLICITATION {
167 return None;
168 }
169
170 let target_bytes: [u8; 16] = data[8..24].try_into().ok()?;
172 let target_addr = Ipv6Addr::from(target_bytes);
173
174 let mut source_ll_addr = None;
176 let mut offset = 24;
177
178 while offset + 2 <= data.len() {
179 let opt_type = data[offset];
180 let opt_len = data[offset + 1] as usize * 8; if opt_len == 0 {
183 break; }
185
186 if offset + opt_len > data.len() {
187 break; }
189
190 if opt_type == NDP_OPT_SOURCE_LL_ADDR && opt_len >= 8 {
191 source_ll_addr = Some(data[offset + 2..offset + 8].try_into().ok()?);
193 }
194
195 offset += opt_len;
196 }
197
198 Some(Self {
199 target_addr,
200 source_ll_addr,
201 })
202 }
203}
204
205#[derive(Debug, Clone)]
207pub struct NeighborAdvertisement {
208 pub target_addr: Ipv6Addr,
210 pub target_ll_addr: [u8; 6],
212 pub is_router: bool,
214 pub is_solicited: bool,
216 pub is_override: bool,
218}
219
220impl NeighborAdvertisement {
221 pub fn new(target_addr: Ipv6Addr, target_ll_addr: [u8; 6]) -> Self {
223 Self {
224 target_addr,
225 target_ll_addr,
226 is_router: false,
227 is_solicited: true,
228 is_override: true,
229 }
230 }
231
232 pub fn router(mut self, is_router: bool) -> Self {
234 self.is_router = is_router;
235 self
236 }
237
238 pub fn solicited(mut self, is_solicited: bool) -> Self {
240 self.is_solicited = is_solicited;
241 self
242 }
243
244 pub fn override_flag(mut self, is_override: bool) -> Self {
246 self.is_override = is_override;
247 self
248 }
249
250 pub fn encode_icmpv6(&self) -> Vec<u8> {
253 let mut buf = Vec::with_capacity(32);
254
255 buf.push(ICMPV6_NEIGHBOR_ADVERTISEMENT);
257 buf.push(0);
259 buf.push(0);
261 buf.push(0);
262
263 let mut flags: u32 = 0;
265 if self.is_router {
266 flags |= 0x8000_0000;
267 }
268 if self.is_solicited {
269 flags |= 0x4000_0000;
270 }
271 if self.is_override {
272 flags |= 0x2000_0000;
273 }
274 buf.extend_from_slice(&flags.to_be_bytes());
275
276 buf.extend_from_slice(&self.target_addr.octets());
278
279 buf.push(NDP_OPT_TARGET_LL_ADDR); buf.push(1); buf.extend_from_slice(&self.target_ll_addr);
283
284 buf
285 }
286}
287
288pub fn icmpv6_checksum(src: &Ipv6Addr, dst: &Ipv6Addr, icmpv6_data: &[u8]) -> u16 {
292 let mut sum: u32 = 0;
293
294 for chunk in src.octets().chunks(2) {
296 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
297 }
298 for chunk in dst.octets().chunks(2) {
299 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
300 }
301 sum += icmpv6_data.len() as u32;
303 sum += IPPROTO_ICMPV6 as u32;
305
306 let mut i = 0;
308 while i + 1 < icmpv6_data.len() {
309 sum += u16::from_be_bytes([icmpv6_data[i], icmpv6_data[i + 1]]) as u32;
310 i += 2;
311 }
312 if i < icmpv6_data.len() {
313 sum += (icmpv6_data[i] as u32) << 8;
314 }
315
316 while sum >> 16 != 0 {
318 sum = (sum & 0xffff) + (sum >> 16);
319 }
320
321 !sum as u16
322}
323
324#[allow(clippy::missing_panics_doc)]
329pub fn build_na_reply(
330 ns_eth: &EthernetFrame,
331 ns_ipv6: &Ipv6Header,
332 ns: &NeighborSolicitation,
333 our_mac: [u8; 6],
334 our_ipv6: Ipv6Addr,
335) -> Vec<u8> {
336 let dst_mac = ns.source_ll_addr.unwrap_or(ns_eth.src_mac);
338 let dst_ipv6 = if ns_ipv6.src_addr.is_unspecified() {
339 "ff02::1".parse().unwrap()
341 } else {
342 ns_ipv6.src_addr
343 };
344
345 let na = NeighborAdvertisement::new(our_ipv6, our_mac)
347 .solicited(!ns_ipv6.src_addr.is_unspecified());
348
349 let mut icmpv6_data = na.encode_icmpv6();
350
351 let checksum = icmpv6_checksum(&our_ipv6, &dst_ipv6, &icmpv6_data);
353 icmpv6_data[2] = (checksum >> 8) as u8;
354 icmpv6_data[3] = checksum as u8;
355
356 let ipv6 = Ipv6Header {
358 traffic_class: 0,
359 flow_label: 0,
360 payload_len: icmpv6_data.len() as u16,
361 next_header: IPPROTO_ICMPV6,
362 hop_limit: 255,
363 src_addr: our_ipv6,
364 dst_addr: dst_ipv6,
365 };
366
367 let eth = EthernetFrame {
369 dst_mac,
370 src_mac: our_mac,
371 ethertype: ETHERTYPE_IPV6,
372 payload_offset: 0,
373 };
374
375 let mut packet = Vec::with_capacity(ETH_HEADER_LEN + IPV6_HEADER_LEN + icmpv6_data.len());
377 eth.encode(&mut packet);
378 ipv6.encode(&mut packet);
379 packet.extend_from_slice(&icmpv6_data);
380
381 packet
382}
383
384pub fn parse_neighbor_solicitation(
388 data: &[u8],
389) -> Option<(EthernetFrame, Ipv6Header, NeighborSolicitation)> {
390 let eth = EthernetFrame::parse(data)?;
391 if eth.ethertype != ETHERTYPE_IPV6 {
392 return None;
393 }
394
395 let ipv6 = Ipv6Header::parse(&data[eth.payload_offset..])?;
396 if ipv6.next_header != IPPROTO_ICMPV6 {
397 return None;
398 }
399
400 let icmpv6_offset = eth.payload_offset + IPV6_HEADER_LEN;
401 let ns = NeighborSolicitation::parse(&data[icmpv6_offset..])?;
402
403 Some((eth, ipv6, ns))
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn parse_ethernet_frame() {
412 let data = [
413 0x33, 0x33, 0xff, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x86, 0xdd, 0x00, ];
418
419 let eth = EthernetFrame::parse(&data).unwrap();
420 assert_eq!(eth.ethertype, ETHERTYPE_IPV6);
421 assert_eq!(eth.src_mac, [0x02, 0x00, 0x00, 0x00, 0x01, 0x00]);
422 }
423
424 #[test]
425 fn icmpv6_checksum_calculation() {
426 let src: Ipv6Addr = "fe80::1".parse().unwrap();
428 let dst: Ipv6Addr = "fe80::2".parse().unwrap();
429 let data = [136, 0, 0, 0, 0x60, 0, 0, 0]; let cksum = icmpv6_checksum(&src, &dst, &data);
432 assert_ne!(cksum, 0);
434 }
435
436 #[test]
437 fn build_neighbor_advertisement() {
438 let na = NeighborAdvertisement::new(
439 "fd00::100".parse().unwrap(),
440 [0x02, 0x00, 0x00, 0x00, 0x99, 0x00],
441 );
442
443 let icmpv6 = na.encode_icmpv6();
444 assert_eq!(icmpv6[0], ICMPV6_NEIGHBOR_ADVERTISEMENT);
445 assert_eq!(icmpv6.len(), 32); }
447}