1use erbium_net::addr::*;
20use std::convert::TryFrom;
21use std::os::unix::fs::PermissionsExt as _;
22use tokio::io::AsyncReadExt as _;
23use yaml_rust::yaml;
24
25#[derive(Debug)]
26pub enum Error {
27 IoError(std::io::Error),
28 Utf8Error(std::string::FromUtf8Error),
29 YamlError(yaml_rust::scanner::ScanError),
30 MissingConfig,
31 MultipleConfigs,
32 ConfigProcessFailed,
33 InvalidConfig(String),
34}
35
36impl std::fmt::Display for Error {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Error::IoError(e) => write!(f, "{}", e),
40 Error::Utf8Error(e) => {
41 write!(f, "UTF8 Decoding error reading configuration file: {}", e)
42 }
43 Error::YamlError(e) => write!(f, "Yaml parse error while reading configuration: {}", e),
44 Error::MissingConfig => write!(f, "Configuration is empty/missing"),
45 Error::MultipleConfigs => {
46 write!(f, "Configuration file contains multiple configurations")
47 }
48 Error::ConfigProcessFailed => write!(f, "Configuration process failed"),
49 Error::InvalidConfig(e) => write!(f, "Invalid Configuration: {}", e),
50 }
51 }
52}
53
54impl std::error::Error for Error {}
55
56impl Error {
57 #[must_use]
58 pub fn annotate(self, s: &str) -> Error {
59 Error::InvalidConfig(format!("{}: {}", self, s))
60 }
61}
62
63#[derive(Default, Debug, Clone)]
64pub enum ConfigValue<T: Clone> {
65 #[default]
66 NotSpecified,
67 DontSet,
68 Value(T),
69}
70
71impl<T: Clone> ConfigValue<T> {
72 pub fn from_option(v: Option<T>) -> Self {
73 match v {
74 None => ConfigValue::DontSet,
75 Some(x) => ConfigValue::Value(x),
76 }
77 }
78 pub fn unwrap_or(&self, n: T) -> Option<T> {
80 match self {
81 ConfigValue::NotSpecified => Some(n),
82 ConfigValue::DontSet => None,
83 ConfigValue::Value(v) => Some(v.clone()),
84 }
85 }
86 pub fn or(&self, n: Option<T>) -> Option<T> {
87 match self {
88 ConfigValue::NotSpecified => n,
89 ConfigValue::DontSet => None,
90 ConfigValue::Value(v) => Some(v.clone()),
91 }
92 }
93 pub fn as_ref(&self) -> ConfigValue<&T> {
94 match self {
95 ConfigValue::NotSpecified => ConfigValue::NotSpecified,
96 ConfigValue::DontSet => ConfigValue::DontSet,
97 ConfigValue::Value(v) => ConfigValue::Value(v),
98 }
99 }
100 pub fn base_default(&self, n: T) -> Option<T> {
103 match self {
104 ConfigValue::NotSpecified => None,
105 ConfigValue::DontSet => Some(n),
106 ConfigValue::Value(v) => Some(v.clone()),
107 }
108 }
109 pub fn always_unwrap_or(&self, n: T) -> T {
112 match self {
113 ConfigValue::NotSpecified => n,
114 ConfigValue::DontSet => n,
115 ConfigValue::Value(v) => v.clone(),
116 }
117 }
118 #[must_use]
119 pub fn apply_default(&self, n: ConfigValue<T>) -> ConfigValue<T> {
120 match (self, n) {
121 (ConfigValue::NotSpecified, ConfigValue::NotSpecified) => ConfigValue::NotSpecified,
122 (ConfigValue::NotSpecified, ConfigValue::DontSet) => ConfigValue::DontSet,
123 (ConfigValue::NotSpecified, ConfigValue::Value(v)) => ConfigValue::Value(v),
124 (ConfigValue::DontSet, _) => ConfigValue::DontSet,
125 (ConfigValue::Value(v), _) => ConfigValue::Value(v.clone()),
126 }
127 }
128}
129
130pub fn type_to_name(fragment: &yaml::Yaml) -> String {
131 match fragment {
132 yaml::Yaml::Real(_) => "Real".into(),
133 yaml::Yaml::Integer(_) => "Integer".into(),
134 yaml::Yaml::String(_) => "String".into(),
135 yaml::Yaml::Boolean(_) => "Boolean".into(),
136 yaml::Yaml::Array(a) => format!("Array of {}", type_to_name(&a[0])),
137 yaml::Yaml::Hash(_) => "Hash".into(),
138 yaml::Yaml::Alias(_) => "Alias".into(),
139 yaml::Yaml::Null => "Null".into(),
140 yaml::Yaml::BadValue => "Bad Value".into(),
141 }
142}
143
144pub fn parse_i64(name: &str, fragment: &yaml::Yaml) -> Result<Option<i64>, Error> {
145 match fragment {
146 yaml::Yaml::Null => Ok(None),
147 yaml::Yaml::Integer(i) => Ok(Some(*i)),
148 e => Err(Error::InvalidConfig(format!(
149 "{} should be of type Integer, not {}",
150 name,
151 type_to_name(e)
152 ))),
153 }
154}
155
156pub fn parse_num<N: TryFrom<i64>>(name: &str, fragment: &yaml::Yaml) -> Result<Option<N>, Error> {
157 match parse_i64(name, fragment) {
158 Ok(None) => Ok(None),
159 Err(e) => Err(e),
160 Ok(Some(v)) => Ok(Some(N::try_from(v).map_err(|_| {
161 Error::InvalidConfig(format!("{} out of range for {}", v, name))
162 })?)),
163 }
164}
165
166pub fn parse_string(name: &str, fragment: &yaml::Yaml) -> Result<Option<String>, Error> {
167 match fragment {
168 yaml::Yaml::Null => Ok(None),
169 yaml::Yaml::String(s) => Ok(Some(s.into())),
170 e => Err(Error::InvalidConfig(format!(
171 "{} should be of type String, not {}",
172 name,
173 type_to_name(e)
174 ))),
175 }
176}
177
178pub fn parse_boolean(name: &str, fragment: &yaml::Yaml) -> Result<Option<bool>, Error> {
179 match fragment {
180 yaml::Yaml::Null => Ok(None),
181 yaml::Yaml::Boolean(b) => Ok(Some(*b)),
182 e => Err(Error::InvalidConfig(format!(
183 "{} should be of type Boolean, not {}",
184 name,
185 type_to_name(e)
186 ))),
187 }
188}
189
190pub fn parse_array<T, F>(
191 name: &str,
192 fragment: &yaml::Yaml,
193 mut parser: F,
194) -> Result<Option<Vec<T>>, Error>
195where
196 F: FnMut(&str, &yaml::Yaml) -> Result<Option<T>, Error>,
197{
198 match fragment {
199 yaml::Yaml::Null => Ok(None),
200 yaml::Yaml::Array(a) => {
201 let mut v = a
202 .iter()
203 .map(|it| parser(name, it))
204 .collect::<Result<Vec<_>, _>>()?
205 .drain(..)
206 .map(|it| {
207 it.ok_or_else(|| {
208 Error::InvalidConfig(format!("Cannot have a Null value in array {}", name))
209 })
210 })
211 .collect::<Result<Vec<T>, _>>()?;
212 v.shrink_to_fit();
213 Ok(Some(v))
214 }
215 e => Err(Error::InvalidConfig(format!(
216 "{} should be of type Array, not {}",
217 name,
218 type_to_name(e)
219 ))),
220 }
221}
222
223pub const INTERFACE4: std::net::IpAddr = std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0));
224pub const INTERFACE6: std::net::IpAddr =
225 std::net::IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
226
227fn str_ip(ost: Option<String>) -> Result<Option<std::net::IpAddr>, Error> {
228 match ost {
229 Some(st) if st == "$self4" => Ok(Some(INTERFACE4)),
230 Some(st) if st == "$self6" => Ok(Some(INTERFACE6)),
231 Some(st) => Some(
232 st.parse()
233 .map_err(|e| Error::InvalidConfig(format!("{}", e))),
234 )
235 .transpose(),
236 None => Ok(None),
237 }
238}
239
240fn str_ip4(ost: Option<String>) -> Result<Option<std::net::Ipv4Addr>, Error> {
241 match str_ip(ost) {
242 Err(e) => Err(e),
243 Ok(None) => Ok(None),
244 Ok(Some(std::net::IpAddr::V4(ip4))) => Ok(Some(ip4)),
245 Ok(Some(std::net::IpAddr::V6(ip6))) => Err(Error::InvalidConfig(format!(
246 "Expected v4 address, got v6 address ({})",
247 ip6,
248 ))),
249 }
250}
251
252fn str_ip6(ost: Option<String>) -> Result<Option<std::net::Ipv6Addr>, Error> {
253 match str_ip(ost) {
254 Err(e) => Err(e),
255 Ok(None) => Ok(None),
256 Ok(Some(std::net::IpAddr::V6(ip6))) => Ok(Some(ip6)),
257 Ok(Some(std::net::IpAddr::V4(ip4))) => Err(Error::InvalidConfig(format!(
258 "Expected v6 address, got v4 address ({})",
259 ip4,
260 ))),
261 }
262}
263
264#[derive(Debug)]
265enum HexError {
266 InvalidDigit(u8),
267 WrongLength,
268}
269
270impl std::fmt::Display for HexError {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 match self {
273 HexError::InvalidDigit(x) => write!(f, "Unexpected hex digit {}", x),
274 HexError::WrongLength => write!(f, "Hex byte is not two digits long"),
275 }
276 }
277}
278
279const fn hexdigit(c: u8) -> Result<u8, HexError> {
280 match c {
281 b'A'..=b'F' => Ok(c - b'A' + 10),
282 b'a'..=b'f' => Ok(c - b'a' + 10),
283 b'0'..=b'9' => Ok(c - b'0'),
284 _ => Err(HexError::InvalidDigit(c)),
285 }
286}
287
288fn hexbyte(st: &str) -> Result<u8, HexError> {
289 let mut it = st.bytes();
290 if let Some(n1) = it.next()
291 && let Some(n2) = it.next()
292 && it.next().is_none()
293 {
294 return Ok((hexdigit(n1)? << 4) | hexdigit(n2)?);
295 }
296 Err(HexError::WrongLength)
297}
298
299fn str_hwaddr(ost: Option<String>) -> Result<Option<Vec<u8>>, Error> {
300 ost.map(|st| {
301 st.split(':') .map(hexbyte) .collect()
304 })
305 .transpose()
306 .map_err(|e| Error::InvalidConfig(e.to_string()))
307}
308
309fn str_prefix(ost: Option<String>) -> Result<Option<Prefix>, Error> {
313 Ok(ost
314 .map(|st| {
315 let sections = st.split('/').collect::<Vec<_>>();
316 if sections.len() != 2 {
317 Err(Error::InvalidConfig(format!(
318 "Expected IP prefix, but '{}'",
319 st
320 )))
321 } else {
322 let prefixlen = sections[1]
323 .parse()
324 .map_err(|x| Error::InvalidConfig(format!("{}", x)))?;
325 match str_ip(Some(sections[0].into())) {
326 Ok(Some(std::net::IpAddr::V4(ip4))) => Ok(Some(Prefix::V4(Prefix4 {
327 addr: ip4,
328 prefixlen,
329 }))),
330 Ok(Some(std::net::IpAddr::V6(ip6))) => Ok(Some(Prefix::V6(Prefix6 {
331 addr: ip6,
332 prefixlen,
333 }))),
334 Err(e) => Err(e),
335 Ok(None) => Ok(None),
336 }
337 }
338 })
339 .transpose()?
340 .flatten())
341}
342
343fn str_prefix4(ost: Option<String>) -> Result<Option<Prefix4>, Error> {
346 Ok(ost
347 .map(|st| {
348 let sections = st.split('/').collect::<Vec<_>>();
349 if sections.len() != 2 {
350 Err(Error::InvalidConfig(format!(
351 "Expected IPv4 prefix, but '{}'",
352 st
353 )))
354 } else {
355 let prefixlen = sections[1]
356 .parse()
357 .map_err(|x| Error::InvalidConfig(format!("{}", x)))?;
358 match str_ip4(Some(sections[0].into())) {
359 Ok(Some(ip4)) => Ok(Some(Prefix4 {
360 addr: ip4,
361 prefixlen,
362 })),
363 Err(e) => Err(e),
364 Ok(None) => Ok(None),
365 }
366 }
367 })
368 .transpose()?
369 .flatten())
370}
371
372fn str_prefix6(ost: Option<String>) -> Result<Option<Prefix6>, Error> {
375 Ok(ost
376 .map(|st| {
377 let sections = st.split('/').collect::<Vec<_>>();
378 if sections.len() != 2 {
379 Err(Error::InvalidConfig(format!(
380 "Expected IPv6 prefix, but '{}'",
381 st
382 )))
383 } else {
384 let prefixlen = sections[1]
385 .parse()
386 .map_err(|x| Error::InvalidConfig(format!("{}", x)))?;
387 match str_ip6(Some(sections[0].into())) {
388 Ok(Some(ip6)) => Ok(Some(Prefix6 {
389 addr: ip6,
390 prefixlen,
391 })),
392 Err(e) => Err(e),
393 Ok(None) => Ok(None),
394 }
395 }
396 })
397 .transpose()?
398 .flatten())
399}
400
401fn str_duration(ost: Option<String>) -> Result<Option<std::time::Duration>, Error> {
402 ost.map(|st| {
403 let mut num = None;
404 let mut ret = Default::default();
405 for c in st.chars() {
406 match c {
407 '0'..='9' => {
408 if let Some(n) = num {
409 num = Some(n * 10 + c as u64 - '0' as u64);
410 } else {
411 num = Some(c as u64 - '0' as u64);
412 }
413 }
414 's' => {
415 ret += std::time::Duration::from_secs(num.take().unwrap());
416 }
417 'm' => {
418 ret += std::time::Duration::from_secs(num.take().unwrap() * 60);
419 }
420 'h' => {
421 ret += std::time::Duration::from_secs(num.take().unwrap() * 3600);
422 }
423 'd' => {
424 ret += std::time::Duration::from_secs(num.take().unwrap() * 86400);
425 }
426 'w' => {
427 ret += std::time::Duration::from_secs(num.take().unwrap() * 7 * 86400);
428 }
429 x if x.is_whitespace() => (),
430 '_' => (),
431 _ => {
432 return Err(Error::InvalidConfig(format!(
433 "Unexpected {} in duration",
434 c
435 )));
436 }
437 }
438 }
439 if let Some(n) = num {
440 ret += std::time::Duration::from_secs(n);
441 }
442 Ok(ret)
443 })
444 .transpose()
445}
446
447pub fn parse_string_hwaddr(name: &str, fragment: &yaml::Yaml) -> Result<Option<Vec<u8>>, Error> {
448 parse_string(name, fragment).and_then(str_hwaddr)
449}
450
451pub fn parse_string_ip(
452 name: &str,
453 fragment: &yaml::Yaml,
454) -> Result<Option<std::net::IpAddr>, Error> {
455 parse_string(name, fragment)
456 .and_then(|s| str_ip(s).map_err(|e| Error::InvalidConfig(format!("{}: {}", name, e))))
457}
458
459pub fn parse_string_ip4(
460 name: &str,
461 fragment: &yaml::Yaml,
462) -> Result<Option<std::net::Ipv4Addr>, Error> {
463 parse_string(name, fragment)
464 .and_then(|s| str_ip4(s).map_err(|e| Error::InvalidConfig(format!("{}: {}", name, e))))
465}
466
467pub fn parse_string_ip6(
468 name: &str,
469 fragment: &yaml::Yaml,
470) -> Result<Option<std::net::Ipv6Addr>, Error> {
471 parse_string(name, fragment)
472 .and_then(|s| str_ip6(s).map_err(|e| Error::InvalidConfig(format!("{}: {}", name, e))))
473}
474
475pub fn parse_string_prefix(name: &str, fragment: &yaml::Yaml) -> Result<Option<Prefix>, Error> {
476 parse_string(name, fragment)
477 .and_then(|s| str_prefix(s).map_err(|e| Error::InvalidConfig(format!("{}: {}", name, e))))
478}
479
480pub fn parse_string_prefix4(name: &str, fragment: &yaml::Yaml) -> Result<Option<Prefix4>, Error> {
481 parse_string(name, fragment).and_then(str_prefix4)
482}
483
484pub fn parse_string_prefix6(name: &str, fragment: &yaml::Yaml) -> Result<Option<Prefix6>, Error> {
485 parse_string(name, fragment)
486 .and_then(|s| str_prefix6(s).map_err(|e| Error::InvalidConfig(format!("{}: {}", name, e))))
487}
488
489pub fn str_sockaddr(ost: Option<String>) -> Result<Option<NetAddr>, Error> {
490 fn std_to_netaddr(src: std::net::SocketAddr) -> NetAddr {
491 use std::net::SocketAddr::*;
492 match src {
493 V4(v4) => v4.into(),
494 V6(v6) => v6.into(),
495 }
496 }
497 ost.map(|st| match st.get(0..1) {
498 Some("@") => UnixAddr::new_abstract(&st.as_bytes()[1..])
499 .map(to_net_addr)
500 .map_err(|e| Error::InvalidConfig(format!("{} ({})", e, st))),
501 Some(_) if st.contains('/') => UnixAddr::new(st.as_bytes())
502 .map(to_net_addr)
503 .map_err(|e| Error::InvalidConfig(format!("{} ({})", e, st))),
504 Some(_) => st
505 .parse::<std::net::SocketAddr>()
506 .map(std_to_netaddr)
507 .map_err(|e| Error::InvalidConfig(format!("{} ({})", e, st))),
508 None => Err(Error::InvalidConfig(
509 "Invalid socket address, expected unix socket or ip socket".into(),
510 )),
511 })
512 .transpose()
513}
514
515pub fn parse_string_sockaddr(name: &str, fragment: &yaml::Yaml) -> Result<Option<NetAddr>, Error> {
516 parse_string(name, fragment)
517 .and_then(|s| str_sockaddr(s).map_err(|e| Error::InvalidConfig(format!("{}: {}", name, e))))
518}
519
520pub fn parse_duration(
521 name: &str,
522 fragment: &yaml::Yaml,
523) -> Result<Option<std::time::Duration>, Error> {
524 if let yaml::Yaml::Integer(i) = fragment {
525 Ok(Some(std::time::Duration::from_secs(*i as u64)))
526 } else {
527 parse_string(name, fragment).and_then(str_duration)
528 }
529}
530
531pub trait PrefixOps {
532 type Ip;
533 fn network(&self) -> Self::Ip;
534 fn netmask(&self) -> Self::Ip;
535 fn broadcast(&self) -> Self::Ip;
536}
537
538pub trait Match<Ip> {
539 fn contains(&self, ip: Ip) -> bool;
540}
541
542#[derive(Debug, Eq, Clone)]
543pub struct Prefix4 {
544 pub addr: std::net::Ipv4Addr,
545 pub prefixlen: u8,
546}
547
548impl Prefix4 {
549 pub fn new(addr: std::net::Ipv4Addr, prefixlen: u8) -> Self {
550 assert!(prefixlen <= 32);
551 Self { addr, prefixlen }
552 }
553}
554
555impl PrefixOps for Prefix4 {
556 type Ip = std::net::Ipv4Addr;
557 fn network(&self) -> std::net::Ipv4Addr {
558 (u32::from(self.addr) & u32::from(self.netmask())).into()
559 }
560 fn netmask(&self) -> std::net::Ipv4Addr {
561 (!0xffffffff_u32
562 .checked_shr(self.prefixlen as u32)
563 .unwrap_or(0))
564 .into()
565 }
566 fn broadcast(&self) -> std::net::Ipv4Addr {
567 (u32::from(self.network()) | !u32::from(self.netmask())).into()
568 }
569}
570
571impl Match<std::net::Ipv4Addr> for Prefix4 {
572 fn contains(&self, ip: std::net::Ipv4Addr) -> bool {
573 u32::from(ip) & u32::from(self.netmask()) == u32::from(self.addr)
574 }
575}
576
577impl Match<std::net::Ipv6Addr> for Prefix4 {
578 fn contains(&self, ip: std::net::Ipv6Addr) -> bool {
579 match ip.octets() {
580 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => {
582 self.contains(std::net::Ipv4Addr::new(a, b, c, d))
583 }
584 _ => false,
585 }
586 }
587}
588
589impl PartialEq for Prefix4 {
590 fn eq(&self, other: &Self) -> bool {
591 self.network() == other.network() && self.netmask() == other.netmask()
592 }
593}
594
595#[derive(Debug, Eq, Clone)]
596pub struct Prefix6 {
597 pub addr: std::net::Ipv6Addr,
598 pub prefixlen: u8,
599}
600
601impl Prefix6 {
602 pub fn new(addr: std::net::Ipv6Addr, prefixlen: u8) -> Self {
603 assert!(prefixlen <= 128);
604 Self { addr, prefixlen }
605 }
606}
607
608impl PrefixOps for Prefix6 {
609 type Ip = std::net::Ipv6Addr;
610 fn network(&self) -> std::net::Ipv6Addr {
611 (u128::from(self.addr) & u128::from(self.netmask())).into()
612 }
613 fn netmask(&self) -> std::net::Ipv6Addr {
614 (!(0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_u128
615 .checked_shr(self.prefixlen as u32)
616 .unwrap_or(0)))
617 .into()
618 }
619 fn broadcast(&self) -> std::net::Ipv6Addr {
620 (u128::from(self.network()) | !u128::from(self.netmask())).into()
624 }
625}
626
627impl Match<std::net::Ipv6Addr> for Prefix6 {
628 fn contains(&self, ip: std::net::Ipv6Addr) -> bool {
629 u128::from(ip) & u128::from(self.netmask()) == u128::from(self.addr)
630 }
631}
632
633impl Match<std::net::Ipv4Addr> for Prefix6 {
634 fn contains(&self, ip: std::net::Ipv4Addr) -> bool {
635 match self.network().octets() {
636 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => Prefix4::new(
638 std::net::Ipv4Addr::new(a, b, c, d),
639 self.prefixlen - (128 - 32),
640 )
641 .contains(ip),
642 _ => false,
643 }
644 }
645}
646
647impl PartialEq for Prefix6 {
648 fn eq(&self, other: &Self) -> bool {
649 self.network() == other.network() && self.netmask() == other.netmask()
650 }
651}
652
653#[derive(Debug, Eq, PartialEq, Clone)]
654pub enum Prefix {
655 V4(Prefix4),
656 V6(Prefix6),
657}
658
659impl From<Prefix4> for Prefix {
660 fn from(p: Prefix4) -> Self {
661 Self::V4(p)
662 }
663}
664
665impl From<Prefix6> for Prefix {
666 fn from(p: Prefix6) -> Self {
667 Self::V6(p)
668 }
669}
670
671impl Prefix {
672 pub fn new(ip: std::net::IpAddr, prefixlen: u8) -> Self {
673 use std::net::IpAddr::*;
674 match ip {
675 V4(ip4) => Self::V4(Prefix4::new(ip4, prefixlen)),
676 V6(ip6) => Self::V6(Prefix6::new(ip6, prefixlen)),
677 }
678 }
679}
680
681impl PrefixOps for Prefix {
682 type Ip = std::net::IpAddr;
683 fn network(&self) -> Self::Ip {
684 use std::net::IpAddr::*;
685 match self {
686 Prefix::V4(p4) => V4(p4.network()),
687 Prefix::V6(p6) => V6(p6.network()),
688 }
689 }
690 fn netmask(&self) -> Self::Ip {
691 use std::net::IpAddr::*;
692 match self {
693 Prefix::V4(p4) => V4(p4.netmask()),
694 Prefix::V6(p6) => V6(p6.netmask()),
695 }
696 }
697 fn broadcast(&self) -> Self::Ip {
698 use std::net::IpAddr::*;
699 match self {
700 Prefix::V4(p4) => V4(p4.broadcast()),
701 Prefix::V6(p6) => V6(p6.broadcast()),
702 }
703 }
704}
705
706impl Match<std::net::IpAddr> for Prefix {
707 fn contains(&self, ip: std::net::IpAddr) -> bool {
708 match (self, ip) {
709 (Prefix::V4(p4), std::net::IpAddr::V4(ip4)) => p4.contains(ip4),
710 (Prefix::V6(p6), std::net::IpAddr::V6(ip6)) => p6.contains(ip6),
711 (Prefix::V4(p4), std::net::IpAddr::V6(ip6)) => p4.contains(ip6),
713 (Prefix::V6(p6), std::net::IpAddr::V4(ip4)) => p6.contains(ip4),
714 }
715 }
716}
717
718impl Match<std::net::Ipv4Addr> for Prefix {
719 fn contains(&self, ip: std::net::Ipv4Addr) -> bool {
720 self.contains(std::net::IpAddr::V4(ip))
721 }
722}
723
724impl Match<std::net::Ipv6Addr> for Prefix {
725 fn contains(&self, ip: std::net::Ipv6Addr) -> bool {
726 self.contains(std::net::IpAddr::V6(ip))
727 }
728}
729
730#[derive(Debug)]
731pub enum AddressType {
740 Addresses(Vec<NetAddr>),
741 BindInterface,
742}
743
744enum DefaultAddressType {
745 Unspecified,
746 Interface,
747}
748
749impl AddressType {
750 pub async fn as_sockaddrs(
751 &self,
752 addresses: &[Prefix],
753 netinfo: &erbium_net::netinfo::SharedNetInfo,
754 port: u16,
755 ) -> Vec<NetAddr> {
756 match self {
757 AddressType::Addresses(addrs) => addrs.clone(),
758 AddressType::BindInterface => find_listener_addresses(addresses, netinfo)
759 .await
760 .iter()
761 .map(|ip| ip.with_port(port))
762 .collect(),
763 }
764 }
765}
766
767impl Default for AddressType {
768 fn default() -> Self {
769 AddressType::Addresses(vec![
770 std::net::SocketAddrV6::new(UNSPECIFIED6, 0, 0, 0).into(),
771 ])
772 }
773}
774
775#[derive(Debug, Default)]
776pub struct Config {
777 #[cfg(feature = "dhcp")]
778 pub dhcp: crate::dhcp::config::Config,
779 pub ra: crate::radv::config::Config,
780 pub dns_servers: Vec<std::net::IpAddr>,
781 pub dns_search: Vec<String>,
782 pub captive_portal: Option<String>,
783 pub addresses: Vec<Prefix>,
784 pub listeners: Vec<NetAddr>,
785 pub dns_listeners: AddressType,
786 #[cfg(feature = "dns")]
787 pub dns_routes: Vec<crate::dns::config::Route>,
788 pub acls: Vec<crate::acl::Acl>,
789}
790
791pub type SharedConfig = std::sync::Arc<tokio::sync::RwLock<Config>>;
792
793async fn find_listener_addresses(
794 addresses: &[Prefix],
795 netinfo: &erbium_net::netinfo::SharedNetInfo,
796) -> Vec<std::net::IpAddr> {
797 let mut ret = vec![];
798 for (ifaddr, _len) in netinfo.get_if_prefixes().await {
799 for addr in addresses {
800 use crate::config::Match as _;
801 if addr.contains(ifaddr) {
802 ret.push(ifaddr)
803 }
804 }
805 }
806 ret
807}
808
809fn load_config_from_string(cfg: &str) -> Result<SharedConfig, Error> {
810 let y = yaml::YamlLoader::load_from_str(cfg).map_err(Error::YamlError)?;
811 match y.len() {
812 0 => return Err(Error::MissingConfig),
813 1 => (),
814 _ => return Err(Error::MultipleConfigs),
815 }
816 if let Some(fragment) = y[0].as_hash() {
817 let mut ra = None;
818 #[cfg(feature = "dhcp")]
819 let mut dhcp = None;
820 #[cfg(feature = "dns")]
821 let mut dns_servers = vec![INTERFACE4, INTERFACE6];
822 #[cfg(not(feature = "dns"))]
823 let mut dns_servers = vec![];
824 let mut dns_search = vec![];
825 let mut captive_portal = None;
826 let mut addresses = None;
827 let mut listeners = None;
828 let mut dns_listeners = None;
829 #[cfg(feature = "dns")]
830 let mut dns_routes = None;
831 let mut default_listen_style = DefaultAddressType::Unspecified;
832 let mut acls = None;
833 for (k, v) in fragment {
834 match (k.as_str(), v) {
835 (Some("dhcp"), _) => return Err(Error::InvalidConfig("The dhcp section has been replaced with dhcp-policies section, please see the manpage for more details".into())),
836 #[cfg(feature = "dhcp")]
837 (Some("dhcp-policies"), d) => dhcp = crate::dhcp::config::Config::new(d)
838 .map_err(|e| e.annotate("while parsing dhcp-policies"))?,
839 #[cfg(not(feature = "dhcp"))]
840 (Some("dhcp-policies"), _) => (),
841 (Some("router-advertisements"), r) => ra = crate::radv::config::parse(r)
842 .map_err(|e| e.annotate("while parsing router-advertisements"))?,
843 (Some("dns-servers"), s) => {
844 dns_servers = parse_array("dns-servers", s, parse_string_ip)?
845 .ok_or_else(|| Error::InvalidConfig("dns-servers cannot be null".into()))?
846 }
847 (Some("dns-search"), s) => {
848 dns_search = parse_array("dns-search", s, parse_string)?
849 .ok_or_else(|| Error::InvalidConfig("dns-search cannot be null".into()))?
850 }
851 (Some("captive-portal"), s) => {
852 captive_portal = parse_string("captive-portal", s)?;
853 }
854 (Some("addresses"), s) => {
855 addresses = parse_array("addresses", s, parse_string_prefix)?;
856 }
857 (Some("api-listeners"), s) => {
858 listeners = parse_array("api-listeners", s, parse_string_sockaddr)?;
859 }
860 (Some("dhcp-listeners"), _) => {
861 return Err(Error::InvalidConfig("dhcp-listeners is deprecated, because it cannot work correclty".into()));
862 }
863 (Some("dns-listeners"), s) => {
864 dns_listeners = parse_array("dns-listeners",s, parse_string_sockaddr)?
865 .map(AddressType::Addresses);
866 }
867 (Some("default-listen-style"), s) => {
868 match s.as_str() {
869 None => return Err(Error::InvalidConfig(format!("invalid default-listen-style type: {}",
870 type_to_name(s)))),
871 Some("bind-addresses-interfaces") => default_listen_style = DefaultAddressType::Interface,
872 Some("bind-unspecified") => default_listen_style = DefaultAddressType::Unspecified,
873 Some(o) => return Err(Error::InvalidConfig(format!("invalid default-listen-style {}, expected bind-addresses-interfaces or bind-unspecified", o))),
874 }
875 },
876 (Some("acls"), s) => {
877 acls = parse_array("acls", s, crate::acl::parse_acl)?;
878 }
879 (Some("dns-routes"), s) => {
880 #[cfg(feature = "dns")] {
881 dns_routes = crate::dns::config::parse_dns_routes("dns-routes", s)?;
882 }
883 }
884 (Some(x), _) => {
885 return Err(Error::InvalidConfig(format!(
886 "Unknown configuration option {}",
887 x
888 )))
889 }
890 (None, _) => {
891 return Err(Error::InvalidConfig(format!(
892 "Config should be keyed by String, not {}",
893 type_to_name(k)
894 )))
895 }
896 }
897 }
898 let addresses = addresses.unwrap_or_default();
899 let conf = Config {
900 #[cfg(feature = "dhcp")]
901 dhcp: dhcp.unwrap_or_default(),
902 ra: ra.unwrap_or_default(),
903 dns_servers,
904 dns_search,
905 dns_listeners: dns_listeners.unwrap_or_else(|| match default_listen_style {
906 DefaultAddressType::Unspecified => AddressType::Addresses(vec![
907 std::net::SocketAddrV6::new(UNSPECIFIED6, 53, 0, 0).into(),
908 ]),
909 DefaultAddressType::Interface => AddressType::BindInterface,
910 }),
911 #[cfg(feature = "dns")]
912 dns_routes: dns_routes.unwrap_or_default(),
913 captive_portal,
914 listeners: listeners.unwrap_or_else(|| {
915 vec![
916 UnixAddr::new("/var/lib/erbium/control")
917 .unwrap()
918 .to_net_addr(),
919 ]
920 }),
921 acls: acls.unwrap_or_else(|| crate::acl::default_acls(&addresses)),
922 addresses,
923 };
924 Ok(std::sync::Arc::new(tokio::sync::RwLock::new(conf)))
925 } else {
926 Err(Error::InvalidConfig(
927 "Top level configuration should be a Hash".into(),
928 ))
929 }
930}
931
932#[cfg(test)]
933pub fn load_config_from_string_for_test(cfg: &str) -> Result<SharedConfig, Error> {
934 load_config_from_string(cfg)
935}
936
937pub async fn load_config_from_path(path: &std::path::Path) -> Result<SharedConfig, Error> {
943 let metadata = std::fs::metadata(path).map_err(Error::IoError)?;
944 let configdata = if metadata.permissions().mode() & 0o111 != 0 {
945 let output = tokio::process::Command::new(path)
946 .output()
947 .await
948 .map_err(Error::IoError)?;
949 if !output.status.success() {
950 return Err(Error::ConfigProcessFailed);
951 }
952 String::from_utf8(output.stdout).map_err(Error::Utf8Error)?
953 } else {
954 let mut contents = vec![];
955 tokio::fs::File::open(path)
956 .await
957 .map_err(Error::IoError)?
958 .read_to_end(&mut contents)
959 .await
960 .map_err(Error::IoError)?;
961
962 String::from_utf8(contents).map_err(Error::Utf8Error)?
963 };
964
965 load_config_from_string(&configdata)
966}
967
968#[test]
969fn test_config_parse() -> Result<(), Error> {
970 load_config_from_string(
971 "---
972dhcp-policies:
973 - match-interface: eth0
974 apply-dns-servers: ['8.8.8.8', '8.8.4.4']
975 apply-subnet: 192.168.0.0/24
976 apply-time-offset: 3600
977 apply-domain-name: erbium.dev
978 apply-forward: false
979 apply-mtu: 1500
980 apply-broadcast: 192.168.255.255
981 apply-rebind-time: 120
982 apply-renewal-time: 90s
983 apply-arp-timeout: 1w
984 apply-routes:
985 - prefix: 192.0.2.0/24
986 next-hop: 192.0.2.254
987
988
989 policies:
990 - { match-host-name: myhost, apply-address: 192.168.0.1 }
991
992
993 - match-interface: dmz
994 apply-dns-servers: ['8.8.8.8']
995 apply-subnet: 192.0.2.0/24
996
997 # Reserve some space from the pool for servers
998 policies:
999 - apply-range: {start: 192.0.2.10, end: 192.0.2.20}
1000
1001 # From the reserved pool, assign a static address.
1002 policies:
1003 - { match-hardware-address: 00:01:02:03:04:05, apply-address: 192.168.0.2 }
1004
1005 # Reserve space for VPN endpoints
1006 - match-user-class: VPN
1007 apply-subnet: 192.0.2.128/25
1008
1009router-advertisements:
1010 eth0:
1011",
1012 )?;
1013 Ok(())
1014}
1015
1016#[test]
1017fn test_simple_config_parse() -> Result<(), Error> {
1018 load_config_from_string(
1019 "---
1020dns-servers: [$self4, $self6]
1021dns-search: ['example.com']
1022addresses: [192.0.2.0/24, 2001:db8::/64]
1023
1024router-advertisements:
1025 eth0:
1026 lifetime: 1h
1027",
1028 )?;
1029 Ok(())
1030}
1031
1032#[test]
1033fn test_listeners_parse() -> Result<(), Error> {
1034 load_config_from_string(
1035 "---
1036dns-search: ['example.com']
1037addresses: [192.0.2.0/24, 2001:db8::/64]
1038dns-listeners: [192.0.2.0:53]
1039",
1040 )?;
1041 Ok(())
1042}
1043
1044#[test]
1045fn test_duration() {
1046 assert_eq!(
1047 parse_duration("test", &yaml::Yaml::String("5s".into())).unwrap(),
1048 Some(std::time::Duration::from_secs(5))
1049 );
1050 assert_eq!(
1051 parse_duration("test", &yaml::Yaml::String("1w2d3h4m5s".into())).unwrap(),
1052 Some(std::time::Duration::from_secs(
1053 7 * 86400 + 2 * 86400 + 3 * 3600 + 4 * 60 + 5
1054 ))
1055 );
1056}
1057
1058#[test]
1059fn test_prefix() {
1060 let p1 = "2001:db8::1".parse().unwrap();
1061 let p2 = "2001:db8::".parse().unwrap();
1062 assert_eq!(Prefix::new(p1, 64), Prefix::new(p2, 64));
1063}
1064
1065#[test]
1066fn test_prefix6_contains() {
1067 let net1 = "::ffff:192.0.2.0".parse().unwrap();
1068 let ip1: std::net::Ipv6Addr = "::ffff:192.0.2.1".parse().unwrap();
1069 let ip2: std::net::Ipv6Addr = "::ffff:192.168.0.1".parse().unwrap();
1070 assert!(Prefix6::new(net1, 120).contains(ip1));
1071 assert!(!Prefix6::new(net1, 120).contains(ip2));
1072}
1073
1074#[test]
1075fn test_cross_ip_version_contains() {
1076 let net6 = "::ffff:192.0.2.0".parse().unwrap();
1077 let net4 = "192.0.2.0".parse().unwrap();
1078 let ip6: std::net::Ipv6Addr = "::ffff:192.0.2.1".parse().unwrap();
1079 let ip4: std::net::Ipv4Addr = "192.0.2.1".parse().unwrap();
1080 let bad6: std::net::Ipv6Addr = "::ffff:10.0.0.1".parse().unwrap();
1081 let bad4: std::net::Ipv6Addr = "::ffff:10.0.0.1".parse().unwrap();
1082 assert!(Prefix6::new(net6, 120).contains(ip4));
1083 assert!(Prefix6::new(net6, 120).contains(ip6));
1084 assert!(Prefix4::new(net4, 24).contains(ip4));
1085 assert!(Prefix4::new(net4, 24).contains(ip6));
1086 assert!(!Prefix6::new(net6, 120).contains(bad4));
1087 assert!(!Prefix6::new(net6, 120).contains(bad6));
1088 assert!(!Prefix4::new(net4, 24).contains(bad4));
1089 assert!(!Prefix4::new(net4, 24).contains(bad6));
1090 assert!(Prefix::new(net6.into(), 120).contains(ip4));
1091 assert!(Prefix::new(net6.into(), 120).contains(ip6));
1092 assert!(Prefix::new(net4.into(), 24).contains(ip4));
1093 assert!(Prefix::new(net4.into(), 24).contains(ip6));
1094 assert!(!Prefix::new(net6.into(), 120).contains(bad4));
1095 assert!(!Prefix::new(net6.into(), 120).contains(bad6));
1096 assert!(!Prefix::new(net4.into(), 24).contains(bad4));
1097 assert!(!Prefix::new(net4.into(), 24).contains(bad6));
1098}