1use crate::pktparser::Buffer;
2#[cfg(fuzzing)]
3use arbitrary::Arbitrary;
4use std::time::Duration;
5
6#[derive(Debug)]
7pub enum Error {
8 Truncated,
9 InvalidEncoding,
10 InvalidPacket,
11}
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
14#[cfg_attr(fuzzing, derive(Arbitrary))]
15struct Type(u8);
16const ICMP6_DESTINATION_UNREACHABLE: Type = Type(1);
17const ND_ROUTER_SOLICIT: Type = Type(133);
18const ND_ROUTER_ADVERT: Type = Type(134);
19const ND_NEIGHBOR_SOLICIT: Type = Type(135);
20const ND_NEIGHBOR_ADVERT: Type = Type(136);
21const ICMP6_REDIRECT: Type = Type(137);
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
24#[cfg_attr(fuzzing, derive(Arbitrary))]
25pub struct NDOption(u8);
26pub const SOURCE_LL_ADDR: NDOption = NDOption(1);
27pub const _TARGET_LL_ADDR: NDOption = NDOption(2);
28pub const PREFIX_INFO: NDOption = NDOption(3);
29pub const _REDIRECTED: NDOption = NDOption(4);
30pub const MTU: NDOption = NDOption(5);
31pub const _ROUTE_INFO: NDOption = NDOption(24);
32pub const RDNSS: NDOption = NDOption(25);
33pub const DNSSL: NDOption = NDOption(31);
34pub const CAPTIVE_PORTAL: NDOption = NDOption(37);
35pub const PREF64: NDOption = NDOption(38);
36pub const IPV6_ONLY_PREFERRED: NDOption = NDOption(108);
37
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub enum NDOptionValue {
40 SourceLLAddr(Vec<u8>),
41 Mtu(u32),
42 Prefix(AdvPrefix),
43 RecursiveDnsServers((std::time::Duration, Vec<std::net::Ipv6Addr>)),
44 DnsSearchList((std::time::Duration, Vec<String>)), CaptivePortal(String),
46 Pref64((std::time::Duration, u8, std::net::Ipv6Addr)),
47}
48
49#[cfg(fuzzing)]
50impl<'a> Arbitrary<'a> for NDOptionValue {
51 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
52 use std::convert::TryFrom as _;
53 match u.int_in_range(0..=5)? {
54 0 => Ok(NDOptionValue::SourceLLAddr(<[u8; 6]>::arbitrary(u)?.into())),
55 1 => Ok(NDOptionValue::Mtu(<_>::arbitrary(u)?)),
56 2 => Ok(NDOptionValue::Prefix(<_>::arbitrary(u)?)),
57 3 => {
58 let duration = Duration::from_secs(u.int_in_range(0..=2_u64.pow(32) - 1)?);
59 let num_ips = u.arbitrary_len::<[u8; 16]>()?;
60 let mut ips = Vec::new();
61 for _ in 0..std::cmp::min(num_ips, 127) {
62 ips.push(<[u8; 16]>::arbitrary(u)?.into());
63 }
64
65 Ok(NDOptionValue::RecursiveDnsServers((duration, ips)))
66 }
67 4 => loop {
68 let mut url: String = <_>::arbitrary(u)?;
69 while url.ends_with('\0') {
70 url.pop();
71 }
72 return Ok(NDOptionValue::CaptivePortal(url));
73 },
74 5 => Ok(NDOptionValue::Pref64((
75 Duration::from_secs(u.int_in_range(0..=2_u64.pow(13) - 1)? & !7),
76 u.int_in_range(0..=5)? * 8 + 32,
77 <[u8; 128 / 8]>::try_from(
78 [&<[u8; 96 / 8]>::arbitrary(u)?, &[0_u8; (128 - 96) / 8][..]].concat(),
79 )
80 .unwrap()
81 .into(),
82 ))),
83 _ => unimplemented!(),
90 }
91 }
92}
93
94#[derive(Default, Debug, Eq, PartialEq)]
95#[cfg_attr(fuzzing, derive(Arbitrary))]
96pub struct NDOptions(Vec<NDOptionValue>);
97
98impl NDOptions {
99 pub fn add_option(&mut self, ov: NDOptionValue) {
100 self.0.push(ov);
101 }
102
103 #[cfg(test)]
104 pub fn find_option(&self, o: NDOption) -> Vec<NDOptionValue> {
105 self.0
106 .iter()
107 .filter(|x| match (&o, &x) {
108 (&RDNSS, NDOptionValue::RecursiveDnsServers(_)) => true,
109 (&RDNSS, _) => false,
110 (&DNSSL, NDOptionValue::DnsSearchList(_)) => true,
111 (&DNSSL, _) => false,
112 (&CAPTIVE_PORTAL, NDOptionValue::CaptivePortal(_)) => true,
113 (&CAPTIVE_PORTAL, _) => false,
114 (_, _) => unimplemented!(),
115 })
116 .cloned()
117 .collect()
118 }
119}
120
121#[derive(Debug, Eq, PartialEq)]
122#[cfg_attr(fuzzing, derive(Arbitrary))]
123pub enum Icmp6 {
124 Unknown,
125 RtrSolicit(NDOptions),
126 RtrAdvert(RtrAdvertisement),
127}
128
129#[derive(Debug, Eq, PartialEq)]
130pub struct RtrAdvertisement {
131 pub hop_limit: u8,
132 pub flag_managed: bool,
133 pub flag_other: bool,
134 pub lifetime: std::time::Duration,
135 pub reachable: std::time::Duration,
136 pub retrans: std::time::Duration,
137 pub options: NDOptions,
138}
139
140#[cfg(fuzzing)]
141impl<'a> Arbitrary<'a> for RtrAdvertisement {
142 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
143 Ok(Self {
144 hop_limit: <_>::arbitrary(u)?,
145 flag_managed: <_>::arbitrary(u)?,
146 flag_other: <_>::arbitrary(u)?,
147 lifetime: Duration::from_secs(<u16>::arbitrary(u)?.into()),
148 reachable: Duration::from_millis(<u32>::arbitrary(u)?.into()),
149 retrans: Duration::from_millis(<u32>::arbitrary(u)?.into()),
150 options: <_>::arbitrary(u)?,
151 })
152 }
153}
154
155#[derive(Clone, Debug, Eq, PartialEq)]
156pub struct AdvPrefix {
157 pub prefixlen: u8,
158 pub onlink: bool,
159 pub autonomous: bool,
160 pub valid: std::time::Duration,
161 pub preferred: std::time::Duration,
162 pub prefix: std::net::Ipv6Addr,
163}
164
165#[cfg(fuzzing)]
166impl<'a> Arbitrary<'a> for AdvPrefix {
167 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
168 Ok(AdvPrefix {
169 prefixlen: u.int_in_range(0..=128)?,
170 onlink: <_>::arbitrary(u)?,
171 autonomous: <_>::arbitrary(u)?,
172 valid: Duration::from_secs(<u32>::arbitrary(u)?.into()),
173 preferred: Duration::from_secs(<u32>::arbitrary(u)?.into()),
174 prefix: <[u8; 16]>::arbitrary(u)?.into(),
175 })
176 }
177}
178
179fn parse_nd_rtr_options(buf: &mut Buffer) -> Result<NDOptions, Error> {
180 let mut options: NDOptions = Default::default();
181 while buf.remaining() > 0 {
182 let ty = buf.get_u8().map(NDOption).ok_or(Error::Truncated)?;
183 let l = buf.get_u8().ok_or(Error::Truncated)? as usize;
184 if l == 0 {
185 return Err(Error::Truncated);
187 }
188 let data = buf.get_bytes(l * 8 - 2).ok_or(Error::Truncated)?;
189 match (ty, data) {
190 (SOURCE_LL_ADDR, value) => {
191 options.add_option(NDOptionValue::SourceLLAddr(value.to_vec()));
192 }
193 (CAPTIVE_PORTAL, value) => {
194 options.add_option(NDOptionValue::CaptivePortal(
195 String::from_utf8(
196 value[..value
197 .iter()
198 .rposition(|b| *b != 0)
199 .map(|p| p + 1)
200 .unwrap_or(0)]
201 .into(),
202 )
203 .map_err(|_| Error::InvalidEncoding)?,
204 ));
205 }
206 (PREF64, value) => {
207 if value.len() != 2 * 8 - 2 {
208 return Err(Error::InvalidPacket);
211 }
212 use std::convert::{TryFrom as _, TryInto as _};
213 let scaled_lifetime_plc = u16::from_be_bytes(value[0..=1].try_into().unwrap());
214 let lifetime = Duration::from_secs((scaled_lifetime_plc & !7).into());
215 let prefixlen = (scaled_lifetime_plc & 0x07) * 8 + 32;
216 let ip_octets =
217 <[u8; 16]>::try_from([&value[2..], &[0, 0, 0, 0]].concat()).unwrap();
218 let prefix = std::net::Ipv6Addr::from(ip_octets);
219 options.add_option(NDOptionValue::Pref64((lifetime, prefixlen as u8, prefix)));
220 }
221 (MTU, value) => {
222 if value.len() != 8 - 2 {
223 return Err(Error::InvalidPacket);
224 }
225 use std::convert::TryInto as _;
226 options.add_option(NDOptionValue::Mtu(u32::from_be_bytes(
227 value[2..=5].try_into().unwrap(),
228 )));
229 }
230 (RDNSS, value) => {
231 use std::convert::{TryFrom as _, TryInto as _};
232 let lifetime =
233 Duration::from_secs(u32::from_be_bytes(value[2..=5].try_into().unwrap()) as _);
234 let servers = value[6..]
235 .chunks_exact(16)
236 .map(|x| std::net::Ipv6Addr::from(<[u8; 16]>::try_from(x).unwrap()))
237 .collect();
238 options.add_option(NDOptionValue::RecursiveDnsServers((lifetime, servers)));
239 }
240 (PREFIX_INFO, value) => {
241 use std::convert::{TryFrom as _, TryInto as _};
242 if value.len() != 4 * 8 - 2 {
243 return Err(Error::InvalidPacket);
244 }
245 let prefixlen = value[0];
246 let onlink = value[1] & 0x80 != 0;
247 let autonomous = value[1] & 0x40 != 0;
248 let valid =
249 Duration::from_secs(u32::from_be_bytes(value[2..6].try_into().unwrap()) as _);
250 let preferred =
251 Duration::from_secs(u32::from_be_bytes(value[6..10].try_into().unwrap()) as _);
252 let prefix =
254 std::net::Ipv6Addr::from(<[u8; 16]>::try_from(&value[14..30]).unwrap());
255 options.add_option(NDOptionValue::Prefix(AdvPrefix {
256 prefixlen,
257 onlink,
258 autonomous,
259 valid,
260 preferred,
261 prefix,
262 }));
263 }
264 o => log::trace!("Unexpected ND RTR Solicit option {:?}", o),
265 }
266 }
267 Ok(options)
268}
269
270fn parse_nd_rtr_solicit(buf: &mut Buffer) -> Result<Icmp6, Error> {
271 let _reserved = buf.get_be32().ok_or(Error::Truncated)?;
272 Ok(Icmp6::RtrSolicit(parse_nd_rtr_options(buf)?))
273}
274
275fn parse_nd_rtr_advert(buf: &mut Buffer) -> Result<Icmp6, Error> {
276 let hop_limit = buf.get_u8().ok_or(Error::Truncated)?;
277 let mo_byte = buf.get_u8().ok_or(Error::Truncated)?;
278 let lifetime = Duration::from_secs(buf.get_be16().ok_or(Error::Truncated)?.into());
279 let reachable = Duration::from_millis(buf.get_be32().ok_or(Error::Truncated)?.into());
280 let retrans = Duration::from_millis(buf.get_be32().ok_or(Error::Truncated)?.into());
281 let options = parse_nd_rtr_options(buf)?;
282 let flag_managed = (0b1000_0000 & mo_byte) != 0;
283 let flag_other = (0b0100_0000 & mo_byte) != 0;
284
285 Ok(Icmp6::RtrAdvert(RtrAdvertisement {
286 hop_limit,
287 flag_managed,
288 flag_other,
289 lifetime,
290 reachable,
291 retrans,
292 options,
293 }))
294}
295
296pub fn parse(pkt: &[u8]) -> Result<Icmp6, Error> {
297 if pkt.len() < 8 {
299 return Err(Error::Truncated);
300 }
301 let mut buf = Buffer::new(pkt);
302 let ty = Type(buf.get_u8().ok_or(Error::Truncated)?);
303 let code = buf.get_u8().ok_or(Error::Truncated)?;
304 let _chksum = buf.get_be16().ok_or(Error::Truncated)?;
305
306 match (ty, code) {
307 (ICMP6_DESTINATION_UNREACHABLE, _) => Ok(Icmp6::Unknown), (ND_ROUTER_SOLICIT, 0) => parse_nd_rtr_solicit(&mut buf),
309 (ND_ROUTER_ADVERT, 0) => parse_nd_rtr_advert(&mut buf),
310 (ND_NEIGHBOR_SOLICIT, 0) => Ok(Icmp6::Unknown),
311 (ND_NEIGHBOR_ADVERT, 0) => Ok(Icmp6::Unknown),
312 (ICMP6_REDIRECT, _) => Ok(Icmp6::Unknown), (t, c) => {
314 log::trace!("Unexpected ICMP6 message: {:?}/{}", t, c);
315 Ok(Icmp6::Unknown) }
317 }
318}
319
320#[derive(Default)]
321struct Serialise {
322 v: Vec<u8>,
323}
324
325impl Serialise {
326 fn serialise<T: SerialiseInto>(&mut self, value: T) {
327 value.serialise(&mut self.v)
328 }
329
330 fn len(&self) -> usize {
331 self.v.len()
332 }
333}
334
335trait SerialiseInto {
336 fn serialise(&self, v: &mut Vec<u8>);
337}
338
339impl SerialiseInto for u8 {
340 fn serialise(&self, v: &mut Vec<u8>) {
341 v.extend(self.to_be_bytes().iter())
342 }
343}
344
345impl SerialiseInto for u16 {
346 fn serialise(&self, v: &mut Vec<u8>) {
347 v.extend(self.to_be_bytes().iter())
348 }
349}
350
351impl SerialiseInto for u32 {
352 fn serialise(&self, v: &mut Vec<u8>) {
353 v.extend(self.to_be_bytes().iter())
354 }
355}
356
357impl SerialiseInto for &Vec<u8> {
358 fn serialise(&self, v: &mut Vec<u8>) {
359 v.extend(self.iter())
360 }
361}
362
363impl SerialiseInto for &std::net::Ipv6Addr {
364 fn serialise(&self, v: &mut Vec<u8>) {
365 v.extend(self.octets().iter())
366 }
367}
368
369impl SerialiseInto for &str {
370 fn serialise(&self, v: &mut Vec<u8>) {
371 v.extend(self.as_bytes())
372 }
373}
374
375fn serialise_router_advertisement(a: &RtrAdvertisement) -> Vec<u8> {
376 let mut v: Serialise = Default::default();
377 v.serialise(ND_ROUTER_ADVERT.0);
378 v.serialise(0_u8); v.serialise(0_u16); v.serialise(a.hop_limit);
381 v.serialise(
382 if a.flag_managed { 0x80_u8 } else { 0x00_u8 }
383 | if a.flag_other { 0x40_u8 } else { 0x00_u8 },
384 );
385 v.serialise(a.lifetime.as_secs() as u16);
386 v.serialise(a.reachable.as_millis() as u32);
387 v.serialise(a.retrans.as_millis() as u32);
388 for opt in &a.options.0 {
389 match opt {
390 NDOptionValue::SourceLLAddr(src) => {
391 use std::convert::TryFrom as _;
392 v.serialise(SOURCE_LL_ADDR.0);
393 v.serialise(u8::try_from(src.len().div_ceil(8)).unwrap());
394 v.serialise(src);
395 }
396 NDOptionValue::Mtu(mtu) => {
397 v.serialise(MTU.0);
398 v.serialise(1_u8);
399 v.serialise(0_u16);
400 v.serialise(*mtu);
401 }
402 NDOptionValue::Prefix(prefix) => {
403 v.serialise(PREFIX_INFO.0);
404 v.serialise(4_u8);
405 v.serialise(prefix.prefixlen);
406 v.serialise(
407 if prefix.onlink { 0x80_u8 } else { 0x00_u8 }
408 | if prefix.autonomous { 0x40_u8 } else { 0x00_u8 },
409 );
410 v.serialise(prefix.valid.as_secs() as u32);
411 v.serialise(prefix.preferred.as_secs() as u32);
412 v.serialise(0_u32);
413 v.serialise(&prefix.prefix);
414 }
415 NDOptionValue::RecursiveDnsServers((lifetime, servers)) => {
416 use std::convert::TryFrom as _;
417 v.serialise(RDNSS.0);
418 v.serialise(u8::try_from(1 + servers.len() * 2).unwrap());
419 v.serialise(0_u16); v.serialise(lifetime.as_secs() as u32);
421 for server in servers {
422 v.serialise(server);
423 }
424 }
425 NDOptionValue::DnsSearchList((lifetime, suffixes)) => {
426 let mut dnssl = Serialise::default();
427 for suffix in suffixes {
428 for label in suffix.split('.') {
429 dnssl.serialise(label.len() as u8);
430 dnssl.serialise(label);
431 }
432 dnssl.serialise(0_u8);
433 }
434 while dnssl.v.len() % 8 != 0 {
436 dnssl.serialise(0_u8);
437 }
438 v.serialise(DNSSL.0);
439 v.serialise(1 + (dnssl.v.len() / 8) as u8);
440 v.serialise(0_u16); v.serialise(lifetime.as_secs() as u32);
442 v.serialise(&dnssl.v);
443 }
444 NDOptionValue::Pref64((lifetime, prefixlen, prefix)) => {
445 v.serialise(PREF64.0);
446 v.serialise(2_u8);
447 let scaled_lifetime = (lifetime.as_secs() / 8) as u16;
448 let plc = ((prefixlen - 32) / 8) as u16;
449 v.serialise((scaled_lifetime << 3) | plc);
450 for i in 0..12 {
451 v.serialise(prefix.octets()[i])
452 }
453 }
454 NDOptionValue::CaptivePortal(url) => {
455 let mut b = url.clone().into_bytes();
456 while (b.len() + 2) % 8 != 0 {
458 b.push(0x00_u8);
459 }
460 v.serialise(CAPTIVE_PORTAL.0);
461 v.serialise((1 + b.len() / 8) as u8);
462 v.serialise(&b);
463 }
464 }
465 }
466 assert_eq!(v.len() % 8, 0);
467 v.v
468}
469
470pub fn serialise(msg: &Icmp6) -> Vec<u8> {
471 match msg {
472 Icmp6::RtrAdvert(a) => serialise_router_advertisement(a),
473 Icmp6::Unknown => unimplemented!(),
474 Icmp6::RtrSolicit(_) => unimplemented!(),
475 }
476}
477
478#[test]
479fn test_parse_nd_rtr_solicit() {
480 let data = [133, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 6];
481 parse(&data).expect("Failed to parse");
482}
483
484#[test]
485fn test_decode_ra() {
486 let data = vec![
487 0x86, 0x00, 0xc4, 0xfe, 0x40, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
488 0x00, 0x01, 0x01, 0xc2, 0x00, 0x54, 0xf5, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
489 0x05, 0xdc, 0x03, 0x04, 0x40, 0xc0, 0x00, 0x27, 0x8d, 0x00, 0x00, 0x09, 0x3a, 0x80, 0x00,
490 0x00, 0x00, 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
491 0x00, 0x00, 0x00, 0x00,
492 ];
493
494 let result = parse(&data);
495 assert!(result.is_ok());
496 let result_value = result.unwrap();
497 println!("{:?}", result_value);
498}
499
500#[test]
501fn test_reflexitivity() {
502 use std::time::Duration;
503 let data = serialise(&Icmp6::RtrAdvert(RtrAdvertisement {
504 hop_limit: 64,
505 flag_managed: false,
506 flag_other: false,
507 lifetime: Duration::from_secs(600),
508 reachable: Duration::from_secs(30),
509 retrans: Duration::from_secs(1),
510 options: NDOptions(vec![
511 NDOptionValue::Mtu(1480),
512 NDOptionValue::SourceLLAddr(vec![0, 1, 2, 3, 4, 5]),
513 NDOptionValue::Prefix(AdvPrefix {
514 prefixlen: 64,
515 onlink: true,
516 autonomous: true,
517 valid: Duration::from_secs(86400),
518 preferred: Duration::from_secs(3600),
519 prefix: "2001:db8::".parse().unwrap(),
520 }),
521 NDOptionValue::RecursiveDnsServers((
522 Duration::from_secs(600),
523 vec!["2001:db8::53".parse().unwrap()],
524 )),
525 NDOptionValue::DnsSearchList((
526 Duration::from_secs(600),
527 vec!["example.com".into(), "example.net".into()],
528 )),
529 NDOptionValue::CaptivePortal("http://example.com/".into()),
530 ]),
531 }));
532 parse(&data).unwrap();
533}