Rust ipnet库的使用

Rust
317
0
0
2024-03-25

ipnet[1]这个第三方crate提供了处理 IPv4/IPv6 相关的实用方法

使用ipnet ="2.9.0"版本

创建网络地址并打印主机掩码和网络掩码

use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

fn main() {
    // 从 Ipv4Net 和 Ipv6Net 的构造函数创建 Ipv4Net 和 Ipv6Net
    let net4 = Ipv4Net::new(Ipv4Addr::new(10, 1, 1, 0), 24).unwrap();
    let net6 = Ipv6Net::new(Ipv6Addr::new(0xfd, 0, 0, 0, 0, 0, 0, 0), 24).unwrap();

    println!("net4 is: {}", net4); // 10.1.1.0/24
    println!("net6 is: {}", net6); // fd::/24

    // 也可以从字符串表示形式创建
    let net4 = Ipv4Net::from_str("10.1.1.0/24").unwrap();
    let net6 = Ipv6Net::from_str("fd00::/24").unwrap();

    println!("net4_from_str is: {}", net4); // 10.1.1.0/24
    println!("net6_from_str is: {}", net6); // fd00::/24

    // 或如下所示
    let net4: Ipv4Net = "10.1.1.0/24".parse().unwrap();
    let net6: Ipv6Net = "fd00::/24".parse().unwrap();

    println!("net4_as_follows is: {}", net4); // 10.1.1.0/24
    println!("net6_as_follows is: {}", net6); // fd00::/24

    // IpNet 可以表示 IPv4 或 IPv6 网络地址
    let net = IpNet::from(net4);
    println!("net_1: {}", net); // 10.1.1.0/24

    // 也可以从字符串表示形式创建
    let net = IpNet::from_str("10.1.1.0/24").unwrap();
    println!("net_2: {}", net); // 10.1.1.0/24

    let net: IpNet = "10.1.1.0/24".parse().unwrap();
    // 可以使用多种方法. 完整详细信息可阅读文档
    println!("{} hostmask = {}", net, net.hostmask()); // 0.0.0.255
    println!("{} netmask = {}", net4, net4.netmask()); // 255.255.255.0
}

一个ipv4地址是10.1.1.0/24,其hostmask和netmask各为多少?

对一个ipv4地址10.1.1.0/24进行解析:

/24表示子网掩码长度为24个1,即以二进制表示子网掩码是: `11111111.11111111.11111111.00000000``

即对应的子网掩码(netmask)为:255.255.255.0

而主机掩码计算方法是: 取反后的子网掩码

即:00000000.00000000.00000000.11111111

即 对应的主机掩码(hostmask)为: 0.0.0.255

将现有 IP 网络细分为更小的子网

下面的例子 是把一个 /23 的网段,划分成多个 /25 的小网段

需要先理解为什么要做这样的操作:

将一个 /23 网段划分成多个 /25 网段意味着 要把一个较大的网络地址范围划分成几个较小的子网。每个子网有更少的可用IP地址,但能支持更多的独立网络。

  • /23 网段:表示子网掩码有23个连续的1,剩下的9位用于主机地址。这意味着这个网段有 (2^{9} = 512) 个可用IP地址。
  • /25 网段:表示子网掩码有25个连续的1,剩下的7位用于主机地址。这意味着这个网段有 (2^{7} = 128) 个可用IP地址。

举例:

假设有一个 /23 网段 192.168.0.0/23。这个网段的IP地址范围是从 192.168.0.0`` 到 192.168.1.255`,总共512个地址。

现在想把这个网段划分成几个 /25 网段。每个 /25 网段有128个地址,所以可以从192.168.0.0/23 网段中划分出4个 /25 网段,它们的地址范围如下:

  1. 第一个 /25 网段:192.168.0.0/25,范围是 192.168.0.0 - 192.168.0.127
  2. 第二个 /25 网段:192.168.0.128/25,范围是 192.168.0.128 - 192.168.0.255
  3. 第三个 /25 网段:192.168.1.0/25,范围是 192.168.1.0 - 192.168.1.127
  4. 第四个 /25 网段:192.168.1.128/25,范围是 192.168.1.128 - 192.168.1.255

每个这样的子网可以用于不同的网络,允许更细致的网络控制和管理。

use ipnet::Ipv4Net;

