krisprice / ipnet

IpNet, Ipv4Net, and Ipv6Net types and methods for Rust
Apache License 2.0
122 stars 26 forks source link

Iterator for supernets. #23

Open jcgruenhage opened 4 years ago

jcgruenhage commented 4 years ago

It'd be useful if it was possible to iterate over the supernets of a net. This can already be done with something like

let net = "10.0.0.0/8".parse().unwrap();
let mut tmp_net = net.clone();
while let Some(supernet) = tmp_net.supernet() {
  tmp_net = supernet;
  // some code here
}

Doing this here instead would obv be a lot cleaner:

let net = "10.0.0.0/8".parse().unwrap();
for supernet in net.supernets() {
  // some code here
}

I've started implementing this, but wanted to post an issue before I sink more than a few minutes into it, to make sure this is something that would get merged if implemented.

krisprice commented 4 years ago

Hi @jcgruenhage -- out of curiosity how are you using that?

I'm happy to include it if you want to keep implementing it. 👍

jcgruenhage commented 4 years ago

I'm not using that myself, but my flatmate is. The usecase is a list of CIDRs, where some have a flag set and some don't, and the goal is to find the number of IPs that have (or don't have? not sure right now) that bit set.

So, for counting, you check the bigger nets first, but if a smaller net is included in a bigger net, you need to change the count. For that to work, you need to look up whether a supernet is included in that list, so you need to iterate over the supernets. Right now, their code is doing while let for that, but an iterator would be a lot nicer.

For the actual counting of IPs in an IpNet, #24 is needed, so to reduce their code further, that'd also be needed.

They also likely need an IpRange to Iterator<Item = IpNet> method, which they've also written, so that'd be nice to upstream too once it's gotten a few more tests. The python stdlib has such a method included (which is quite impressive, the python stdlib really has everything).

carneeki commented 4 years ago

+1! My use case is to find a set of "parent" CIDR networks for a given network (v4 / v6) up to a desired minimum prefix length and return an iterator. Here's a minimum working example that will supernet 10.224.195.200/29 up to /4.

Happy to fork + send a PR, it would be my first upstream commit for a Rust project!

use ipnet::IpNet;
use std::cmp::Ordering::{Equal, Greater};

struct Supernets {
    network: IpNet,
    min_prefix_len: u8,
}

impl Supernets {
    fn new(network: IpNet, min_prefix_len: u8) -> Self {
        Supernets {
            network: network,
            min_prefix_len: min_prefix_len,
        }
    }
}

impl Iterator for Supernets {
    type Item = IpNet;

    fn next(&mut self) -> Option<Self::Item> {
        match self.network.prefix_len().partial_cmp(&self.min_prefix_len) {
            Some(Greater) => {
                // calc next, return current
                let current = self.network;
                self.network = current.supernet().unwrap();

                Some(current)
            }

            Some(Equal) => {
                // calc next, return current
                let current = self.network;
                self.network = current.supernet().unwrap();

                Some(current)
            }
            _ => {
                // handle end condition
                None
            }
        }
    }
}

fn main() {
    let start_net = "10.224.195.200/29".parse::<IpNet>().unwrap();

    let supernets = Supernets::new(start_net, 4);

    for net in supernets {
        println!("{:?}", net);
    }
}

Produces the following:

10.224.195.200/29
10.224.195.192/28
10.224.195.192/27
10.224.195.192/26
10.224.195.128/25
10.224.195.0/24
10.224.194.0/23
10.224.192.0/22
10.224.192.0/21
10.224.192.0/20
10.224.192.0/19
10.224.192.0/18
10.224.128.0/17
10.224.0.0/16
10.224.0.0/15
10.224.0.0/14
10.224.0.0/13
10.224.0.0/12
10.224.0.0/11
10.192.0.0/10
10.128.0.0/9
10.0.0.0/8
10.0.0.0/7
8.0.0.0/6
8.0.0.0/5
0.0.0.0/4

For ipv6:

fn main() {
    let start_net = "2406:3600:25b:c500::/56".parse::<IpNet>().unwrap();

    let supernets = Supernets::new(start_net, 48);

    for net in supernets {
        println!("{:?}", net);
    }
}

Output:

2406:3600:25b:c500::/56
2406:3600:25b:c400::/55
2406:3600:25b:c400::/54
2406:3600:25b:c000::/53
2406:3600:25b:c000::/52
2406:3600:25b:c000::/51
2406:3600:25b:c000::/50
2406:3600:25b:8000::/49
2406:3600:25b::/48
jcgruenhage commented 4 years ago

That code looks good to me, except for the one match where you have branches with the same content. Maybe you should put Some(Less) first, handle the end case there, and then handle the other two as one in the _ case.

carneeki commented 4 years ago

That code looks good to me, except for the one match where you have branches with the same content. Maybe you should put Some(Less) first, handle the end case there, and then handle the other two as one in the _ case.

Hi Jan,

Nice catch. I've sent a PR with your recommendation in it.