blob: 70db2ebb469253752a1cffc97270a4797431eabc [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use std::net::{Ipv4Addr, Ipv6Addr};
use netext::IsLocalAddr;
use nix::net::if_::if_nametoindex;
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// IP address with support for IPv6 scope identifiers as defined in RFC 4007.
#[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum IpAddr {
/// An IPv4 address.
V4(Ipv4Addr),
/// An IPv6 address with optional scope identifier.
V6(Ipv6Addr, Option<u32>),
}
impl Into<std::net::IpAddr> for IpAddr {
fn into(self) -> std::net::IpAddr {
match self {
IpAddr::V4(ip) => std::net::IpAddr::from(ip),
IpAddr::V6(ip, _) => std::net::IpAddr::from(ip),
}
}
}
impl From<Ipv6Addr> for IpAddr {
fn from(value: Ipv6Addr) -> Self {
IpAddr::V6(value, None)
}
}
impl From<Ipv4Addr> for IpAddr {
fn from(value: Ipv4Addr) -> Self {
IpAddr::V4(value)
}
}
impl From<std::net::IpAddr> for IpAddr {
fn from(value: std::net::IpAddr) -> Self {
match value {
std::net::IpAddr::V4(ip) => IpAddr::from(ip),
std::net::IpAddr::V6(ip) => IpAddr::from(ip),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
/// An error which can be returned when parsing an IP address with optional IPv6
/// scope ID. See [`std::net::AddrParseError`].
pub enum AddrParseError {
#[error(transparent)]
IpInvalid(#[from] std::net::AddrParseError),
#[error("no interface found with name \"{0}\"")]
InterfaceNotFound(String),
#[error("only IPv6 link-local may include a scope ID")]
/// Scope IDs are only supported for IPv6 link-local addresses as per RFC
/// 6874 Section 4.
ScopeNotSupported,
}
impl std::str::FromStr for IpAddr {
type Err = AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.splitn(2, '%');
let addr = parts.next().unwrap(); // first element is guaranteed
let ip = std::net::IpAddr::from_str(addr)?;
let scope = parts.next();
match (ip, scope) {
(std::net::IpAddr::V4(ip), None) => Ok(IpAddr::from(ip)),
(std::net::IpAddr::V4(_), Some(_)) => Err(AddrParseError::ScopeNotSupported),
(std::net::IpAddr::V6(ip), None) => Ok(IpAddr::V6(ip, None)),
(std::net::IpAddr::V6(ip), Some(scope)) => {
if !ip.is_link_local_addr() {
return Err(AddrParseError::ScopeNotSupported);
}
if let Ok(index) = scope.parse::<u32>() {
return Ok(IpAddr::V6(ip, Some(index)));
}
match if_nametoindex(scope) {
Ok(index) => Ok(IpAddr::V6(ip, Some(index))),
Err(_) => Err(AddrParseError::InterfaceNotFound(scope.to_string())),
}
}
}
}
}
impl Display for IpAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpAddr::V4(ip) => Display::fmt(ip, f),
IpAddr::V6(ip, None) => Display::fmt(ip, f),
IpAddr::V6(ip, Some(scope)) => {
Display::fmt(ip, f)?;
write!(f, "%{}", scope)
}
}
}
}
impl Debug for IpAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl Serialize for IpAddr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> Deserialize<'de> for IpAddr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(FromStrVisitor::new())
}
}
struct FromStrVisitor<T> {
ty: PhantomData<T>,
}
impl<T> FromStrVisitor<T> {
fn new() -> Self {
FromStrVisitor { ty: PhantomData }
}
}
impl<'de, T> serde::de::Visitor<'de> for FromStrVisitor<T>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
type Value = T;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("IP address")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
s.parse().map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod test {
use super::{AddrParseError, IpAddr};
use assert_matches::assert_matches;
#[test]
fn parse_ip_invalid() {
assert_matches!("".parse::<IpAddr>(), Err(AddrParseError::IpInvalid(_)));
assert_matches!("192.168.1.".parse::<IpAddr>(), Err(AddrParseError::IpInvalid(_)));
assert_matches!("fe80:".parse::<IpAddr>(), Err(AddrParseError::IpInvalid(_)));
}
#[test]
fn parse_ipv4() {
assert_matches!(
"192.168.1.1".parse::<IpAddr>(),
Ok(IpAddr::V4(ip))
if ip == "192.168.1.1".parse::<std::net::Ipv4Addr>().unwrap()
);
}
#[test]
fn parse_ipv4_with_scope() {
assert_matches!(
"192.168.1.1%1".parse::<IpAddr>(),
Err(AddrParseError::ScopeNotSupported)
);
}
#[test]
fn parse_ipv6() {
assert_matches!(
"fe80::1".parse::<IpAddr>(),
Ok(IpAddr::V6(ip, None))
if ip == "fe80::1".parse::<std::net::Ipv6Addr>().unwrap()
);
}
#[test]
fn parse_ipv6_global_with_scope() {
assert_matches!("2001::1%1".parse::<IpAddr>(), Err(AddrParseError::ScopeNotSupported));
}
#[test]
fn parse_ipv6_link_local_with_scope() {
assert_matches!(
"fe80::1%1".parse::<IpAddr>(),
Ok(IpAddr::V6(ip, Some(scope)))
if ip == "fe80::1".parse::<std::net::Ipv6Addr>().unwrap()
&& scope == 1
);
}
#[test]
fn parse_ipv6_link_local_with_scope_interface_not_found() {
// An empty scope ID should trigger a failed lookup.
assert_matches!(
"fe80::1%".parse::<IpAddr>(),
Err(AddrParseError::InterfaceNotFound(name))
if name == ""
);
// The trailing '%' forces a failed lookup. At the time of writing, no
// OS supports this character as part of interface names.
assert_matches!(
"fe80::1%eth0%".parse::<IpAddr>(),
Err(AddrParseError::InterfaceNotFound(name))
if name == "eth0%"
);
}
}