fn main() {
    let net: Ipv4Net = "192.168.0.0/23".parse().unwrap();

    println!("\n/25 subnets in {}:", net); // /25 subnets in 192.168.0.0/23:


    // `subnets()` 返回一个 `Result`。 如果给定的前缀长度小于现有的前缀长度,“结果”将包含错误

    let subnets = net
        .subnets(25)
        .expect("PrefixLenError: new prefix length cannot be shorter than existing");

    // Output:
    //  subnet 0 = 192.168.0.0/25
    //  subnet 1 = 192.168.0.128/25
    //  subnet 2 = 192.168.1.0/25
    //  subnet 3 = 192.168.1.128/25

    for (i, n) in subnets.enumerate() {
        println!("\tsubnet {} = {}", i, n);
    }
}

迭代两个 IPv4 地址之间的有效子网

下面的例子,是 获取两个ipv4地址10.0.0.010.0.0.239之间的有效子网

use ipnet::Ipv4Subnets;
use std::net::Ipv4Addr;

fn main() {
    let start = Ipv4Addr::new(10, 0, 0, 0);
    let end = Ipv4Addr::new(10, 0, 0, 239);

    println!("\n/0 or greater subnets between {} and {}:", start, end);

    // 输出所有子网,从适合的最大子网开始。 这将为我们提供尽可能最小的有效子网集
    //
    // Output:
    //  subnet 0 = 10.0.0.0/25
    //  subnet 1 = 10.0.0.128/26
    //  subnet 2 = 10.0.0.192/27
    //  subnet 3 = 10.0.0.224/28

    let subnets = Ipv4Subnets::new(start, end, 0);

    for (i, n) in subnets.enumerate() {
        println!("\tsubnet {} = {}", i, n);
    }

    println!("\n/26 or greater subnets between {} and {}:", start, end);

    // 输出前缀长度小于或等于26的所有子网。这会产生更多子网,但会限制它们的最大大小
    //
    // Output:
    //  subnet 0 = 10.0.0.0/26
    //  subnet 1 = 10.0.0.64/26
    //  subnet 2 = 10.0.0.128/26
    //  subnet 3 = 10.0.0.192/27
    //  subnet 4 = 10.0.0.224/28

    let subnets = Ipv4Subnets::new(start, end, 26);

    for (i, n) in subnets.enumerate() {
        println!("\tsubnet {} = {}", i, n);
    }
}

上面这段 Rust 程序的目的是找出从 10.0.0.010.0.0.239 的所有有效子网。这里的关键在于理解“有效子网”的含义,以及为什么程序从 /25 开始。

  1. 有效子网:有效子网是指能够包含起始和结束IP地址(这里是 10.0.0.010.0.0.239)的最小子网。换句话说,子网不应超过结束地址 10.0.0.239
  2. /25 开始:10.0.0.0/25 覆盖从 10.0.0.010.0.0.127 的地址。没有超出,而如果是10.0.0.0/24 则从 10.0.0.010.0.0.255,超过了10.0.0.239

该程序通过列出不同大小的子网来实现这一点,从 /25 开始,因为 /24 或更大的子网(比如 /23/22 等)将包括比 10.0.0.239 更高的地址。

程序的输出显示了从 10.0.0.0/25 开始到能够覆盖 10.0.0.239 为止的所有子网。这包括 /25, /26, /27, /28 的子网,因为每一个都是在尝试找到一个更小的子网来覆盖这个范围。

10.0.0.128/26的范围是从哪到哪?

10.0.0.128/26对应的子网地址范围如下:

  • 子网掩码(netmask): 255.255.255.192
  • 或者子网掩码长度: 26
  • 网络地址: 10.0.0.128
  • 首个主机地址: 10.0.0.129
  • 最后一个主机地址: 10.0.0.190
  • 广播地址: 10.0.0.191

所以: 10.0.0.128/26的ip地址范围是:

从10.0.0.129到10.0.0.190

计算方法:

  • 网络地址 = 地址与子网掩码与运算结果
  • 首个主机地址 = 网络地址 + 1
  • 最后一个主机地址 = 网络地址 | 不与子网掩码匹配的位数全部为1
  • 广播地址 = 最后一个主机地址 + 1

所以对于10.0.0.128/26来说,它划分出的地址范围就是10.0.0.129到10.0.0.190这63个可用的主机地址。

10.0.0.192/27的范围是从哪到哪?

10.0.0.192/27对应的子网范围如下:

  • 子网掩码(netmask): 255.255.255.224
  • 或者子网掩码长度: 27
  • 网络地址: 10.0.0.192
  • 首个主机地址: 10.0.0.193
  • 最后一个主机地址: 10.0.0.198
  • 广播地址: 10.0.0.199

所以: 10.0.0.192/27的IP地址范围是:

从10.0.0.193到10.0.0.198

