Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ripcalc for openSUSE:Factory checked in at 2025-01-05 15:29:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ripcalc (Old) and /work/SRC/openSUSE:Factory/.ripcalc.new.1881 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ripcalc" Sun Jan 5 15:29:19 2025 rev:2 rq:1234641 version:0.1.13 Changes: -------- --- /work/SRC/openSUSE:Factory/ripcalc/ripcalc.changes 2024-08-02 17:27:40.262130058 +0200 +++ /work/SRC/openSUSE:Factory/.ripcalc.new.1881/ripcalc.changes 2025-01-05 15:29:30.145433822 +0100 @@ -1,0 +2,8 @@ +Thu Jan 2 20:08:16 UTC 2025 - Andreas Stieger <andreas.stie...@gmx.de> + +- Update to version 0.1.13 + * don't require input to be one IP per line + * show subnets within network in format + * limit how much an encapsulating network can grow with --group + +------------------------------------------------------------------- Old: ---- ripcalc-0.1.12.obscpio New: ---- ripcalc-0.1.13.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ripcalc.spec ++++++ --- /var/tmp/diff_new_pack.3eVwlQ/_old 2025-01-05 15:29:31.045470841 +0100 +++ /var/tmp/diff_new_pack.3eVwlQ/_new 2025-01-05 15:29:31.045470841 +0100 @@ -1,7 +1,7 @@ # # spec file for package ripcalc # -# Copyright (c) 2024 Andreas Stieger <andreas.stie...@gmx.de> +# Copyright (c) 2025 Andreas Stieger <andreas.stie...@gmx.de> # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: ripcalc -Version: 0.1.12 +Version: 0.1.13 Release: 0 Summary: Tool for network addresses License: GPL-3.0-or-later @@ -35,6 +35,9 @@ %prep %autosetup -p1 -a1 +%if 0%{?suse_version} < 1600 +find Cargo.lock vendor/ -type f -name Cargo.lock -exec sed -Ei 's/^version = 4$/version = 3/g' {} \; +%endif %build %{cargo_build} ++++++ _service ++++++ --- /var/tmp/diff_new_pack.3eVwlQ/_old 2025-01-05 15:29:31.081472322 +0100 +++ /var/tmp/diff_new_pack.3eVwlQ/_new 2025-01-05 15:29:31.085472486 +0100 @@ -5,7 +5,7 @@ <param name="changesgenerate">enable</param> <param name="filename">ripcalc</param> <param name="versionformat">@PARENT_TAG@</param> - <param name="revision">v0.1.12</param> + <param name="revision">v0.1.13</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="versionrewrite-replacement">\1</param> </service> @@ -20,8 +20,5 @@ <param name="compression">zst</param> <param name="update">true</param> </service> - <service name="cargo_audit" mode="manual"> - <param name="srcdir">projectname</param> - </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.3eVwlQ/_old 2025-01-05 15:29:31.105473309 +0100 +++ /var/tmp/diff_new_pack.3eVwlQ/_new 2025-01-05 15:29:31.109473474 +0100 @@ -1,7 +1,8 @@ <servicedata> <service name="tar_scm"> <param name="url">https://gitlab.com/edneville/ripcalc.git</param> - <param name="changesrevision">5aca7acaf7fc5e6d53eb737e620fe418cf76bfdb</param> + <param name="changesrevision">d1e29a5ea08ffcb525d423dcbe51e26436d5f851</param> </service> </servicedata> +(No newline at EOF) ++++++ ripcalc-0.1.12.obscpio -> ripcalc-0.1.13.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/Cargo.lock new/ripcalc-0.1.13/Cargo.lock --- old/ripcalc-0.1.12/Cargo.lock 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/Cargo.lock 2025-01-01 19:06:50.000000000 +0100 @@ -116,7 +116,7 @@ [[package]] name = "ripcalc" -version = "0.1.12" +version = "0.1.13" dependencies = [ "csv", "getopts", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/Cargo.toml new/ripcalc-0.1.13/Cargo.toml --- old/ripcalc-0.1.12/Cargo.toml 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/Cargo.toml 2025-01-01 19:06:50.000000000 +0100 @@ -1,7 +1,7 @@ [package] name = "ripcalc" description = "ripcalc, format and lookup IP addresses" -version = "0.1.12" +version = "0.1.13" authors = ["ed neville <e...@s5h.net>"] edition = "2018" license = "GPL-3.0-or-later" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/Makefile new/ripcalc-0.1.13/Makefile --- old/ripcalc-0.1.12/Makefile 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/Makefile 2025-01-01 19:06:50.000000000 +0100 @@ -49,6 +49,11 @@ printf "127.0.0.1/8\n" | $(RELEASE) | grep "IP is: 127.0.0.1/8" | wc -l | tr -d '[:blank:]' | grep -Fx 1 printf " 127.0.0.1/8 \n " | $(RELEASE) | grep "IP is: 127.0.0.1/8" | wc -l | tr -d '[:blank:]' | grep -Fx 1 $(RELEASE) --base 10 -6 55835323703435061617372717077650323870 | grep "IP is: 2a01:7e00::f03c:92ff:fe35:b99e/64" | wc -l | tr -d '[:blank:]' | grep -Fx 1 + printf '10.0.1.0 10.0.255.0' | $(RELEASE) -e --format cidr | grep "10.0.0.0/16" | wc -l | tr -d '[:blank:]' | grep -Fx 1 + printf '10.0.1.0 10.0.255.0\n10.2.0.0 10.2.2.2\n10.3.0.0\n10.10.10.10\n' | $(RELEASE) --format cidr | wc -l | tr -d '[:blank:]' | grep -Fx 6 + printf '2a0a:1100:1002::/48' | $(RELEASE) --networks 64 | tr -d '[:blank:]' | grep -Fx "Networks(64):65536" + printf '2a0a:1100:1002::/48' | $(RELEASE) --networks 64 --format '%D:%N' | grep -Fx '64:65536' + for i in 1 2 3 4; do for j in 1 2 3 4; do echo 192.$$i.$$j.1; done; done | $(RELEASE) --group 16 --format short --encapsulating | wc -l | tr -d '[:blank:]' | grep -Fx 4 install: all command -v please && please install -m 0755 -s $(RELEASE) /usr/local/bin || sudo install -m 0755 -s $(RELEASE) /usr/local/bin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/README.md new/ripcalc-0.1.13/README.md --- old/ripcalc-0.1.12/README.md 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/README.md 2025-01-01 19:06:50.000000000 +0100 @@ -11,6 +11,12 @@ && please install -m 0755 -s target/release/ripcalc /usr/local/bin ``` +or + +``` +please snap install ripcalc +``` + # usage Ripcalc allows networks to be provided by argument @@ -85,7 +91,9 @@ | %r | Network reservation information (if available) | | %d | Matching device interface by IP | | %m | Matching media link interface by network | -| %k | RBL-style format | +| %k | RBL/reverse DNS-style format | +| %D | Network size (--networks) | +| %N | Number of subnets (--networks) | | %% | % | | \n | Line break | | \t | Tab character | @@ -188,6 +196,12 @@ please ip route add blackhole `ripcalc -e 192.168.56.10 192.168.57.1 192.168.44.47` ``` +Networks can be grouped, in a scenario where you have a list of unwanted traffic, you can turn this into a list of small networks to block, supposing you don't want to block anything that covers more than a /19: + +``` +cat bad_traffic | ripcalc --encapsulating --group 19 --format cidr +``` + # help ``` @@ -199,19 +213,22 @@ -c, --csv PATH csv reference file -d, --divide CIDR divide network into chunks --noexpand do not expand networks in list - -e, --encapsulating + -e, --encapsulating display encapsulating network from arguments or lookup list -f, --format STRING format output 'cidr' expands to %a/%c\n 'short' expands to %a\n See manual for more options + --group CIDR maximum network group size for encapsulation -h, --help display help -i, --field FIELD csv field -l, --list list all addresses in network --outside display when extremities are outside network --inside display when extremities are inside network -m, --mask CIDR cidr mask + -n, --networks CIDR instead of hosts, display number of subnets of this + size -r, --reverse (none, inputs, sources or both) v4 octets, v6 hex -s, --file PATH lookup addresses from, - for stdin -v, --version print version diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/changelog.md new/ripcalc-0.1.13/changelog.md --- old/ripcalc-0.1.12/changelog.md 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/changelog.md 2025-01-01 19:06:50.000000000 +0100 @@ -1,3 +1,9 @@ +0.1.13 + + * don't require input to be one IP per line + * show subnets within network in format, suggested by a...@bitfolk.com + * limit how much an encapsulating network can grow with --group + 0.1.12 * now builds on macos #1, thanks @sander_maijers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/ripcalc.1 new/ripcalc-0.1.13/ripcalc.1 --- old/ripcalc-0.1.12/ripcalc.1 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/ripcalc.1 2025-01-01 19:06:50.000000000 +0100 @@ -15,7 +15,7 @@ . ftr VB CB . ftr VBI CBI .\} -.TH "ripcalc" "1" "11 July 2024" "ripcalc 0.1.12" "User Manual" +.TH "ripcalc" "1" "01 January 2025" "ripcalc 0.1.13" "User Manual" .hy .SH NAME .PP @@ -40,7 +40,7 @@ .PP \f[B]ripcalc -s/--file [-] 127.0.0.1\f[R] .PP -\f[B]ripcalc -e/--encapsulating [-s/--file name]\f[R] +\f[B]ripcalc -e/--encapsulating [-s/--file name] [--group CIDR]\f[R] .PP \f[B]ripcalc -s/--file name [--inside/--outside] 127.0.0.1\f[R] .PP @@ -48,6 +48,8 @@ .PP \f[B]ripcalc -d/--divide [CIDR] 127.0.0.1/24\f[R] .PP +\f[B]ripcalc \[en]networks [CIDR] 127.0.0.1/24\f[R] +.PP \f[B]ripcalc -h/--help\f[R] .SH DESCRIPTION .PP @@ -76,11 +78,16 @@ or both can be treated as back-to-front. .PP \f[B]ripcalc\f[R] can return a list of subnets when a network is -provided along with the \f[B]divide\f[R] argument and a subnet CIDR +provided along with the \f[V]--divide\f[R] argument and a subnet CIDR mask. .PP When \f[V]--encapsulating\f[R] is used the containing network will be -returned. +returned, use with \f[V]--group\f[R] to limit the range that an +encapsulating network can grow. +.PP +The number (\f[B]%D\f[R]) of subnets can be printed when using the +\f[V]--group\f[R] argument with the \f[B]%N\f[R] formatters. +The argument should be the CIDR mask, see below for example. .SH CSV .PP Network matches can be returned from a \f[B]CSV\f[R]. @@ -233,7 +240,17 @@ T{ %k T}@T{ -RBL-style format +RBL/reverse DNS-style format +T} +T{ +%D +T}@T{ +Network size (--networks) +T} +T{ +%N +T}@T{ +Number of subnets (--networks) T} T{ %% @@ -263,10 +280,64 @@ --format \[aq]%{name}\[aq] \f[R] .fi -.SS inside/outside +.SH inside/outside .PP When \f[V]--inside\f[R] or \f[V]--outside\f[R] are given addresses that match \f[V]--file\f[R] are printed. If no matches are found \f[V]ripcalc\f[R] will exit non-zero. +.SH subnets +.PP +For large networks it can be useful to see the number of subnets, to see +the number of /29 subnets within a /24 network, the command would look +like this: +.IP +.nf +\f[C] +ripcalc --networks 29 192.168.230.0/24 + IP is: 192.168.230.0/24 + Broadcast is: 192.168.230.255 + Network is: 192.168.230.0 + Subnet is: 255.255.255.0 + Wildcard is: 0.0.0.255 + Networks (29): 32 +\f[R] +.fi +.PP +Or for a IPv6 /48 network that you want to subnet into /64, you can see +there are 65536 subnets: +.IP +.nf +\f[C] + ripcalc --networks 64 2001:db8:1::/48 + IP is: 2001:db8:1::/48 + Expanded: 2001:0db8:0001:0000:0000:0000:0000:0000 + Network is: 2001:0db8:0001:0000:0000:0000:0000:0000 +Last host address: 2001:0db8:0001:ffff:ffff:ffff:ffff:ffff + Subnet is: ffff:ffff:ffff:0000:0000:0000:0000:0000 + Networks (64): 65536 +\f[R] +.fi +.SH encapsulating +.PP +Suppose a large flood of requests are from a network pattern, to +preserve service you may want to block the whole network that +encapsulates a list: +.IP +.nf +\f[C] +please ip route add blackhole \[ga]ripcalc -e 192.168.56.10 192.168.57.1 192.168.44.47\[ga] +\f[R] +.fi +.PP +Networks can be grouped, in a scenario where you have a list of unwanted +traffic, you can turn this into a list of small networks to block, +supposing you don\[cq]t want to block anything that covers more than a +/19: +.IP +.nf +\f[C] +cat bad_traffic | ripcalc --encapsulating --group 19 --format cidr +\f[R] +.fi .SH AUTHORS Ed Neville (ed-ripcalc\[at]s5h.net). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/ripcalc.md new/ripcalc-0.1.13/ripcalc.md --- old/ripcalc-0.1.12/ripcalc.md 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/ripcalc.md 2025-01-01 19:06:50.000000000 +0100 @@ -2,9 +2,9 @@ title: ripcalc section: 1 header: User Manual -footer: ripcalc 0.1.12 +footer: ripcalc 0.1.13 author: Ed Neville (ed-ripc...@s5h.net) -date: 11 July 2024 +date: 01 January 2025 --- # NAME @@ -31,7 +31,7 @@ **ripcalc -s/--file [-] 127.0.0.1** -**ripcalc -e/--encapsulating [-s/--file name]** +**ripcalc -e/--encapsulating [-s/--file name] [--group CIDR]** **ripcalc -s/--file name [--inside/--outside] 127.0.0.1** @@ -39,6 +39,8 @@ **ripcalc -d/--divide [CIDR] 127.0.0.1/24** +**ripcalc --networks [CIDR] 127.0.0.1/24** + **ripcalc -h/--help** @@ -56,9 +58,11 @@ When `--reverse` is used the `inputs`, `sources` or both can be treated as back-to-front. -**ripcalc** can return a list of subnets when a network is provided along with the **divide** argument and a subnet CIDR mask. +**ripcalc** can return a list of subnets when a network is provided along with the `--divide` argument and a subnet CIDR mask. + +When `--encapsulating` is used the containing network will be returned, use with `--group` to limit the range that an encapsulating network can grow. -When `--encapsulating` is used the containing network will be returned. +The number (**%D**) of subnets can be printed when using the `--group` argument with the **%N** formatters. The argument should be the CIDR mask, see below for example. # CSV @@ -117,7 +121,9 @@ | %r | Network reservation information (if available) | | %d | Matching device interface by IP | | %m | Matching media link interface by network | -| %k | RBL-style format | +| %k | RBL/reverse DNS-style format | +| %D | Network size (--networks) | +| %N | Number of subnets (--networks) | | %% | % | | \n | Line break | | \t | Tab character | @@ -128,8 +134,39 @@ --format '%{name}' -## inside/outside +# inside/outside When `--inside` or `--outside` are given addresses that match `--file` are printed. If no matches are found `ripcalc` will exit non-zero. +# subnets + +For large networks it can be useful to see the number of subnets, to see the number of /29 subnets within a /24 network, the command would look like this: + + ripcalc --networks 29 192.168.230.0/24 + IP is: 192.168.230.0/24 + Broadcast is: 192.168.230.255 + Network is: 192.168.230.0 + Subnet is: 255.255.255.0 + Wildcard is: 0.0.0.255 + Networks (29): 32 + +Or for a IPv6 /48 network that you want to subnet into /64, you can see there are 65536 subnets: + + ripcalc --networks 64 2001:db8:1::/48 + IP is: 2001:db8:1::/48 + Expanded: 2001:0db8:0001:0000:0000:0000:0000:0000 + Network is: 2001:0db8:0001:0000:0000:0000:0000:0000 + Last host address: 2001:0db8:0001:ffff:ffff:ffff:ffff:ffff + Subnet is: ffff:ffff:ffff:0000:0000:0000:0000:0000 + Networks (64): 65536 + +# encapsulating + +Suppose a large flood of requests are from a network pattern, to preserve service you may want to block the whole network that encapsulates a list: + + please ip route add blackhole `ripcalc -e 192.168.56.10 192.168.57.1 192.168.44.47` + +Networks can be grouped, in a scenario where you have a list of unwanted traffic, you can turn this into a list of small networks to block, supposing you don't want to block anything that covers more than a /19: + + cat bad_traffic | ripcalc --encapsulating --group 19 --format cidr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/src/lib.rs new/ripcalc-0.1.13/src/lib.rs --- old/ripcalc-0.1.12/src/lib.rs 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/src/lib.rs 2025-01-01 19:06:50.000000000 +0100 @@ -6,6 +6,7 @@ use nix::sys::stat::SFlag; use std::collections::HashMap; use std::fmt; +use std::io::BufRead; use std::net::Ipv4Addr; use std::net::Ipv6Addr; use std::net::ToSocketAddrs; @@ -417,7 +418,7 @@ for key in networks.keys().skip(1) { let mut key = key.clone(); match (&ip.address, &key.address) { - (Addr::V4(_), Addr::V4(_)) => { + (Addr::V4(_), Addr::V4(_)) | (Addr::V6(_), Addr::V6(_)) => { if key.cidr < ip.cidr { ip.cidr = key.cidr; } @@ -431,19 +432,57 @@ } ip = network(&ip); } - (Addr::V6(_), Addr::V6(_)) => { - if key.cidr < ip.cidr { - ip.cidr = key.cidr; + (_, _) => { + return None; + } + } + } + + Some(ip) +} + +pub fn smallest_group_network_limited(networks: &HashMap<Ip, bool>, cidr: u32) -> Option<Vec<Ip>> { + if networks.is_empty() { + return None; + } + + let mut net_list: HashMap<Ip, Ip> = HashMap::new(); + + for key in networks.keys() { + let mut bucket_ip = key.clone(); + bucket_ip.cidr = cidr; + + let mut key_copy = key.clone(); + + // don't add a entry with a bigger network than cidr + if key_copy.cidr < cidr { + key_copy.cidr = cidr; + } + + let bucket = network(&bucket_ip); + + if !net_list.contains_key(&bucket) { + net_list.insert(bucket.clone(), key_copy.clone()); + } + + let ip = net_list.get_mut(&bucket).unwrap(); + + match (&ip.address, &key.address) { + (Addr::V4(_), Addr::V4(_)) | (Addr::V6(_), Addr::V6(_)) => { + // println!("ip.cidr {}, key_copy.cidr {}", ip.cidr, key_copy.cidr); + if key_copy.cidr < ip.cidr { + // println!("key less than ip: ip.cidr {}, key_copy.cidr {}", ip.cidr, key_copy.cidr); + ip.cidr = key_copy.cidr; } - key.cidr = ip.cidr; - while network(&key) != network(&ip) { + key_copy.cidr = ip.cidr; + while network(&key_copy) != network(ip) && ip.cidr > cidr { if ip.cidr == 0 { return None; } ip.cidr -= 1; - key.cidr = ip.cidr; + key_copy.cidr = ip.cidr; } - ip = network(&ip); + *ip = network(ip); } (_, _) => { return None; @@ -451,7 +490,7 @@ } } - Some(ip) + Some(net_list.values().cloned().collect()) } impl fmt::Display for Ip { @@ -1046,6 +1085,7 @@ ip: &Ip, formatted: String, rows: &Option<HashMap<Ip, NetRow>>, + subnet_size: Option<u32>, ) -> Option<String> { let ip = &mut ip.clone(); let mut reformatted = formatted; @@ -1200,6 +1240,20 @@ '%' => { out_str.push('%'); } + 'D' => { + if let Some(s) = subnet_size { + out_str.push_str(&s.to_string()); + } else { + out_str.push('D'); + }; + } + 'N' => { + if let Some(s) = subnet_size { + out_str.push_str(&subnets_in_network(s, ip).to_string()); + } else { + out_str.push('N'); + }; + } _ => { out_str.push(k); } @@ -1265,3 +1319,46 @@ false } + +pub fn find_ips<'a>( + reader: &'a mut Box<dyn BufRead>, + input_base: Option<i32>, + reverse: &'a Reverse, +) -> impl 'a + std::iter::Iterator<Item = Vec<Ip>> { + std::iter::from_fn(move || { + if let Some(line) = reader.lines().next().into_iter().by_ref().next() { + let line: String = line.as_ref().unwrap().trim().to_string(); + let mut v = vec![]; + + for part in line.split(' ') { + let p = part.trim(); + if p.is_empty() { + continue; + } + + let ip = match parse_address_mask( + p, + Some(32), + Some(128), + input_base, + matches!(reverse, Reverse::Both | Reverse::Input), + ) { + Some(x) => x, + None => { + eprintln!("Could not parse {}", p); + continue; + } + }; + + v.push(ip); + } + return Some(v); + } + None + }) +} + +pub fn subnets_in_network(networks: u32, ip: &Ip) -> u128 { + let base: u128 = 2; + base.pow(networks - ip.cidr) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/src/main.rs new/ripcalc-0.1.13/src/main.rs --- old/ripcalc-0.1.12/src/main.rs 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/src/main.rs 2025-01-01 19:06:50.000000000 +0100 @@ -12,13 +12,46 @@ rows: &Option<HashMap<Ip, NetRow>>, used: Option<&HashMap<Addr, bool>>, ) { + let mut networks: Option<u32> = None; + + if matches.opt_present("networks") { + let nets = matches.opt_str("networks").unwrap().trim().parse().unwrap(); + + if nets < ip.cidr && !(matches.opt_present("encapsulating") && matches.opt_present("group")) + { + eprintln!("{} is bigger than the network mask {}", nets, ip.cidr); + std::process::exit(1); + } + + match ip.address { + Addr::V4(_) => { + if nets > 32 { + eprintln!("{} is too big", nets); + std::process::exit(1); + } + } + Addr::V6(_) => { + if nets > 128 { + eprintln!("{} is too big", nets); + std::process::exit(1); + } + } + } + networks = Some(nets); + } + let mut formatted = if matches.opt_present("f") { matches.opt_str("f").unwrap() } else { + let mut network_size = "Network size: %t".to_string(); let width = 25; + if matches.opt_present("networks") { + network_size = format!("Networks ({}): %N", networks.as_ref().unwrap()).to_string(); + } match ip.address { - Addr::V4(_) => format!("{ip:>width$}/{cidr}\n{broadcast:>width$}\n{network:>width$}\n{subnet:>width$}\n{wildcard:>width$}\n{network_size:>width$}\n", ip="IP is: %a", cidr="%c", broadcast="Broadcast is: %b", network="Network is: %n", subnet="Subnet is: %s", wildcard="Wildcard is: %w", network_size="Network size: %t", width=width), - Addr::V6(_) => format!("{ip:>widthn$}/{cidr}\n{expanded:>width$}\n{network:>width$}\n{last_host_address:>width$}\n{subnet:>width$}\n{network_size:>widthn$}\n", ip="IP is: %a", cidr="%c", expanded="Expanded: %xa", network="Network is: %xn", last_host_address="Last host address: %xb", subnet="Subnet is: %xs", network_size="Network size: %t", width=width, widthn=width-1), + + Addr::V4(_) => format!("{ip:>width$}/{cidr}\n{broadcast:>width$}\n{network:>width$}\n{subnet:>width$}\n{wildcard:>width$}\n{network_size:>width$}\n", ip="IP is: %a", cidr="%c", broadcast="Broadcast is: %b", network="Network is: %n", subnet="Subnet is: %s", wildcard="Wildcard is: %w", network_size=network_size, width=width), + Addr::V6(_) => format!("{ip:>widthn$}/{cidr}\n{expanded:>width$}\n{network:>width$}\n{last_host_address:>width$}\n{subnet:>width$}\n{network_size:>widthn$}\n", ip="IP is: %a", cidr="%c", expanded="Expanded: %xa", network="Network is: %xn", last_host_address="Last host address: %xb", subnet="Subnet is: %xs", network_size=network_size, width=width, widthn=width-1), } }; @@ -40,7 +73,7 @@ }; for ip_copy in addresses(ip, used, Some(divide)) { - if let Some(m) = format_details(&ip_copy, formatted.to_string(), rows) { + if let Some(m) = format_details(&ip_copy, formatted.to_string(), rows, networks) { print!("{}", m); } } @@ -49,21 +82,21 @@ if matches.opt_present("list") { if matches.opt_present("noexpand") { - if let Some(m) = format_details(ip, formatted, rows) { + if let Some(m) = format_details(ip, formatted, rows, networks) { print!("{}", m); } return; } for ip_copy in addresses(ip, used, None) { - if let Some(m) = format_details(&ip_copy, formatted.to_string(), rows) { + if let Some(m) = format_details(&ip_copy, formatted.to_string(), rows, networks) { print!("{}", m); } } return; } - if let Some(m) = format_details(ip, formatted, rows) { + if let Some(m) = format_details(ip, formatted, rows, networks) { print!("{}", m); } } @@ -189,7 +222,7 @@ rows: &Option<HashMap<Ip, NetRow>>, inside: Option<bool>, ) { - let reader: Box<dyn BufRead> = if path == "-" { + let mut reader: Box<dyn BufRead> = if path == "-" { Box::new(BufReader::new(std::io::stdin())) } else { let path = std::path::Path::new(&path); @@ -203,24 +236,10 @@ Box::new(BufReader::new(File::open(path).unwrap())) }; - let mut used: HashMap<Addr, bool> = HashMap::new(); if matches.opt_present("available") { - for line in reader.lines() { - let line: String = line.as_ref().unwrap().trim().to_string(); - let ip = match parse_address_mask( - line.as_ref(), - Some(32), - Some(128), - input_base, - matches!(reverse, Reverse::Both | Reverse::Input), - ) { - Some(x) => x, - None => { - eprintln!("Could not parse {}", line); - continue; - } - }; - for ip in addresses(&ip, None, None) { + let mut used: HashMap<Addr, bool> = HashMap::new(); + for a in find_ips(&mut reader, input_base, reverse) { + for ip in a { used.insert(ip.address, true); } } @@ -233,31 +252,36 @@ if matches.opt_present("encapsulating") { let mut used: HashMap<Ip, bool> = HashMap::new(); - for line in reader.lines() { - let line: String = line.as_ref().unwrap().trim().to_string(); - let ip = match parse_address_mask( - line.as_ref(), - Some(32), - Some(128), - input_base, - matches!(reverse, Reverse::Both | Reverse::Input), - ) { - Some(x) => x, - None => { - eprintln!("Could not parse {}", line); - continue; - } - }; - used.insert(ip, true); + for a in find_ips(&mut reader, input_base, reverse) { + for i in a { + used.insert(i, true); + } } - match smallest_group_network(&used) { - Some(x) => { - print_details(&x, matches, rows, None); + if matches.opt_present("group") { + let network_size: u32 = matches.opt_str("group").unwrap().trim().parse().unwrap(); + + match smallest_group_network_limited(&used, network_size) { + Some(mut x) => { + x.sort_by(|a, b| a.partial_cmp(b).unwrap()); + for y in x { + print_details(&y, matches, rows, None); + } + } + None => { + eprintln!("Could not find an encapsulating network, sorry"); + std::process::exit(1); + } } - None => { - eprintln!("Could not find an encapsulating network, sorry"); - std::process::exit(1); + } else { + match smallest_group_network(&used) { + Some(x) => { + print_details(&x, matches, rows, None); + } + None => { + eprintln!("Could not find an encapsulating network, sorry"); + std::process::exit(1); + } } } @@ -266,51 +290,41 @@ let mut found_match = false; - for line in reader.lines() { - let line: String = line.as_ref().unwrap().trim().to_string(); - let ip = parse_address_mask( - &line, - Some(32), - Some(128), - input_base, - matches!(reverse, Reverse::Both | Reverse::Source), - ); - if ip.is_none() { - continue; - } + for a in find_ips(&mut reader, input_base, reverse) { + for ip in a { + match inside { + Some(true) => { + let mut found = false; + for arg in ip_args { + if within(arg, &ip) { + found = true; + break; + } + } - match inside { - Some(true) => { - let mut found = false; - for arg in ip_args { - if within(arg, ip.as_ref().unwrap()) { - found = true; - break; + if found { + found_match = true; + print_details(&ip, matches, rows, None); } } + Some(false) => { + let mut found = false; - if found { - found_match = true; - print_details(&ip.unwrap(), matches, rows, None); - } - } - Some(false) => { - let mut found = false; - - for arg in ip_args { - if within(arg, ip.as_ref().unwrap()) { - found = true; - break; + for arg in ip_args { + if within(arg, &ip) { + found = true; + break; + } } - } - if !found { - found_match = true; - print_details(&ip.unwrap(), matches, rows, None); + if !found { + found_match = true; + print_details(&ip, matches, rows, None); + } + } + None => { + print_details(&ip, matches, rows, None); } - } - None => { - print_details(&ip.unwrap(), matches, rows, None); } } } @@ -363,6 +377,12 @@ ); opts.optopt("f", "format", "format output\n'cidr' expands to %a/%c\\n\n'short' expands to %a\\n\nSee manual for more options", "STRING"); + opts.optopt( + "", + "group", + "maximum network group size for encapsulation", + "CIDR", + ); opts.optflag("h", "help", "display help"); opts.optopt("i", "field", "csv field", "FIELD"); @@ -374,6 +394,12 @@ ); opts.optflag("", "inside", "display when extremities are inside network"); opts.optopt("m", "mask", "cidr mask", "CIDR"); + opts.optopt( + "n", + "networks", + "instead of hosts, display number of subnets of this size", + "CIDR", + ); opts.optopt( "r", @@ -416,6 +442,26 @@ inside = Some(false); } + if matches.opt_present("group") { + let _: u32 = match matches.opt_str("group").unwrap().trim().parse() { + Ok(x) => x, + Err(x) => { + eprintln!("Cannot convert {} to number", x); + std::process::exit(1); + } + }; + } + + if matches.opt_present("networks") { + let _: u32 = match matches.opt_str("networks").unwrap().trim().parse() { + Ok(x) => x, + Err(x) => { + eprintln!("Cannot convert {} to number", x); + std::process::exit(1); + } + }; + } + if matches.opt_present("reverse") { match matches.opt_str("reverse").unwrap().as_str() { "inputs" => { @@ -556,13 +602,29 @@ } if matches.opt_present("encapsulating") { - match smallest_group_network(&used) { - Some(x) => { - print_details(&x, &matches, &rows, None); + if matches.opt_present("networks") { + let network_size: u32 = matches.opt_str("networks").unwrap().trim().parse().unwrap(); + + match smallest_group_network_limited(&used, network_size) { + Some(x) => { + for y in x { + print_details(&y, &matches, &rows, None); + } + } + None => { + eprintln!("Could not find an encapsulating network, sorry"); + std::process::exit(1); + } } - None => { - eprintln!("Could not find an encapsulating network, sorry"); - std::process::exit(1); + } else { + match smallest_group_network(&used) { + Some(x) => { + print_details(&x, &matches, &rows, None); + } + None => { + eprintln!("Could not find an encapsulating network, sorry"); + std::process::exit(1); + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ripcalc-0.1.12/tests/funcs.rs new/ripcalc-0.1.13/tests/funcs.rs --- old/ripcalc-0.1.12/tests/funcs.rs 2024-07-11 22:35:02.000000000 +0200 +++ new/ripcalc-0.1.13/tests/funcs.rs 2025-01-01 19:06:50.000000000 +0100 @@ -277,7 +277,7 @@ cidr: 30, }; - let f = format_details(&net, "%a".to_string(), &None); + let f = format_details(&net, "%a".to_string(), &None, None); assert_eq!(f, Some("192.168.0.0".to_string())); } @@ -291,6 +291,7 @@ }, "%".to_string(), &None, + None, ); assert_eq!(f, Some("%".to_string())); @@ -302,6 +303,7 @@ }, "%%".to_string(), &None, + None, ); assert_eq!(f, Some("%".to_string())); @@ -313,6 +315,7 @@ }, "%%%".to_string(), &None, + None, ); assert_eq!(f, Some("%%".to_string())); @@ -324,6 +327,7 @@ }, "%%%%".to_string(), &None, + None, ); assert_eq!(f, Some("%%".to_string())); @@ -336,7 +340,7 @@ cidr: 64, }; - let f = format_details(&net, "select * from IP6 where (ip >= %ln and ip <= %lb) and active = 1;\nupdate IP6 set active = 0 where (ip >= %ln and ip <= %lb) and active = 1;".to_string(), &None); + let f = format_details(&net, "select * from IP6 where (ip >= %ln and ip <= %lb) and active = 1;\nupdate IP6 set active = 0 where (ip >= %ln and ip <= %lb) and active = 1;".to_string(), &None, None); assert_eq!(f, Some("select * from IP6 where (ip >= 42540724579414763292693624807812497408 and ip <= 42540724579414763311140368881522049023) and active = 1; update IP6 set active = 0 where (ip >= 42540724579414763292693624807812497408 and ip <= 42540724579414763311140368881522049023) and active = 1;".to_string())); @@ -349,7 +353,7 @@ cidr: 64, }; - let f = format_details(&net, "%%b".to_string(), &None); + let f = format_details(&net, "%%b".to_string(), &None, None); assert_eq!(f, Some("%b".to_string())); } @@ -361,7 +365,7 @@ cidr: 64, }; - let f = format_details(&net, "%lb".to_string(), &None); + let f = format_details(&net, "%lb".to_string(), &None, None); assert_eq!( f, @@ -376,7 +380,7 @@ cidr: 64, }; - let f = format_details(&net, "%lb\n\n\n%%".to_string(), &None); + let f = format_details(&net, "%lb\n\n\n%%".to_string(), &None, None); assert_eq!( f, @@ -391,16 +395,16 @@ cidr: 64, }; - let f = format_details(&net, "\n".to_string(), &None); + let f = format_details(&net, "\n".to_string(), &None, None); assert_eq!(f, Some("\n".to_string())); - let f = format_details(&net, "\\".to_string(), &None); + let f = format_details(&net, "\\".to_string(), &None, None); assert_eq!(f, Some('\\'.to_string())); - let f = format_details(&net, "\\i".to_string(), &None); + let f = format_details(&net, "\\i".to_string(), &None, None); assert_eq!(f, Some("i".to_string())); - let f = format_details(&net, "\\t".to_string(), &None); + let f = format_details(&net, "\\t".to_string(), &None, None); assert_eq!(f, Some("\t".to_string())); } @@ -626,9 +630,9 @@ cidr: 30, }; - let f = format_details(&net, "%La".to_string(), &None); + let f = format_details(&net, "%La".to_string(), &None, None); assert_eq!(f, Some("-1819047474".to_string())); - let f = format_details(&net, "%la".to_string(), &None); + let f = format_details(&net, "%la".to_string(), &None, None); assert_eq!(f, Some("2475919822".to_string())); let net = Ip { @@ -636,9 +640,9 @@ cidr: 30, }; - let f = format_details(&net, "%La".to_string(), &None); + let f = format_details(&net, "%La".to_string(), &None, None); assert_eq!(f, Some("-5192296858534827628530496329154561".to_string())); - let f = format_details(&net, "%la".to_string(), &None); + let f = format_details(&net, "%la".to_string(), &None, None); assert_eq!( f, Some("340277174624079928635746076935439056895".to_string()) @@ -806,4 +810,148 @@ let v: Vec<Ip> = i.collect(); assert_eq!(v.len(), 65536); } + + #[test] + fn test_networks_sizing_v6() { + let net = Ip { + address: Addr::V6(Ipv6Addr::from_str("::1").unwrap()), + cidr: 48, + }; + + assert_eq!(subnets_in_network(64, &net), 65536); + assert_eq!(subnets_in_network(48, &net), 1); + assert_eq!(subnets_in_network(49, &net), 2); + } + + #[test] + fn test_networks_sizing_v4() { + let net = Ip { + address: Addr::V4(Ipv4Addr::from_str("127.0.0.1").unwrap()), + cidr: 16, + }; + + assert_eq!(subnets_in_network(24, &net), 256); + assert_eq!(subnets_in_network(25, &net), 512); + assert_eq!(subnets_in_network(26, &net), 1024); + assert_eq!(subnets_in_network(27, &net), 2048); + assert_eq!(subnets_in_network(28, &net), 4096); + } + + #[test] + fn test_smallest_network_limited() { + let empty: HashMap<Ip, bool> = HashMap::new(); + assert_eq!(smallest_group_network_limited(&empty, 32), None); + } + + #[test] + fn test_smallest_network_limited_22_24() { + let mut net_list: HashMap<Ip, bool> = HashMap::new(); + for i in 0..4 { + for j in 0..4 { + net_list.insert( + Ip { + address: Addr::V4( + Ipv4Addr::from_str(&format!("192.168.{i}.{j}")).expect("bad ipv4"), + ), + cidr: 24, + }, + true, + ); + } + } + + let mut resp = smallest_group_network_limited(&net_list, 22).unwrap(); + resp.sort_by(|a, b| a.partial_cmp(b).unwrap()); + assert_eq!( + resp, + [Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.168.0.0")).expect("bad ipv4")), + cidr: 22 + }] + ); + } + + #[test] + fn test_smallest_network_limited_22_8() { + let mut net_list: HashMap<Ip, bool> = HashMap::new(); + for i in 0..4 { + for j in 0..4 { + net_list.insert( + Ip { + address: Addr::V4( + Ipv4Addr::from_str(&format!("192.{i}.{j}.0")).expect("bad ipv4"), + ), + cidr: 8, + }, + true, + ); + } + } + + let mut resp = smallest_group_network_limited(&net_list, 22).unwrap(); + resp.sort_by(|a, b| a.partial_cmp(b).unwrap()); + assert_eq!( + resp, + [ + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.0.0.0")).expect("bad ipv4")), + cidr: 22 + }, + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.1.0.0")).expect("bad ipv4")), + cidr: 22 + }, + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.2.0.0")).expect("bad ipv4")), + cidr: 22 + }, + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.3.0.0")).expect("bad ipv4")), + cidr: 22 + }, + ] + ); + } + + #[test] + fn test_smallest_network_limited_24_8() { + let mut net_list: HashMap<Ip, bool> = HashMap::new(); + for i in 0..4 { + for j in 0..4 { + net_list.insert( + Ip { + address: Addr::V4( + Ipv4Addr::from_str(&format!("192.0.{i}.{j}")).expect("bad ipv4"), + ), + cidr: 8, + }, + true, + ); + } + } + + let mut resp = smallest_group_network_limited(&net_list, 24).unwrap(); + resp.sort_by(|a, b| a.partial_cmp(b).unwrap()); + assert_eq!( + resp, + [ + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.0.0.0")).expect("bad ipv4")), + cidr: 24 + }, + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.0.1.0")).expect("bad ipv4")), + cidr: 24 + }, + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.0.2.0")).expect("bad ipv4")), + cidr: 24 + }, + Ip { + address: Addr::V4(Ipv4Addr::from_str(&format!("192.0.3.0")).expect("bad ipv4")), + cidr: 24 + }, + ] + ); + } } ++++++ ripcalc.obsinfo ++++++ --- /var/tmp/diff_new_pack.3eVwlQ/_old 2025-01-05 15:29:31.229478410 +0100 +++ /var/tmp/diff_new_pack.3eVwlQ/_new 2025-01-05 15:29:31.233478574 +0100 @@ -1,5 +1,5 @@ name: ripcalc -version: 0.1.12 -mtime: 1720730102 -commit: 5aca7acaf7fc5e6d53eb737e620fe418cf76bfdb +version: 0.1.13 +mtime: 1735754810 +commit: d1e29a5ea08ffcb525d423dcbe51e26436d5f851 ++++++ vendor.tar.zst ++++++ ++++ 995733 lines of diff (skipped)