hyveos_ifaddr/
lib.rs

1use std::{
2    borrow::Cow,
3    fmt::Display,
4    io,
5    net::{Ipv6Addr, SocketAddr, SocketAddrV6},
6    str::FromStr,
7    sync::Arc,
8};
9
10use multiaddr::{Multiaddr, Protocol};
11
12pub fn if_name_to_index(name: impl Into<Vec<u8>>) -> io::Result<u32> {
13    let ifname = std::ffi::CString::new(name)?;
14    match unsafe { libc::if_nametoindex(ifname.as_ptr()) } {
15        0 => Err(io::Error::last_os_error()),
16        otherwise => Ok(otherwise),
17    }
18}
19
20pub fn if_index_to_name(name: u32) -> io::Result<String> {
21    let mut buffer = [0; libc::IF_NAMESIZE];
22    let maybe_name = unsafe { libc::if_indextoname(name, buffer.as_mut_ptr()) };
23    if maybe_name.is_null() {
24        Err(io::Error::last_os_error())
25    } else {
26        let cstr = unsafe { std::ffi::CStr::from_ptr(maybe_name) };
27        Ok(cstr.to_string_lossy().into_owned())
28    }
29}
30
31#[derive(Debug, thiserror::Error)]
32pub enum Error {
33    #[error("Empty string")]
34    EmptyString,
35    #[error("Missing scope ID")]
36    MissingScopeId,
37    #[error("Invalid ip address: {0}")]
38    InvalidIp(#[from] std::net::AddrParseError),
39    #[error("IO error: {0}")]
40    IO(#[from] io::Error),
41    #[error("Invalid multiaddr")]
42    InvalidMultiaddr,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct IfAddr {
47    pub if_index: u32,
48    pub if_name: Arc<str>,
49    pub addr: Ipv6Addr,
50}
51
52impl IfAddr {
53    pub fn new_with_index(addr: Ipv6Addr, if_index: u32) -> io::Result<Self> {
54        Ok(Self {
55            if_index,
56            if_name: if_index_to_name(if_index)?.into(),
57            addr,
58        })
59    }
60
61    pub fn new_with_name(addr: Ipv6Addr, if_name: impl AsRef<str>) -> io::Result<Self> {
62        let if_name = if_name.as_ref();
63
64        Ok(Self {
65            if_index: if_name_to_index(if_name)?,
66            if_name: if_name.into(),
67            addr,
68        })
69    }
70
71    pub fn with_port(&self, port: u16) -> io::Result<SocketAddr> {
72        Ok(SocketAddrV6::new(self.addr, port, 0, self.if_index).into())
73    }
74
75    pub fn to_multiaddr(&self, name: bool) -> Multiaddr {
76        let zone_str = if name {
77            Cow::Borrowed(self.if_name.as_ref())
78        } else {
79            Cow::from(self.if_index.to_string())
80        };
81
82        Multiaddr::empty()
83            .with(Protocol::Ip6(self.addr))
84            .with(Protocol::Ip6zone(zone_str))
85    }
86}
87
88impl Display for IfAddr {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "{}%{}", self.addr, self.if_name)
91    }
92}
93
94impl FromStr for IfAddr {
95    type Err = Error;
96
97    fn from_str(s: &str) -> Result<Self, Error> {
98        let mut parts = s.splitn(2, '%');
99        let addr = parts.next().ok_or(Error::EmptyString)?;
100        let zone_str = parts.next().ok_or(Error::MissingScopeId)?;
101
102        let addr = Ipv6Addr::from_str(addr).map_err(Error::from)?;
103
104        if let Ok(index) = zone_str.parse() {
105            Self::new_with_index(addr, index)
106        } else {
107            Self::new_with_name(addr, zone_str)
108        }
109        .map_err(Into::into)
110    }
111}
112
113impl TryFrom<&Multiaddr> for IfAddr {
114    type Error = Error;
115
116    fn try_from(multiaddr: &Multiaddr) -> Result<Self, Self::Error> {
117        let mut iter = multiaddr.iter();
118
119        let Some(Protocol::Ip6(addr)) = iter.next() else {
120            return Err(Error::InvalidMultiaddr);
121        };
122
123        let Some(Protocol::Ip6zone(zone_str)) = iter.next() else {
124            return Err(Error::InvalidMultiaddr);
125        };
126
127        if let Ok(index) = zone_str.parse() {
128            Self::new_with_index(addr, index)
129        } else {
130            Self::new_with_name(addr, zone_str)
131        }
132        .map_err(Into::into)
133    }
134}
135
136impl TryFrom<Multiaddr> for IfAddr {
137    type Error = Error;
138
139    fn try_from(multiaddr: Multiaddr) -> Result<Self, Self::Error> {
140        IfAddr::try_from(&multiaddr)
141    }
142}