Skip to main content

erbium/
config.rs

1/*   Copyright 2024 Perry Lorier
2 *
3 *  Licensed under the Apache License, Version 2.0 (the "License");
4 *  you may not use this file except in compliance with the License.
5 *  You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 *  Unless required by applicable law or agreed to in writing, software
10 *  distributed under the License is distributed on an "AS IS" BASIS,
11 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 *  See the License for the specific language governing permissions and
13 *  limitations under the License.
14 *
15 *  SPDX-License-Identifier: Apache-2.0
16 *
17 *  Erbium Configuration parsing.
18 */
19use 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    /// Converts an ConfigValue into an Option, leaving DontSet as None.
79    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    // Converts a ConfigValue into an Option, leaving NotSpecified as None.
101    // This is useful if "don't set" has a default value that must be applied anyway.
102    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    // This return T, setting the default, and unspecified to a value.
110    // This is useful if the unspecified value has an obvious required default.
111    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(':') /* Vec<String> */
302            .map(hexbyte) /* Vec<Result<u8>> */
303            .collect()
304    })
305    .transpose()
306    .map_err(|e| Error::InvalidConfig(e.to_string()))
307}
308
309/// Parses a prefix of the form IP/prefixlen.
310/// IP can be v4 or v6.
311/// Currently no error handling on prefixlen is done.
312fn 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
343/// Parses a prefix of the form IPv4/prefixlen.
344/// Currently no error handling on prefixlen is done.
345fn 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
372/// Parses a prefix of the form IPv6/prefixlen.
373/// Currently no error handling on prefixlen is done.
374fn 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            // If this is a ::ffff:a.b.c.d address, check it against the v4 equivalent.
581            [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        /* v6 addresses don't have a "broadcast".
621         * Perhaps this should be "all nodes multicast" instead.
622         */
623        (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            // If this is a ::ffff:a.b.c.d prefix, check it against the v4 equivalent.
637            [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            // For ::ffff:a.b.c.d matches.
712            (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)]
731// Sometimes you want to only bind to interfaces that are necessary, rather than the unspecified
732// address.  This allows multiple processes to cooperate in providing a particular service on
733// differing interfaces.
734//
735// To support this, we look at the "addresses" configuration option, and only bind to addresses of
736// interfaces that match a prefix there.
737//
738// If this heuristic is incorrect, then people can override this with "dns-listeners".
739pub 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
937/* We support reading configs from a yaml file, _or_ a program (eg a shell script?) that outputs
938 * yaml on stdout.
939 *
940 * TODO: Implement reading a directory of configs.
941 */
942pub 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}