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}