net.cidr
CIDR blocks: network, broadcast, first and last host, host count, netmask and prefix, and membership.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 70 tests, run in TypeScript, Python and Rust.cidrInfo 15 · cidrContains 18 · isCidr 13 · prefixToNetmask 12 · netmaskToPrefix 12
What it does
A subnet calculator as five functions that only make sense together: describe an IPv4 block (`cidrInfo`), test membership for IPv4 or IPv6 (`cidrContains`), check a block without raising (`isCidr`), and convert between prefix lengths and dotted netmasks.
## Decisions
The functions
A group: 5 functions that work together, each in its own file, each pinned by its own tests in TypeScript, Python and Rust. A project can install only the ones it calls.
- cidr_info (cidr: string) -> CidrInfo
- cidr_contains (cidr: string, address: string) -> bool
- is_cidr (text: string) -> bool
- prefix_to_netmask (prefix: int) -> string
- netmask_to_prefix (netmask: string) -> int
The type it declares, generated into your project
/// Everything a subnet calculator shows for one IPv4 block.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CidrInfo {
/// the block with host bits cleared, e.g. 192.168.1.0/24
pub cidr: String,
pub network: String,
pub prefix: i64,
pub netmask: String,
/// the inverse mask, as ACLs write it
pub wildcard: String,
/// null for /31 and /32, which have none
pub broadcast: Option<String>,
pub first_host: String,
pub last_host: String,
/// usable addresses: all of them for /31 (RFC 3021) and /32
pub host_count: i64,
pub address_count: i64,
}
Once installed, your code imports each one from the group's module.
cidr_info throws on bad input 15 tests
pub fn cidr_info(cidr: &str) -> CidrInfo
| cidr | string | an IPv4 block, address/prefix, e.g. 192.168.1.0/24; host bits may be set and are dropped |
| returns | CidrInfo |
For example
cidr_info(192.168.1.0/24)→ cidr 192.168.1.0/24, network 192.168.1.0, prefix 24, netmask 255.255.255.0, wildcard 0.0.0.255, broadcast 192.168.1.255, first host 192.168.1.1, last host 192.168.1.254, host coun… a /24cidr_info(192.168.1.77/24)→ cidr 192.168.1.0/24, network 192.168.1.0, prefix 24, netmask 255.255.255.0, wildcard 0.0.0.255, broadcast 192.168.1.255, first host 192.168.1.1, last host 192.168.1.254, host coun… an interface address: host bits are droppedcidr_info(10.0.0.0/8)→ cidr 10.0.0.0/8, network 10.0.0.0, prefix 8, netmask 255.0.0.0, wildcard 0.255.255.255, broadcast 10.255.255.255, first host 10.0.0.1, last host 10.255.255.254, host count 16,777,… a /8
fune!(net.cidr@^1); // then call cidr_info(…)
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_is_cidr::cidr_block; ← isCidr, another function of this group · built into the same file, even by a slim install
use super::net_ipv4_format_ipv4::format_ipv4;
/// Everything a subnet calculator shows for one IPv4 block.
///
/// Host bits in the input are dropped (192.168.1.77/24 describes
/// 192.168.1.0/24). A /31 is a point-to-point link with two usable addresses
/// and no broadcast (RFC 3021); a /32 is one host.
///
/// # Panics
/// Panics on a malformed block or an IPv6 block.
pub fn cidr_info(cidr: &str) -> CidrInfo {
let (version, bits, prefix) = match cidr_block(cidr) {
Some(block) => block,
None => panic!("\"{}\" is not a CIDR block", cidr),
};
if version != 4 {
panic!("cidrInfo handles IPv4 blocks only, received \"{}\"", cidr);
}
let size: i64 = 1 << (32 - prefix);
let network = bits[0] - bits[0] % size;
let last = network + size - 1;
let point_to_point = prefix >= 31;
CidrInfo {
cidr: format!("{}/{}", format_ipv4(network), prefix),
network: format_ipv4(network),
prefix,
netmask: format_ipv4(4294967296 - size),
wildcard: format_ipv4(size - 1),
broadcast: if point_to_point { None } else { Some(format_ipv4(last)) },
first_host: format_ipv4(if point_to_point { network } else { network + 1 }),
last_host: format_ipv4(if point_to_point { last } else { last - 1 }),
host_count: if point_to_point { size } else { size - 2 },
address_count: size,
}
}
pub fn cidr_info_to_value(info: &CidrInfo) -> Value {
Value::obj(vec![
("cidr", Value::str(&info.cidr)),
("network", Value::str(&info.network)),
("prefix", Value::Int(info.prefix)),
("netmask", Value::str(&info.netmask)),
("wildcard", Value::str(&info.wildcard)),
("broadcast", match &info.broadcast {
Some(b) => Value::str(b),
None => Value::Null,
}),
("firstHost", Value::str(&info.first_host)),
("lastHost", Value::str(&info.last_host)),
("hostCount", Value::Int(info.host_count)),
("addressCount", Value::Int(info.address_count)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
cidr_info_to_value(&cidr_info(args[0].as_str()))
}cidr_contains throws on bad input 18 tests
pub fn cidr_contains(cidr: &str, address: &str) -> bool
| cidr | string | an IPv4 or IPv6 block |
| address | string | an IPv4 or IPv6 address; one of the other family is never contained |
| returns | bool |
For example
cidr_contains(192.168.1.0/24, 192.168.1.200)→ true inside a /24cidr_contains(192.168.1.0/24, 192.168.2.1)→ false the next /24 is outsidecidr_contains(10.0.0.0/8, 10.255.255.255)→ true the last address of a /8
fune!(net.cidr@^1); // then call cidr_contains(…)
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_is_cidr::cidr_block; ← isCidr, another function of this group · built into the same file, even by a slim install
use super::net_ipv4_parse_ipv4::ipv4_value;
use super::net_ipv6_parse_ipv6::ipv6_groups;
/// Whether an address lies inside a block, IPv4 or IPv6.
///
/// An address of the other family is never inside (an IPv4-mapped IPv6
/// address is not treated as its IPv4 address).
///
/// # Panics
/// Panics on a malformed block or address.
pub fn cidr_contains(cidr: &str, address: &str) -> bool {
let (version, bits, prefix) = match cidr_block(cidr) {
Some(block) => block,
None => panic!("\"{}\" is not a CIDR block", cidr),
};
let v4 = ipv4_value(address);
let v6 = if v4.is_none() { ipv6_groups(address) } else { None };
if v4.is_none() && v6.is_none() {
panic!("\"{}\" is not an IP address", address);
}
if version == 4 {
return match v4 {
None => false,
Some(a) => {
let size: i64 = 1 << (32 - prefix);
a / size == bits[0] / size
}
};
}
let groups = match v6 {
None => return false,
Some(g) => g,
};
for i in 0..8 {
let taken = (prefix - 16 * i as i64).clamp(0, 16);
let size: i64 = 1 << (16 - taken);
if groups[i] / size != bits[i] / size {
return false;
}
}
true
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::Bool(cidr_contains(args[0].as_str(), args[1].as_str()))
}is_cidr 13 tests
pub fn is_cidr(text: &str) -> bool
| text | string | any text; true for a well-formed IPv4 or IPv6 block |
| returns | bool |
For example
is_cidr(10.0.0.0/8)→ true an IPv4 blockis_cidr(2001:db8::/32)→ true an IPv6 blockis_cidr(0.0.0.0/0)→ true /0
fune!(net.cidr@^1); // then call is_cidr(…)
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_ipv4_parse_ipv4::ipv4_value;
use super::net_ipv6_parse_ipv6::ipv6_groups;
/// True for a well-formed IPv4 (prefix 0-32) or IPv6 (prefix 0-128) block; never panics.
pub fn is_cidr(text: &str) -> bool {
cidr_block(text).is_some()
}
// Exported for the other functions of this group; not part of its contract.
/// The block as (version, bits, prefix): bits is [address] for version 4 and
/// the eight groups for version 6. None when the text is not address/prefix
/// with a strict decimal prefix.
pub fn cidr_block(text: &str) -> Option<(i64, Vec<i64>, i64)> {
let mut halves = text.split('/');
let address = halves.next()?;
let digits = halves.next()?;
if halves.next().is_some() {
return None;
}
let bytes = digits.as_bytes();
if bytes.is_empty() || bytes.len() > 3 || !bytes.iter().all(|b| b.is_ascii_digit()) {
return None;
}
if bytes.len() > 1 && bytes[0] == b'0' {
return None;
}
let prefix = bytes.iter().fold(0i64, |acc, b| acc * 10 + (b - b'0') as i64);
if let Some(v4) = ipv4_value(address) {
return if prefix <= 32 { Some((4, vec![v4], prefix)) } else { None };
}
if let Some(v6) = ipv6_groups(address) {
return if prefix <= 128 { Some((6, v6, prefix)) } else { None };
}
None
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::Bool(is_cidr(args[0].as_str()))
}prefix_to_netmask throws on bad input 12 tests
pub fn prefix_to_netmask(prefix: i64) -> String
| prefix | int | 0 to 32 |
| returns | string |
For example
prefix_to_netmask(24)→ 255.255.255.0 /24prefix_to_netmask(0)→ 0.0.0.0 /0prefix_to_netmask(8)→ 255.0.0.0 /8
fune!(net.cidr@^1); // then call prefix_to_netmask(…)
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_ipv4_format_ipv4::format_ipv4;
/// The dotted-quad mask for a prefix length: 24 is 255.255.255.0.
///
/// # Panics
/// Panics when the prefix is outside 0 to 32.
pub fn prefix_to_netmask(prefix: i64) -> String {
if !(0..=32).contains(&prefix) {
panic!("prefix must be an integer from 0 to 32, received {}", prefix);
}
format_ipv4(4294967296 - (1i64 << (32 - prefix)))
}
pub fn fune_vector(args: &[Value]) -> Value {
let prefix = match &args[0] {
Value::Int(i) => *i,
other => panic!("prefix must be an integer from 0 to 32, received {}", other),
};
Value::str(&prefix_to_netmask(prefix))
}netmask_to_prefix throws on bad input 12 tests
pub fn netmask_to_prefix(netmask: &str) -> i64
| netmask | string | a dotted-quad mask whose one bits are contiguous from the left |
| returns | int |
For example
netmask_to_prefix(255.255.255.0)→ 24 255.255.255.0 is /24netmask_to_prefix(0.0.0.0)→ 0 all zeros is /0netmask_to_prefix(255.255.255.255)→ 32 all ones is /32
fune!(net.cidr@^1); // then call netmask_to_prefix(…)
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_ipv4_parse_ipv4::parse_ipv4;
/// The prefix length of a dotted-quad mask; a mask with a gap in its one bits is refused.
///
/// # Panics
/// Panics on text that is not a dotted quad, or a non-contiguous mask.
pub fn netmask_to_prefix(netmask: &str) -> i64 {
let value = parse_ipv4(netmask);
let host_part = 4294967295 - value + 1;
for prefix in 0..=32i64 {
if (1i64 << (32 - prefix)) == host_part {
return prefix;
}
}
panic!("\"{}\" is not a contiguous netmask", netmask);
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::Int(netmask_to_prefix(args[0].as_str()))
}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
That builds the whole group. To build only what you call, and whatever it uses inside the group:
fune add net.cidr --only cidrInfo
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./net.cidr-1.0.0-rust.fune, or fetch it from a terminal with fune pull net.cidr@1.0.0:rust.
The whole function, every language, is one file too: net.cidr-1.0.0.fune, 36,244 bytes, sha256 bbc5cdb055ae733750b3c2cfb9061a67690545d83a9deae6e546ea7de67c4510. 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.cidrInfo
// fune: before net.cidr.cidrContains
// fune: before net.cidr.isCidr
// fune: before net.cidr.prefixToNetmask
// fune: before net.cidr.netmaskToPrefix
after — your function gets the result and the arguments, and returns the final result.
// fune: after net.cidr.cidrInfo
// fune: after net.cidr.cidrContains
// fune: after net.cidr.isCidr
// fune: after net.cidr.prefixToNetmask
// fune: after net.cidr.netmaskToPrefix
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.ipv4 in net.cidr
// fune: replace net.ipv6 in net.cidr
step — your function runs at a numbered point inside a function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show net.cidr --steps.
// fune: step net.cidr.<fn> 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.
cidrInfo 15 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a /24 | 192.168.1.0/24 | → | cidr 192.168.1.0/24, network 192.168.1.0, prefix 24, netmask 255.255.255.0, wildcard 0.0.0.255, broadcast 192.168.1.255, first host 192.168.1.1, last host 192.168.1.254, host coun… |
| an interface address: host bits are dropped | 192.168.1.77/24 | → | cidr 192.168.1.0/24, network 192.168.1.0, prefix 24, netmask 255.255.255.0, wildcard 0.0.0.255, broadcast 192.168.1.255, first host 192.168.1.1, last host 192.168.1.254, host coun… |
| a /8 | 10.0.0.0/8 | → | cidr 10.0.0.0/8, network 10.0.0.0, prefix 8, netmask 255.0.0.0, wildcard 0.255.255.255, broadcast 10.255.255.255, first host 10.0.0.1, last host 10.255.255.254, host count 16,777,… |
| a /12 that does not fall on an octet: 172.16-172.31 | 172.16.5.4/12 | → | cidr 172.16.0.0/12, network 172.16.0.0, prefix 12, netmask 255.240.0.0, wildcard 0.15.255.255, broadcast 172.31.255.255, first host 172.16.0.1, last host 172.31.255.254, host coun… |
| a /26 in the middle of a /24 | 203.0.113.64/26 | → | cidr 203.0.113.64/26, network 203.0.113.64, prefix 26, netmask 255.255.255.192, wildcard 0.0.0.63, broadcast 203.0.113.127, first host 203.0.113.65, last host 203.0.113.126, host … |
| a /30 has two hosts | 192.168.1.0/30 | → | cidr 192.168.1.0/30, network 192.168.1.0, prefix 30, netmask 255.255.255.252, wildcard 0.0.0.3, broadcast 192.168.1.3, first host 192.168.1.1, last host 192.168.1.2, host count 2,… |
| a /31 point-to-point link: both addresses usable, no broadcast (RFC 3021) | 198.51.100.7/31 | → | cidr 198.51.100.6/31, network 198.51.100.6, prefix 31, netmask 255.255.255.254, wildcard 0.0.0.1, broadcast —, first host 198.51.100.6, last host 198.51.100.7, host count 2, addre… |
| a /32 is one host | 192.0.2.9/32 | → | cidr 192.0.2.9/32, network 192.0.2.9, prefix 32, netmask 255.255.255.255, wildcard 0.0.0.0, broadcast —, first host 192.0.2.9, last host 192.0.2.9, host count 1, address count 1 |
| the whole IPv4 space | 0.0.0.0/0 | → | cidr 0.0.0.0/0, network 0.0.0.0, prefix 0, netmask 0.0.0.0, wildcard 255.255.255.255, broadcast 255.255.255.255, first host 0.0.0.1, last host 255.255.255.254, host count 4,294,96… |
| prefix above 32 | 192.168.1.0/33 | → | error: is not a CIDR block |
Show the other 5 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| no prefix | 192.168.1.0 | → | error: is not a CIDR block |
| a leading zero in the prefix | 192.168.1.0/024 | → | error: is not a CIDR block |
| a trailing newline | 192.168.1.0/24 | → | error: is not a CIDR block |
| a non-ASCII digit in the prefix | 10.0.0.0/٨ | → | error: is not a CIDR block |
| IPv6 blocks are not described | 2001:db8::/32 | → | error: cidrInfo handles IPv4 blocks only |
cidrContains 18 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| inside a /24 | 192.168.1.0/24, 192.168.1.200 | → | true |
| the next /24 is outside | 192.168.1.0/24, 192.168.2.1 | → | false |
| the last address of a /8 | 10.0.0.0/8, 10.255.255.255 | → | true |
| 172.31 is inside 172.16.0.0/12 | 172.16.0.0/12, 172.31.0.1 | → | true |
| 172.32 is not (a naive /16 reading gets 172.16-only) | 172.16.0.0/12, 172.32.0.1 | → | false |
| /0 contains everything of its family | 0.0.0.0/0, 203.0.113.5 | → | true |
| /32 contains only itself | 192.0.2.9/32, 192.0.2.10 | → | false |
| IPv6 inside a /32 | 2001:db8::/32, 2001:db8:ffff::1 | → | true |
| IPv6 just outside a /32 | 2001:db8::/32, 2001:db9::1 | → | false |
| link-local /10 reaches febf | fe80::/10, febf::1 | → | true |
Show the other 8 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| link-local /10 stops before fec0 | fe80::/10, fec0::1 | → | false |
| a /33 splits a group: 7fff is in the lower half | 2001:db8::/33, 2001:db8:7fff::1 | → | true |
| a /33 splits a group: 8000 is in the upper half | 2001:db8::/33, 2001:db8:8000::1 | → | false |
| an IPv4-mapped IPv6 address is not in an IPv4 block | 10.0.0.0/8, ::ffff:10.0.0.1 | → | false |
| an IPv4 address is not in ::/0 | ::/0, 10.0.0.1 | → | false |
| a malformed address | 10.0.0.0/8, 10.0.0.256 | → | error: is not an IP address |
| a block without a prefix | 10.0.0.0, 10.0.0.1 | → | error: is not a CIDR block |
| an IPv6 prefix above 128 | 2001:db8::/129, 2001:db8::1 | → | error: is not a CIDR block |
isCidr 13 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| an IPv4 block | 10.0.0.0/8 | → | true |
| an IPv6 block | 2001:db8::/32 | → | true |
| /0 | 0.0.0.0/0 | → | true |
| IPv6 /128 | ::/128 | → | true |
| host bits set are still a block | 192.168.1.77/24 | → | true |
| IPv4 prefix 33 | 10.0.0.0/33 | → | false |
| IPv6 prefix 129 | ::/129 | → | false |
| a bare address | 10.0.0.0 | → | false |
| two slashes | 10.0.0.0/8/8 | → | false |
| a leading zero in the prefix | 10.0.0.0/08 | → | false |
Show the other 3 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a trailing newline | 10.0.0.0/8 | → | false |
| a zone id | fe80::1%eth0/64 | → | false |
| empty text | → | false |
prefixToNetmask 12 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| /24 | 24 | → | 255.255.255.0 |
| /0 | 0 | → | 0.0.0.0 |
| /8 | 8 | → | 255.0.0.0 |
| /12 | 12 | → | 255.240.0.0 |
| /20 | 20 | → | 255.255.240.0 |
| /25 | 25 | → | 255.255.255.128 |
| /30 | 30 | → | 255.255.255.252 |
| /31 | 31 | → | 255.255.255.254 |
| /32 | 32 | → | 255.255.255.255 |
| 33 is too long | 33 | → | error: prefix must be an integer from 0 to 32 |
Show the other 2 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| negative | -1 | → | error: prefix must be an integer from 0 to 32 |
| a fraction | 24.5 | → | error: prefix must be an integer from 0 to 32 |
netmaskToPrefix 12 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 255.255.255.0 is /24 | 255.255.255.0 | → | 24 |
| all zeros is /0 | 0.0.0.0 | → | 0 |
| all ones is /32 | 255.255.255.255 | → | 32 |
| 255.255.254.0 is /23 | 255.255.254.0 | → | 23 |
| 255.240.0.0 is /12 | 255.240.0.0 | → | 12 |
| 255.255.255.128 is /25 | 255.255.255.128 | → | 25 |
| 128.0.0.0 is /1 | 128.0.0.0 | → | 1 |
| 255.255.255.252 is /30 | 255.255.255.252 | → | 30 |
| a gap in the one bits | 255.0.255.0 | → | error: is not a contiguous netmask |
| a wildcard mask is not a netmask | 0.255.255.255 | → | error: is not a contiguous netmask |
Show the other 2 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a stray low bit | 255.255.255.1 | → | error: is not a contiguous netmask |
| not a dotted quad | 255.255.256.0 | → | error: is not a dotted-quad IPv4 address |
More from the author
- **Host bits are allowed and dropped.** `192.168.1.77/24` is how an interface address is written, and it describes `192.168.1.0/24`; `cidrInfo` returns the cleaned-up block in `cidr`. (Python's `ipaddress.ip_network` refuses it unless `strict=False`; this behaves like `strict=False`.) - **/31 and /32.** A /31 is a point-to-point link: RFC 3021 makes both addresses usable, so `hostCount` is 2, the hosts are the two addresses and `broadcast` is null. A /32 is one host, also with no broadcast. Everything from /0 to /30 has network and broadcast addresses reserved, so `hostCount` is `addressCount - 2`. - **Families do not mix.** `cidrContains("10.0.0.0/8", "::ffff:10.0.0.1")` is false: an IPv4-mapped IPv6 address is a different address as far as a socket is concerned. Convert it first if you mean the IPv4 address. - **Strict text.** Addresses follow `net.ipv4` and `net.ipv6` (no leading zeros, no zone ids); the prefix is ASCII decimal with no leading zero, 0-32 for IPv4 and 0-128 for IPv6. - `cidrInfo` is IPv4 only: an IPv6 /64 has 2^64 addresses, which no language here holds in an `int`, and "broadcast" has no IPv6 meaning. - Netmasks must be contiguous: `255.0.255.0` is refused, and so is a wildcard mask such as `0.0.0.255` (write the netmask, `255.255.255.0`).
`cidrBlock` (`cidr_block`) is exported for the group's own functions; it is not part of the contract.
Sources: RFC 4632 (CIDR) section 3.1; RFC 3021 (Using 31-Bit Prefixes on IPv4 Point-to-Point Links); RFC 4291 section 2.3 (IPv6 prefix notation).