IP addresses and networks for MoonBit: IPv4, IPv6, prefixes and how to classify them. Parsing and arithmetic only, no sockets.
moon add moonbitstack/moonipfn main raise {
let block = @moonip.Prefix::parse("192.0.2.0/24")
println(block.netmask()) // 255.255.255.0
println(block.contains(@moonip.Addr::parse("192.0.2.7"))) // true
println(block.subnets(bits=26).collect().length()) // 4
}| Type | What it is | Made by |
|---|---|---|
| Addr | One IPv4 or IPv6 address, plus the zone a scoped IPv6 address may carry | Addr::parse, Addr::decode, Addr::v4, Addr::v6 |
| Prefix | An address and a mask length | Prefix::parse, Prefix::new |
| AddrPort | An address and a port | AddrPort::parse, AddrPort::new |
let a = @moonip.Addr::parse("2001:0DB8:0000:0000:0000:0000:0000:0001")
assert_eq(a.to_string(), "2001:db8::1")
assert_eq(a.exploded(), "2001:0db8:0000:0000:0000:0000:0000:0001")
assert_eq(a.encode().length(), 16)
assert_eq(@moonip.Addr::parse("::ffff:192.0.2.1").unmap().to_string(), "192.0.2.1")let block = @moonip.Prefix::parse("192.0.2.0/24")
assert_eq(block.masked().to_string(), "192.0.2.0/24")
assert_eq(block.netmask().to_string(), "255.255.255.0")
assert_eq(block.hostmask().to_string(), "0.0.0.255")
assert_eq(block.broadcast().map(@moonip.Addr::to_string), Some("192.0.2.255"))
assert_eq(@moonip.Prefix::parse("2001:db8::/32").broadcast(), None) // IPv6 has none| Call | Answer |
|---|---|
| contains, overlaps | whether an address or another block meets this one |
| is_subnet_of, is_supernet_of | which of the two holds the other |
| hosts | the addresses an interface can be given, lazily |
| subnets, supernet | split down, or the block that holds this one |
| exclude | the parts of a block outside another one |
| summarize, collapse | the fewest blocks holding a range or a set |
| num_addresses, is_single_ip | how many addresses there are, or whether there is one |
let parts = @moonip.summarize(
@moonip.Addr::parse("192.0.2.0"),
@moonip.Addr::parse("192.0.2.130"),
)
assert_eq(parts.map(@moonip.Prefix::to_string), [
"192.0.2.0/25", "192.0.2.128/31", "192.0.2.130/32",
])| Variant | Meaning |
|---|---|
| Malformed(at~) | the text stopped making sense at character at, counted from 0 |
| OutOfRange(limit~, got~) | a number did not fit: a mask length or a port |
| Length(want~, got~) | a byte string was the wrong size |
| HostBits | host bits set below the mask while strict forbade it |
| Family | IPv4 against IPv6 |
| Reversed | a range that ends before it starts |
pub(all) suberror Refused {
Malformed(at~ : Int)
OutOfRange(limit~ : Int, got~ : Int)
Length(want~ : Int, got~ : Int)
HostBits
Family
Reversed
}pub struct Addr {
hi : UInt64
lo : UInt64
v4 : Bool
zone : String?
}test {
assert_eq(
@moonip.Addr::parse("2001:0DB8:0000:0000:0000:0000:0000:0001").to_string(),
"2001:db8::1",
)
assert_eq(
@moonip.Addr::parse("::ffff:192.0.2.1").to_string(),
"::ffff:192.0.2.1",
)
}test {
assert_eq(
@moonip.Addr::parse("2001:db8::1").exploded(),
"2001:0db8:0000:0000:0000:0000:0000:0001",
)
}test {
assert_true(@moonip.Addr::parse("8.8.8.8").is_global())
assert_true(@moonip.Addr::parse("2001:4860::1").is_global())
assert_false(@moonip.Addr::parse("100.64.0.1").is_global())
assert_false(@moonip.Addr::parse("100.64.0.1").is_private())
}test {
assert_true(@moonip.Addr::parse("ff01::1").is_interface_local_multicast())
assert_false(@moonip.Addr::parse("ff02::1").is_interface_local_multicast())
}test {
assert_true(@moonip.Addr::parse("224.0.0.251").is_link_local_multicast())
assert_true(@moonip.Addr::parse("ff02::fb").is_link_local_multicast())
assert_false(@moonip.Addr::parse("239.1.1.1").is_link_local_multicast())
}test {
assert_true(@moonip.Addr::parse("10.1.2.3").is_private())
assert_false(@moonip.Addr::parse("8.8.8.8").is_private())
assert_true(@moonip.Addr::parse("fd00::1").is_private())
}test {
assert_eq(
@moonip.Addr::parse("192.0.2.1").reverse_pointer(),
"1.2.0.192.in-addr.arpa",
)
assert_eq(
@moonip.Addr::parse("::1").reverse_pointer(),
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
)
}test {
let end = @moonip.AddrPort::parse("[::1]:443")
assert_eq(end.to_string(), "[::1]:443")
assert_eq(end.port(), 443)
}test {
let parts = @moonip.Prefix::parse("192.0.2.0/28").exclude(
@moonip.Prefix::parse("192.0.2.1/32"),
)
assert_eq(parts.length(), 4)
assert_eq(parts[0].to_string(), "192.0.2.8/29")
}test {
let usable = @moonip.Prefix::parse("192.0.2.0/30").hosts().collect()
assert_eq(usable.length(), 2)
assert_eq(usable[0].to_string(), "192.0.2.1")
}test {
let wide = @moonip.Prefix::parse("10.0.0.0/8")
assert_true(wide.is_supernet_of(@moonip.Prefix::parse("10.1.0.0/16")))
assert_false(wide.is_supernet_of(@moonip.Prefix::parse("11.0.0.0/8")))
}test {
assert_eq(@moonip.Prefix::parse("192.0.2.0/24").num_addresses(), Some(256UL))
assert_eq(@moonip.Prefix::parse("2001:db8::/32").num_addresses(), None)
}test {
let subnet = @moonip.Prefix::parse("192.0.2.0/24")
assert_eq(subnet.to_string(), "192.0.2.0/24")
assert_eq(
@moonip.Prefix::parse("192.0.2.5/24", strict=false).to_string(),
"192.0.2.0/24",
)
}test {
let halves = @moonip.Prefix::parse("10.0.0.0/8").subnets(bits=9).collect()
assert_eq(halves.length(), 2)
assert_eq(halves[1].to_string(), "10.128.0.0/9")
}test {
assert_eq(
@moonip.Prefix::parse("192.0.2.0/24").supernet(bits=16).to_string(),
"192.0.0.0/16",
)
}test {
let halves = [
@moonip.Prefix::parse("192.0.2.0/25"),
@moonip.Prefix::parse("192.0.2.128/25"),
]
let whole = @moonip.collapse(halves)
assert_eq(whole.length(), 1)
assert_eq(whole[0].to_string(), "192.0.2.0/24")
}test {
let blocks = @moonip.summarize(
@moonip.Addr::parse("192.0.2.0"),
@moonip.Addr::parse("192.0.2.130"),
)
assert_eq(blocks.length(), 3)
assert_eq(blocks[0].to_string(), "192.0.2.0/25")
}Install
Download zipIP addresses and networks for MoonBit: IPv4, IPv6, prefixes and how to classify them. Parsing and arithmetic only, no sockets.