use std::{io, net::Ipv6Addr};
use crate::{IfName, IfNameError};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("netlink error: {0}")]
Netlink(String),
#[error("unexpected kernel reply: {0}")]
UnexpectedReply(String),
#[error("genetlink family resolution failed: {0}")]
FamilyResolution(String),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("invalid interface name: {0}")]
InvalidIfName(#[from] IfNameError),
#[error("invalid cookie length {0}: must be 0, 4, or 8")]
InvalidCookieLength(usize),
#[error(
"address family mismatch: local and remote must be the same IP version"
)]
AddressFamilyMismatch,
#[error("encapsulation mismatch: operation does not apply to this socket")]
EncapsulationMismatch,
#[error("link-local IPv6 address {0} requires an interface scope")]
ScopeRequired(Ipv6Addr),
#[error("interface scope mismatch: {expected} and {found}")]
ScopeMismatch {
expected: IfName,
found: IfName,
},
#[error(
"unmanaged tunnels cannot use link-local IPv6 endpoints; use a \
managed TunnelSocket with an interface scope"
)]
UnmanagedLinkLocal,
#[error(
"operation requires a managed socket (tunnel was created without \
L2TP_ATTR_FD)"
)]
UnmanagedSocket,
#[error(
"disabling UDP checksums for an IPv4-mapped peer requires a managed \
socket; use IPv4 endpoints or the managed tunnel API"
)]
UnmanagedUdpChecksum,
#[error("kernel error {code}: {message}")]
KernelError {
code: i32,
message: String,
},
}
impl Error {
pub fn raw_os_error(&self) -> Option<i32> {
match self {
Self::KernelError { code, .. } => Some(*code),
Self::Io(err) => err.raw_os_error(),
_ => None,
}
}
pub fn kind(&self) -> Option<io::ErrorKind> {
match self {
Self::KernelError { code, .. } => {
Some(io::Error::from_raw_os_error(*code).kind())
}
Self::Io(err) => Some(err.kind()),
_ => None,
}
}
pub(crate) fn netlink(err: netlink_packet_core::DecodeError) -> Self {
Self::Netlink(err.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use std::io;
use super::{Error, IfName, Ipv6Addr};
#[test]
fn kernel_error_exposes_os_code_and_kind() {
let err = Error::KernelError {
code: libc::EEXIST,
message: "exists".to_string(),
};
assert_eq!(err.raw_os_error(), Some(libc::EEXIST));
assert_eq!(err.kind(), Some(io::ErrorKind::AlreadyExists));
assert_eq!(Error::AddressFamilyMismatch.kind(), None);
assert_eq!(Error::UnmanagedLinkLocal.raw_os_error(), None);
}
#[test]
fn scope_errors_name_the_interfaces() {
let err = Error::ScopeMismatch {
expected: IfName::new("eth0").unwrap(),
found: IfName::new("eth1").unwrap(),
};
assert_eq!(err.to_string(), "interface scope mismatch: eth0 and eth1");
let err =
Error::ScopeRequired(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
assert!(err.to_string().contains("fe80::1"));
}
#[test]
fn netlink_decode_error_keeps_message() {
let err =
Error::netlink(netlink_packet_core::DecodeError::from("truncated"));
assert_eq!(err.to_string(), "netlink error: truncated");
}
}