9 releases

0.1.1 Mar 6, 2024
0.1.0 Feb 2, 2024
0.1.0-rc.4 Aug 7, 2023
0.1.0-rc.2 Apr 27, 2023
0.1.0-alpha.2 Apr 13, 2022

#875 in Network programming

45 downloads per month
Used in 4 crates

MIT license

10MB
6.5K SLoC

generic-ip-rs

Crates.io CI/CD codecov docs.rs

About

A replacement for the stdlib IP address types intended to provide easier generic abstraction over IP address family.


lib.rs:

Types and traits for working with IP addresses and prefixes generically over address families.

The IP address types in std::net do not share any common trait that expresses "this thing is an IP address".

This limitation makes writing code that deals with IP addresses in an address-family-independent way unnecessarily difficult.

This crate provides a collection of types that seek to be compatible with the address types from std::net and prefix types from the popular ipnet crate, but which are generic over address-families.

For example:

use ip::{Address, Afi, Error, Ipv4, Ipv6, Prefix};

struct RibEntry<A: Afi> {
    prefix: Prefix<A>,
    next_hop: Address<A>,
}

impl<A: Afi> RibEntry<A> {
    fn get_next_hop(&self, addr: Address<A>) -> Option<Address<A>> {
        (self.prefix >= addr).then(|| self.next_hop)
    }
}

fn main() -> Result<(), Error> {
    let v4: RibEntry<Ipv4> = RibEntry {
        prefix: "192.0.2.0/24".parse()?,
        next_hop: "198.51.100.1".parse()?,
    };

    let v6: RibEntry<Ipv6> = RibEntry {
        prefix: "2001:db8::/48".parse()?,
        next_hop: "2001:db8:f00::1".parse()?,
    };

    assert_eq!(
        v4.get_next_hop("192.0.2.127".parse()?),
        Some("198.51.100.1".parse()?)
    );
    assert_eq!(v6.get_next_hop("2001:db8:ffff::ffff".parse()?), None);

    Ok(())
}

Orientation

Names such as Address, Interface, Prefix or Afi are re-used in various different modules within the crate. For example Address is used to name:

This can make understanding which item a given name is referring to difficult without understanding the crate layout.

Address-families

The IP address-families ipv4 and ipv6 are represented in the type system by the zero-sized types concrete::Ipv4 and concrete::Ipv6.

These "concrete" address-families implement traits::Afi, which in turn bounds the generic parameter of the items exported by the concrete module, such as concrete::Address<A> and concrete::Prefix<A>.

Conversely, the [any] module exports a collection of enums with variants corresponding to the two concrete address families, with each variant containing the corresponding concrete::* item.

Address-family classes

Usually a given use-case will call for either processing objects of a single known (at compile time) address-family or objects that may be of either address-family, as in the following:

use ip::{any, concrete, Afi, Ipv4, Ipv6};

// `x` and `y` must be the same address-family
fn longer_concrete<A: Afi>(
    x: concrete::Prefix<A>,
    y: concrete::Prefix<A>,
) -> concrete::Prefix<A> {
    if x.length() > y.length() {
        x
    } else {
        y
    }
}

// `x` and `y` may be of different address families, so may not be
// comparable
fn longer_any(x: any::Prefix, y: any::Prefix) -> Option<any::Prefix> {
    match (x, y) {
        (any::Prefix::Ipv4(x), any::Prefix::Ipv4(y)) => Some(longer_concrete(x, y).into()),
        (any::Prefix::Ipv6(x), any::Prefix::Ipv6(y)) => Some(longer_concrete(x, y).into()),
        _ => None,
    }
}

let x4: concrete::Prefix<Ipv4> = "192.0.2.0/24".parse().unwrap();
let y4: concrete::Prefix<Ipv4> = "203.0.113.128/25".parse().unwrap();

let x6: concrete::Prefix<Ipv6> = "2001:db8:f00::/48".parse().unwrap();
let y6: concrete::Prefix<Ipv6> = "2001:db8::/32".parse().unwrap();

assert_eq!(longer_concrete(x4, y4), y4);
assert_eq!(longer_concrete(x6, y6), x6);

assert_eq!(longer_any(x4.into(), y4.into()), Some(y4.into()));
assert_eq!(longer_any(x4.into(), y6.into()), None);

Occassionally, however, one may need a data structure that may sometimes contain a mix of address-families, but at other times must contain only a single address-family.

To deal with such a requirement, traits::AfiClass provides further generalisation to avoid choosing between items from [any] or concrete, by defining a type-level mapping from an "address-family class" to its associated type for Address, Prefix, etc.

AfiClass is implemented for each of Ipv4 and Ipv6. In this context Ipv4/Ipv6 can be conceptually considered to be the singleton classes of address-families { ipv4 } and { ipv6 }.

Additionally, the any::Any type implements AfiClass, providing type-level mappings to the items of the [any] module. [Any] can be thought of as the class { ipv4, ipv6 }.

Various type aliases are defined at the crate root to provide easy access to this mapping. In general, it is easier and clearer to use Address<Ipv4> or Address<Any> than concrete::Address<Ipv4> or any::Address.

Example

use ip::{Address, Afi, AfiClass, Any, Ipv4};

#[derive(Debug, PartialEq)]
struct Foo<A: AfiClass> {
    addr: Address<A>,
}

impl<A: AfiClass> Foo<A> {
    fn new(addr: Address<A>) -> Self {
        Self { addr }
    }

    fn into_concrete<C>(self) -> Option<Foo<C>>
    where
        C: Afi,
        Address<C>: TryFrom<Address<A>>,
    {
        self.addr.try_into().map(Foo::new).ok()
    }
}

let anys: Vec<Foo<Any>> = vec![
    Foo {
        addr: Address::<Any>::Ipv4("192.0.2.1".parse().unwrap()),
    },
    Foo {
        addr: Address::<Any>::Ipv6("2001:db8::1".parse().unwrap()),
    },
    Foo {
        addr: Address::<Any>::Ipv4("198.51.100.1".parse().unwrap()),
    },
];

let filtered: Vec<Foo<Ipv4>> = vec![
    Foo {
        addr: "192.0.2.1".parse().unwrap(),
    },
    Foo {
        addr: "198.51.100.1".parse().unwrap(),
    },
];

assert_eq!(
    anys.into_iter()
        .filter_map(Foo::into_concrete)
        .collect::<Vec<Foo<Ipv4>>>(),
    filtered
);

Dependencies