1use erbium_net::addr::{ALL_NODES, ALL_ROUTERS};
21use std::convert::TryInto as _;
22use erbium_net::nix;
26
27pub(crate) mod config;
28pub mod icmppkt;
29
30#[cfg(test)]
31mod test {
32 mod rfc4861;
33}
34
35const DEFAULT_MAX_RTR_ADV_INTERVAL: std::time::Duration = std::time::Duration::from_secs(600);
37const DEFAULT_MIN_RTR_ADV_INTERVAL: std::time::Duration =
38 std::time::Duration::from_micros((DEFAULT_MAX_RTR_ADV_INTERVAL.as_micros() / 3) as u64);
39const ADV_DEFAULT_LIFETIME: std::time::Duration =
40 std::time::Duration::from_secs(3 * DEFAULT_MAX_RTR_ADV_INTERVAL.as_secs());
41
42lazy_static::lazy_static! {
43 static ref RADV_RX_PACKETS: prometheus::IntCounterVec =
44 prometheus::register_int_counter_vec!("radv_received_packets", "Number of packets received", &["interface"])
45 .unwrap();
46 static ref RADV_SOLICITATIONS: prometheus::IntCounterVec =
47 prometheus::register_int_counter_vec!("radv_solicitations",
48 "Number of router solicitations received",
49 &["interface"])
50 .unwrap();
51 static ref RADV_TX_PACKETS: prometheus::IntCounterVec =
52 prometheus::register_int_counter_vec!("radv_sent_packets", "Number of packets sent", &["interface"])
53 .unwrap();
54}
55
56pub enum Error {
57 Io(std::io::Error),
58 Message(String),
59 UnconfiguredInterface(String),
60}
61
62impl std::fmt::Display for Error {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Error::Io(e) => write!(f, "I/O Error: {:?}", e),
66 Error::Message(e) => write!(f, "{}", e),
67 Error::UnconfiguredInterface(int) => write!(
68 f,
69 "No router advertisement configuration for interface {}, ignoring.",
70 int
71 ),
72 }
73 }
74}
75
76enum Void {}
78
79impl std::fmt::Debug for Void {
80 fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result {
81 unreachable!()
82 }
83}
84
85pub struct RaAdvService {
86 netinfo: erbium_net::netinfo::SharedNetInfo,
87 conf: crate::config::SharedConfig,
88 rawsock: std::sync::Arc<erbium_net::raw::Raw6Socket>,
89}
90
91#[derive(Eq, PartialEq)]
92struct ScopeSorter(std::net::Ipv6Addr);
93
94#[derive(Eq, PartialEq)]
95enum Scope {
96 Link,
97 Loopback,
98 UniqueLocalAddress,
99 Global,
100 Unspecified,
101 Multicast,
102}
103
104const fn v6_scope(ip6: std::net::Ipv6Addr) -> Scope {
105 use std::net::*;
106 if (ip6.segments()[0] & 0xfe00) == 0xfc00 {
107 Scope::UniqueLocalAddress
108 } else if u128::from_be_bytes(ip6.octets()) == u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets())
109 {
110 Scope::Loopback
111 } else if u128::from_be_bytes(ip6.octets())
112 == u128::from_be_bytes(Ipv6Addr::UNSPECIFIED.octets())
113 {
114 Scope::Unspecified
115 } else if ip6.segments()[0] == 0xfe80
116 && ip6.segments()[1] == 0
117 && ip6.segments()[2] == 0
118 && ip6.segments()[3] == 0
119 {
120 Scope::Link
122 } else if (ip6.segments()[0] & 0xff00) == 0xff00 {
123 Scope::Multicast
124 } else {
125 Scope::Global
126 }
127}
128
129impl Ord for ScopeSorter {
130 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
131 use Scope::*;
132 let scopes = [Scope::Unspecified, Link, Global, UniqueLocalAddress];
134 let sscope = v6_scope(self.0);
135 let oscope = v6_scope(other.0);
136 let sscopepos = scopes
137 .iter()
138 .position(|x| *x == sscope)
139 .unwrap_or(usize::MIN);
140 let oscopepos = scopes
141 .iter()
142 .position(|x| *x == oscope)
143 .unwrap_or(usize::MIN);
144 let ret = sscopepos.cmp(&oscopepos);
145 if ret == std::cmp::Ordering::Equal {
146 self.0.cmp(&other.0)
149 } else {
150 ret
151 }
152 }
153}
154
155impl PartialOrd for ScopeSorter {
156 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
157 Some(self.cmp(other))
158 }
159}
160
161impl RaAdvService {
162 pub fn new(
163 netinfo: erbium_net::netinfo::SharedNetInfo,
164 conf: super::config::SharedConfig,
165 ) -> Result<Self, Error> {
166 let rawsock = std::sync::Arc::new(
167 erbium_net::raw::Raw6Socket::new(erbium_net::raw::IpProto::ICMP6).map_err(Error::Io)?,
168 );
169
170 rawsock
171 .set_socket_option(erbium_net::Ipv6RecvPacketInfo, &true)
172 .map_err(Error::Io)?;
173 use std::os::unix::io::AsRawFd as _;
178 erbium_net::socket::set_ipv6_unicast_hoplimit(rawsock.as_raw_fd(), 255)
179 .map_err(|e| Error::Io(e.into()))?;
180 erbium_net::socket::set_ipv6_multicast_hoplimit(rawsock.as_raw_fd(), 255)
184 .map_err(|e| Error::Io(e.into()))?;
185 rawsock
192 .set_socket_option(
193 erbium_net::Ipv6AddMembership,
194 &nix::sys::socket::Ipv6MembershipRequest::new(ALL_ROUTERS),
195 )
196 .map_err(Error::Io)?;
197
198 Ok(Self {
199 netinfo,
200 conf,
201 rawsock,
202 })
203 }
204
205 fn build_announcement_pure(
206 config: &crate::config::Config,
207 intf: &config::Interface,
208 ll: Option<[u8; 6]>, mtu: Option<u32>,
210 self6: std::net::Ipv6Addr,
211 lifetime: std::time::Duration,
212 ) -> icmppkt::RtrAdvertisement {
213 let mut options = icmppkt::NDOptions::default();
214 if let Some(lladdr) = ll {
216 options.add_option(icmppkt::NDOptionValue::SourceLLAddr(lladdr.to_vec()));
217 }
218
219 if let Some(mtu) = mtu {
220 options.add_option(icmppkt::NDOptionValue::Mtu(mtu));
221 }
222
223 for prefix in &intf.prefixes {
224 options.add_option(icmppkt::NDOptionValue::Prefix(icmppkt::AdvPrefix {
225 prefixlen: prefix.prefixlen,
226 onlink: prefix.onlink,
227 autonomous: prefix.autonomous,
228 valid: prefix.valid,
229 preferred: prefix.preferred,
230 prefix: prefix.addr,
231 }));
232 }
233
234 if let Some(v) = &intf.rdnss.unwrap_or(
235 config
236 .dns_servers
237 .iter()
238 .filter_map(|ip| match ip {
239 std::net::IpAddr::V6(ip6) if *ip6 == std::net::Ipv6Addr::UNSPECIFIED => {
240 Some(self6)
241 }
242 std::net::IpAddr::V6(ip6) => Some(*ip6),
243 _ => None,
244 })
245 .collect(),
246 ) {
247 options.add_option(icmppkt::NDOptionValue::RecursiveDnsServers((
248 intf.rdnss_lifetime
249 .always_unwrap_or(3 * DEFAULT_MAX_RTR_ADV_INTERVAL),
250 v.clone(),
251 )))
252 }
253
254 if let Some(v) = &intf.dnssl.unwrap_or(config.dns_search.clone()) {
255 options.add_option(icmppkt::NDOptionValue::DnsSearchList((
256 intf.dnssl_lifetime
257 .always_unwrap_or(3 * DEFAULT_MAX_RTR_ADV_INTERVAL),
258 v.clone(),
259 )))
260 }
261
262 if let Some(pref64) = &intf.pref64 {
263 options.add_option(icmppkt::NDOptionValue::Pref64((
264 pref64.lifetime,
265 pref64.prefixlen,
266 pref64.prefix,
267 )))
268 }
269
270 if let Some(url) = intf
271 .captive_portal
272 .as_ref()
273 .or(config.captive_portal.as_ref())
274 {
275 options.add_option(icmppkt::NDOptionValue::CaptivePortal(url.into()))
276 }
277
278 icmppkt::RtrAdvertisement {
279 hop_limit: intf.hoplimit,
280 flag_managed: intf.managed,
281 flag_other: intf.other,
282 lifetime: intf.lifetime.always_unwrap_or(lifetime),
283 reachable: intf.reachable,
284 retrans: intf.retrans,
285 options,
286 }
287 }
288
289 async fn build_announcement(
290 &self,
291 ifidx: u32,
292 intf: &config::Interface,
293 ) -> icmppkt::RtrAdvertisement {
294 let ll = match self.netinfo.get_linkaddr_by_ifidx(ifidx).await {
296 Some(erbium_net::netinfo::LinkLayer::Ethernet(lladdr)) => Some(lladdr),
297 _ => None,
298 };
299
300 let ScopeSorter(self6) = self
304 .netinfo
305 .get_prefixes_by_ifidx(ifidx)
306 .await
307 .unwrap() .iter()
309 .filter_map(|(addr, _prefixlen)| {
310 if let std::net::IpAddr::V6(ip6) = addr {
311 Some(ScopeSorter(*ip6))
312 } else {
313 None
314 }
315 })
316 .max()
317 .unwrap(); use config::ConfigValue::*;
325 let mtu = match intf.mtu {
326 NotSpecified => self.netinfo.get_mtu_by_ifidx(ifidx).await,
327 Value(v) => Some(v),
328 DontSet => None,
329 };
330
331 let lifetime = match intf.lifetime {
335 NotSpecified => {
336 if let Some((_gw, gwif)) = self.netinfo.get_ipv6_default_route().await {
337 if gwif != Some(ifidx) {
338 ADV_DEFAULT_LIFETIME
340 } else {
341 std::time::Duration::from_secs(0)
342 }
343 } else {
344 std::time::Duration::from_secs(0)
345 }
346 }
347 Value(v) => v,
348 DontSet => std::time::Duration::from_secs(0),
349 };
350
351 Self::build_announcement_pure(&*self.conf.read().await, intf, ll, mtu, self6, lifetime)
352 }
353
354 async fn build_announcement_by_ifidx(
355 &self,
356 ifidx: u32,
357 ) -> Result<icmppkt::RtrAdvertisement, Error> {
358 let ifname = self.netinfo.get_safe_name_by_ifidx(ifidx).await;
359 if let Some(intf) = self
360 .conf
361 .read()
362 .await
363 .ra
364 .interfaces
365 .iter()
366 .find(|intf| intf.name == ifname)
367 {
368 Ok(self.build_announcement(ifidx, intf).await)
369 } else if let Some(prefixes) = self.netinfo.get_prefixes_by_ifidx(ifidx).await {
370 let addresses = &self.conf.read().await.addresses;
371 let prefixes = prefixes
372 .iter()
373 .filter_map(|(addr, prefixlen)| {
374 if let std::net::IpAddr::V6(ref ip6) = *addr {
375 if addresses.contains(&crate::config::Prefix::new(*addr, *prefixlen)) {
376 Some(config::Prefix {
377 addr: *ip6,
378 prefixlen: *prefixlen,
379 onlink: true,
380 autonomous: true,
381 valid: std::time::Duration::from_secs(2592000),
382 preferred: std::time::Duration::from_secs(604800),
383 })
384 } else {
385 None
386 }
387 } else {
388 None
389 }
390 })
391 .collect::<Vec<config::Prefix>>();
392 if prefixes.is_empty() {
393 Err(Error::UnconfiguredInterface(ifname))
394 } else {
395 let intf = config::Interface {
396 prefixes,
398 ..Default::default()
399 };
400 Ok(self.build_announcement(ifidx, &intf).await)
401 }
402 } else {
403 Err(Error::UnconfiguredInterface(ifname))
404 }
405 }
406
407 async fn send_announcement(
408 &self,
409 msg: icmppkt::RtrAdvertisement,
410 dst: erbium_net::addr::NetAddr,
411 intf: u32,
412 ) -> Result<(), Error> {
413 let smsg = icmppkt::Icmp6::RtrAdvert(msg);
414 let s = icmppkt::serialise(&smsg);
415 use erbium_net::socket;
416 let cmsg = if intf != 0 {
417 socket::ControlMessage::new().set_src6_intf(intf)
418 } else {
419 socket::ControlMessage::new()
420 };
421 if let Err(e) = self
422 .rawsock
423 .send_msg(&s, &cmsg, socket::MsgFlags::empty(), Some(&dst))
424 .await
425 {
426 log::warn!(
427 "Failed to send router advertisement for {}(if#{}) ({}): {}",
428 self.netinfo.get_safe_name_by_ifidx(intf).await,
429 intf,
430 dst,
431 e
432 );
433 } else {
434 RADV_TX_PACKETS
435 .with_label_values(&[&self.netinfo.get_safe_name_by_ifidx(intf).await])
436 .inc();
437 }
438 Ok(())
439 }
440
441 async fn handle_solicit(
442 &self,
443 rm: erbium_net::socket::RecvMsg,
444 _in_opt: &icmppkt::NDOptions,
445 ) -> Result<(), Error> {
446 if let Some(ifidx) = rm.local_intf() {
447 if let Some(dst) = rm.address.as_ref() {
448 let ifidx = ifidx.try_into().expect("Interface with ifidx");
449 let reply = self.build_announcement_by_ifidx(ifidx).await?;
450 self.send_announcement(reply, *dst, ifidx).await
451 } else {
452 Err(Error::Io(std::io::Error::other(
453 "Missing destination address",
454 )))
455 }
456 } else {
457 Err(Error::Io(std::io::Error::other(
458 "Packet missing interface information",
459 )))
460 }
461 }
462
463 async fn send_unsolicited(&self, ifidx: u32) -> Result<(), Error> {
464 let msg = self.build_announcement_by_ifidx(ifidx).await?;
465 let dst = std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
466 ALL_NODES,
467 erbium_net::raw::IpProto::ICMP6.into(), 0, ifidx, ))
471 .into();
472
473 self.send_announcement(msg, dst, ifidx).await
474 }
475
476 async fn run_unsolicited(&self) -> Result<Void, Error> {
477 use rand::RngExt as _;
478 loop {
479 let timeout = std::time::Duration::from_secs(rand::rng().random_range(
481 DEFAULT_MIN_RTR_ADV_INTERVAL.as_secs()..DEFAULT_MAX_RTR_ADV_INTERVAL.as_secs(),
482 ));
483 tokio::time::sleep(timeout).await;
484 for idx in self.netinfo.get_ifindexes().await {
485 if let Some(ifflags) = self.netinfo.get_flags_by_ifidx(idx).await
486 && ifflags.has_multicast()
487 {
488 match self.send_unsolicited(idx).await {
489 Ok(_) => (),
490 Err(Error::UnconfiguredInterface(_)) => (), e => e?,
492 }
493 }
494 }
495 }
496 }
497
498 async fn run_solicited(&self) -> Result<Void, Error> {
499 loop {
500 let rm = match self
501 .rawsock
502 .recv_msg(65536, erbium_net::raw::MsgFlags::empty())
503 .await
504 {
505 Ok(m) => m,
506 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
507 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
508 Err(e) => return Err(Error::Io(e)),
509 };
510 let ifname = match rm.local_intf() {
511 Some(ifidx) => self.netinfo.get_safe_name_by_ifidx(ifidx as u32).await,
512 None => "<unknown>".into(),
513 };
514 RADV_RX_PACKETS.with_label_values(&[&ifname]).inc();
515 let msg = icmppkt::parse(&rm.buffer);
516 match msg {
517 Ok(icmppkt::Icmp6::Unknown) => (),
518 Err(_) => (),
519 Ok(icmppkt::Icmp6::RtrSolicit(opt)) => {
520 RADV_SOLICITATIONS.with_label_values(&[&ifname]).inc();
521 if let Err(e) = self.handle_solicit(rm, &opt).await {
522 log::warn!("Failed to handle router solicitation: {}", e);
523 }
524 }
525 Ok(icmppkt::Icmp6::RtrAdvert(_)) => (),
526 }
527 }
528 }
529
530 pub async fn run(self: std::sync::Arc<Self>) -> Result<(), String> {
531 use futures::StreamExt as _;
532 log::info!("Starting Router Advertisement service");
533 let mut services = futures::stream::FuturesUnordered::new();
534 let sol_self = self.clone();
535 let unsol_self = self.clone();
536 let sol = async move { sol_self.run_solicited().await };
537 let unsol = async move { unsol_self.run_unsolicited().await };
538 services.push(tokio::spawn(sol));
539 services.push(tokio::spawn(unsol));
540 while !services.is_empty() {
541 let ret = match services.next().await {
542 None => "No router advertisement services found".into(),
543 Some(Ok(Ok(v))) => format!(
544 "Router advertisement service unexpectedly exited successfully: {:?}",
545 v
546 ),
547 Some(Ok(Err(e))) => e.to_string(), Some(Err(e)) => e.to_string(), };
550 log::error!("Router advertisement service shutdown: {}", ret);
551 }
552 Err("Router advertisement service shutdown".into())
553 }
554}
555
556#[cfg(test)]
557use crate::config::ConfigValue;
558
559#[test]
560fn test_build_announcement() {
561 let conf = crate::config::Config::default();
562 let msg = RaAdvService::build_announcement_pure(
563 &conf,
564 &config::Interface {
565 name: "eth0".into(),
566 hoplimit: 64,
567 managed: false,
568 other: false,
569 lifetime: ConfigValue::Value(std::time::Duration::from_secs(3600)),
570 reachable: std::time::Duration::from_secs(1800),
571 retrans: std::time::Duration::from_secs(10),
572 mtu: config::ConfigValue::NotSpecified,
573 min_rtr_adv_interval: ConfigValue::Value(std::time::Duration::from_secs(200)),
574 max_rtr_adv_interval: ConfigValue::Value(std::time::Duration::from_secs(600)),
575 prefixes: vec![config::Prefix {
576 addr: "2001:db8::".parse().unwrap(),
577 prefixlen: 64,
578 onlink: true,
579 autonomous: true,
580 valid: std::time::Duration::from_secs(3600),
581 preferred: std::time::Duration::from_secs(1800),
582 }],
583 rdnss_lifetime: config::ConfigValue::Value(std::time::Duration::from_secs(3600)),
584 rdnss: config::ConfigValue::Value(vec!["2001:db8::53".parse().unwrap()]),
585 dnssl_lifetime: config::ConfigValue::Value(std::time::Duration::from_secs(3600)),
586 dnssl: config::ConfigValue::Value(vec![]),
587 captive_portal: config::ConfigValue::Value("http://example.com/".into()),
588 pref64: Some(config::Pref64 {
589 lifetime: std::time::Duration::from_secs(600),
590 prefix: "64:ff9b::".parse().unwrap(),
591 prefixlen: 96,
592 }),
593 },
594 Some([1, 2, 3, 4, 5, 6]),
595 Some(1480),
596 std::net::Ipv6Addr::UNSPECIFIED,
597 ADV_DEFAULT_LIFETIME,
598 );
599 icmppkt::serialise(&icmppkt::Icmp6::RtrAdvert(msg));
600}
601
602#[test]
603fn test_default_values() {
604 let conf = crate::config::Config {
605 dns_servers: vec![
606 "192.0.2.53".parse().unwrap(),
607 "2001:db8::53".parse().unwrap(),
608 ],
609 dns_search: vec!["example.com".into()],
610 captive_portal: Some("example.com".into()),
611 ..Default::default()
612 };
613 let msg = RaAdvService::build_announcement_pure(
614 &conf,
615 &config::Interface {
616 dnssl: config::ConfigValue::NotSpecified,
617 rdnss: config::ConfigValue::NotSpecified,
618 captive_portal: config::ConfigValue::NotSpecified,
619 ..Default::default()
620 },
621 Some([1, 2, 3, 4, 5, 6]),
622 Some(1480),
623 std::net::Ipv6Addr::UNSPECIFIED,
624 ADV_DEFAULT_LIFETIME,
625 );
626 assert_eq!(
627 msg.options
628 .find_option(icmppkt::RDNSS)
629 .iter()
630 .map(
631 |x| if let icmppkt::NDOptionValue::RecursiveDnsServers((_, servers)) = x {
632 servers
633 } else {
634 panic!("bad")
635 }
636 )
637 .cloned()
638 .collect::<Vec<Vec<_>>>(),
639 vec![vec!["2001:db8::53".parse::<std::net::Ipv6Addr>().unwrap()]]
640 );
641 assert_eq!(
642 msg.options
643 .find_option(icmppkt::DNSSL)
644 .iter()
645 .map(
646 |x| if let icmppkt::NDOptionValue::DnsSearchList((_, domains)) = x {
647 domains
648 } else {
649 panic!("bad")
650 }
651 )
652 .cloned()
653 .collect::<Vec<Vec<_>>>(),
654 vec![vec![String::from("example.com")]]
655 );
656 assert_eq!(
657 msg.options
658 .find_option(icmppkt::CAPTIVE_PORTAL)
659 .iter()
660 .map(
661 |x| if let icmppkt::NDOptionValue::CaptivePortal(domain) = x {
662 domain
663 } else {
664 panic!("bad")
665 }
666 )
667 .cloned()
668 .collect::<Vec<_>>(),
669 vec![String::from("example.com")]
670 );
671}
672
673#[test]
674fn test_dontset_values() {
675 let conf = crate::config::Config {
676 dns_servers: vec![
677 "192.0.2.53".parse().unwrap(),
678 "2001:db8::53".parse().unwrap(),
679 ],
680 dns_search: vec!["example.com".into()],
681 captive_portal: Some("example.com".into()),
682 ..Default::default()
683 };
684 let msg = RaAdvService::build_announcement_pure(
685 &conf,
686 &config::Interface {
687 dnssl: config::ConfigValue::DontSet,
688 rdnss: config::ConfigValue::DontSet,
689 captive_portal: config::ConfigValue::DontSet,
690 ..Default::default()
691 },
692 Some([1, 2, 3, 4, 5, 6]),
693 Some(1480),
694 std::net::Ipv6Addr::UNSPECIFIED,
695 ADV_DEFAULT_LIFETIME,
696 );
697 assert!(msg.options.find_option(icmppkt::RDNSS).is_empty());
698 assert!(msg.options.find_option(icmppkt::DNSSL).is_empty());
699 assert!(msg.options.find_option(icmppkt::CAPTIVE_PORTAL).is_empty());
700}