net.cidr-hosts
List every usable host address in an IPv4 CIDR block, refusing blocks larger than a limit.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 15 tests, run in TypeScript, Python and Rust.
What it does
Expand an IPv4 block into its usable host addresses, first to last, for a ping or port sweep.
- "Usable" follows `net.cidr`'s `cidrInfo`: the network and broadcast addresses are left out from /0 to /30; a /31 gives both addresses (RFC 3021) and a /32 the one. - Host bits in the block are dropped, so `10.0.0.13/29` sweeps `10.0.0.8/29`. - `maxCount` is required. A sweep of `10.0.0.0/8` (16,777,214 hosts) is almost always a typo for a /28; the call fails and says how many hosts the block has instead of quietly building a sixteen-million-item list. - IPv4 only: an IPv6 /64 cannot be swept by enumeration.
For example
cidr_hosts(192.168.1.0/30, 16)→ 192.168.1.1, 192.168.1.2 a /30 has two hosts, network and broadcast left outcidr_hosts(10.0.0.8/29, 16)→ 10.0.0.9, 10.0.0.10, 10.0.0.11, 10.0.0.12, 10.0.0.13, 10.0.0.14 a /29 has sixcidr_hosts(10.0.0.13/29, 6)→ 10.0.0.9, 10.0.0.10, 10.0.0.11, 10.0.0.12, 10.0.0.13, 10.0.0.14 host bits in the block are dropped
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn cidr_hosts(cidr: &str, max_count: i64) -> Vec<String>
| cidr | string | an IPv4 block, e.g. 192.168.1.0/28 |
| max_count | int | refuse a block with more usable hosts than this, at least 1 |
| returns | string[] | first host to last host in ascending order |
Your code names it in one line, in the file that uses it
fune!(net.cidr-hosts@^1); // then call cidr_hosts(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::net_cidr_cidr_info::cidr_info;
use super::net_ipv4::{format_ipv4, parse_ipv4}; ← from net.ipv4 ^1.0.0 · built alongside by fune
/// Every usable host address of an IPv4 block, in order, for a sweep.
///
/// The limit is required, not optional: a typo of /8 for /28 is sixteen
/// million addresses, and the caller should hear about it before allocating
/// them.
///
/// # Panics
/// Panics on a malformed or IPv6 block, a max_count below 1, or a block with
/// more hosts than max_count.
pub fn cidr_hosts(cidr: &str, max_count: i64) -> Vec<String> {
if max_count < 1 {
panic!("maxCount must be a whole number of at least 1, received {}", max_count);
}
let info = cidr_info(cidr);
if info.host_count > max_count {
panic!(
"{} has {} hosts, more than maxCount {}",
info.cidr, info.host_count, max_count
);
}
let first = parse_ipv4(&info.first_host);
(0..info.host_count).map(|i| format_ipv4(first + i)).collect()
}
pub fn fune_vector(args: &[Value]) -> Value {
let max_count = match &args[1] {
Value::Int(i) => *i,
other => panic!("maxCount must be a whole number of at least 1, received {}", other),
};
Value::Arr(cidr_hosts(args[0].as_str(), max_count).iter().map(|h| Value::str(h)).collect())
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add net.cidr-hosts
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./net.cidr-hosts-1.0.0-rust.fune, or fetch it from a terminal with fune pull net.cidr-hosts@1.0.0:rust.
The whole function, every language, is one file too: net.cidr-hosts-1.0.0.fune, 8,078 bytes, sha256 2d687cc796d80ae148d21078616ddf2ee78e96cbd9c9702318f2a28dc36a3283. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before net.cidr-hosts
after — your function gets the result and the arguments, and returns the final result.
// fune: after net.cidr-hosts
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace net.cidr in net.cidr-hosts
// fune: replace net.ipv4 in net.cidr-hosts
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show net.cidr-hosts --steps.
// fune: step net.cidr-hosts after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| a /30 has two hosts, network and broadcast left out | 192.168.1.0/30, 16 | → | 192.168.1.1, 192.168.1.2 |
| a /29 has six | 10.0.0.8/29, 16 | → | 10.0.0.9, 10.0.0.10, 10.0.0.11, 10.0.0.12, 10.0.0.13, 10.0.0.14 |
| host bits in the block are dropped | 10.0.0.13/29, 6 | → | 10.0.0.9, 10.0.0.10, 10.0.0.11, 10.0.0.12, 10.0.0.13, 10.0.0.14 |
| a /31 point-to-point link: both addresses | 198.51.100.6/31, 2 | → | 198.51.100.6, 198.51.100.7 |
| a /32 is the address itself | 127.0.0.1/32, 1 | → | 127.0.0.1 |
| loopback /30 for a local sweep | 127.0.0.0/30, 4 | → | 127.0.0.1, 127.0.0.2 |
| crossing an octet boundary counts on through .255 to the next octet | 192.0.2.252/29, 8 | → | 192.0.2.249, 192.0.2.250, 192.0.2.251, 192.0.2.252, 192.0.2.253, 192.0.2.254 |
| a /28 crossing into the next third octet | 10.1.1.240/27, 30 | → | 10.1.1.225, 10.1.1.226, 10.1.1.227, 10.1.1.228, 10.1.1.229, 10.1.1.230, 10.1.1.231, 10.1.1.232, 10.1.1.233, 10.1.1.234, 10.1.1.235, 10.1.1.236, 10.1.1.237, 10.1.1.238, 10.1.1.239,… |
| exactly at the limit is allowed | 192.168.1.0/30, 2 | → | 192.168.1.1, 192.168.1.2 |
| one over the limit is refused | 192.168.1.0/29, 5 | → | error: 192.168.1.0/29 has 6 hosts, more than maxCount 5 |
Show the other 5 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a /8 is refused under a sweep-sized limit | 10.0.0.0/8, 1,024 | → | error: more than maxCount 1024 |
| maxCount zero | 192.168.1.0/30, 0 | → | error: maxCount must be a whole number of at least 1 |
| maxCount fractional | 192.168.1.0/30, 2.5 | → | error: maxCount must be a whole number of at least 1 |
| an IPv6 block | 2001:db8::/126, 4 | → | error: cidrInfo handles IPv4 blocks only |
| a malformed block | 192.168.1.0/, 4 | → | error: is not a CIDR block |
Files
| Path | Bytes |
|---|---|
| README.md | 636 |
| impl/python.py | 883 |
| impl/rust.rs | 1,294 |
| impl/typescript.ts | 936 |
| vectors.json | 2,427 |