计算方法同10.0.0.128/26一致:

  • 网络地址 = 地址与子网掩码与运算结果
  • 首个主机地址 = 网络地址 + 1
  • 最后一个主机地址 = 网络地址 | 不与子网掩码匹配的位数全部为1
  • 广播地址 = 最后一个主机地址 + 1

因此,10.0.0.192/27对应的地址范围就是从10.0.0.193开始,到10.0.0.198结束,共计6个可用主机IP地址。

10.0.0.224/28的范围是从哪到哪?

10.0.0.224/28对应的子网范围是:

  • 子网掩码(netmask): 255.255.255.240
  • 或者子网掩码长度: 28
  • 网络地址: 10.0.0.224
  • 首个主机地址: 10.0.0.225
  • 最后一个主机地址: 10.0.0.230
  • 广播地址: 10.0.0.231

所以,10.0.0.224/28的IP地址范围是:

从10.0.0.225到10.0.0.230

计算方法同前两例:

  • 网络地址 = 地址与子网掩码与运算结果
  • 首个主机地址 = 网络地址 + 1
  • 最后一个主机地址 = 网络地址 | 不与子网掩码匹配的位数全部为1
  • 广播地址 = 最后一个主机地址 + 1

因此,10.0.0.224/28对应的子网范围就是从10.0.0.225开始,到10.0.0.230结束,共计6个主机IP地址。

总结来说,给出一个子网地址和掩码长度,就可以通过简单计算得出其对应的有效IP地址范围。

聚合(汇总) (包含重叠和相邻前缀的)IP 前缀列表

use ipnet::IpNet;

fn main() {
    // Example input list of overlapping and adjacent prefixes.
    // (重叠和相邻前缀的示例输入列表)

    let strings = vec![
        "10.0.0.0/24",
        "10.0.1.0/24",
        "10.0.1.1/24",
        "10.0.1.2/24",
        "10.0.2.0/24",
        "10.1.0.0/24",
        "10.1.1.0/24",
        "192.168.0.0/24",
        "192.168.1.0/24",
        "192.168.2.0/24",
        "192.168.3.0/24",
        "fd00::/32",
        "fd00:1::/32",
    ];

    let nets: Vec<IpNet> = strings.iter().filter_map(|p| p.parse().ok()).collect();

    println!("\nAggregated IP prefixes:");

    // Output:
    //  10.0.0.0/23
    //  10.0.2.0/24
    //  10.1.0.0/23
    //  192.168.0.0/22
    //  fd00::/31

    for n in IpNet::aggregate(&nets) {
        println!("\t{}", n);
    }
}

以上 Rust 代码演示了如何对一组 IPv4 和 IPv6 地址前缀进行聚合。聚合的目的是简化和优化 IP 地址的表示,通过将重叠和相邻的网络前缀合并成更大的单个网络前缀来减少总数。这对于路由表的优化特别有用。

其中,

  1. 输入字符串列表:
  • let strings = vec![...]: 定义一个包含多个 CIDR 表示法的 IP 网络前缀的字符串向量。这些前缀可能重叠或相邻。
  1. 字符串解析为 IpNet 对象:
  • let nets: Vec<IpNet> = strings.iter().filter_map(|p| p.parse().ok()).collect();: 这行代码遍历字符串向量,尝试将每个字符串解析为 IpNet 对象。filter_map 结合 parse().ok() 用于过滤出有效的解析结果并收集它们到一个新的 IpNet 对象向量中。
  1. 聚合 IP 前缀:
  • IpNet::aggregate(&nets): 这个方法调用将输入的 IpNet 对象向量进行聚合。聚合的结果是一个新的 IpNet 向量,其中包含了优化后的、更少数量的网络前缀。
  1. 打印聚合后的结果:
  • for n in IpNet::aggregate(&nets) { println!("\t{}", n); }: 这段代码遍历聚合后的 IpNet 对象并打印它们。这些打印出来的网络前缀是原始输入的优化版本,包含了最少量的不重叠且不相邻的网络前缀。

对于输出的结果:

  • 输出显示了聚合过程的结果,其中合并了重叠和相邻的前缀。例如,10.0.0.0/2410.0.1.0/24 被聚合成 10.0.0.0/23,因为它们是相邻的前缀,可以用一个更大的前缀来表示。
  • 对于 IPv6 地址,fd00::/32fd00:1::/32 被聚合成 fd00::/31

通过这种方式,代码有效地减少了所需处理的网络前缀的数量,这在管理大型网络时尤其有用。

参考资料

[1] ipnet: https://crates.io/crates/ipnet