cidrmerge is an offline policy compiler and reusable Swift library. It turns
IPv4 and IPv6 addresses, networks, and inclusive ranges into a deterministic,
minimal exact address cover. It uses
swift-cidr as the canonical
source for IP parsing, normalization, containment, network construction, and
range-to-CIDR summarization.
The compiler is intended for admission policies, ACL preparation, and other control-plane pipelines. It coalesces only duplicate, contained, overlapping, or adjacent coverage; it never fills an uncovered gap to make output shorter.
- Swift 6.1 or newer when building from source or using the
CIDRMergeCorelibrary through SwiftPM - macOS 15 or newer, or Ubuntu 22.04 or newer, to run the command-line executable
- iOS 18 or newer when using
CIDRMergeCorein an application
The package is verified against the declared compatibility floors of
swift-cidr 0.5.0 and Swift Argument Parser 1.7.0. Package.resolved records
the exact dependency revisions used for release verification.
GitHub Releases provide native archives for macOS and Linux on ARM64 and x86-64. Select the archive for the current host, download it together with the published checksum file, and verify it before installation:
version=0.1.0
case "$(uname -s)-$(uname -m)" in
Darwin-arm64) platform=darwin-aarch64 ;;
Darwin-x86_64) platform=darwin-x86_64 ;;
Linux-aarch64) platform=linux-aarch64 ;;
Linux-x86_64) platform=linux-x86_64 ;;
*) printf 'Unsupported host: %s-%s\n' "$(uname -s)" "$(uname -m)" >&2; exit 1 ;;
esac
asset="cidrmerge-${version}-${platform}.tar.gz"
base_url="https://github.com/RouteObjects/cidrmerge/releases/download/${version}"
curl --fail --location --remote-name "${base_url}/${asset}"
curl --fail --location --remote-name "${base_url}/SHA256SUMS"
awk -v name="${asset}" '$2 == name' SHA256SUMS >"${asset}.sha256"
test -s "${asset}.sha256"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum --check "${asset}.sha256"
else
shasum -a 256 --check "${asset}.sha256"
fiExtract the verified archive and install the executable in a directory on
PATH:
stage="cidrmerge-${version}"
mkdir "${stage}"
tar -C "${stage}" -xzf "${asset}"
mkdir -p "${HOME}/.local/bin"
install -m 0755 "${stage}/cidrmerge" "${HOME}/.local/bin/cidrmerge"
"${HOME}/.local/bin/cidrmerge" --versionEach archive also contains LICENSE and THIRD_PARTY_NOTICES.txt. Review and
retain those files with redistributed copies of the executable.
Clone the repository and build a release executable:
git clone https://github.com/RouteObjects/cidrmerge.git
cd cidrmerge
swift build -c release --product cidrmerge
.build/release/cidrmerge --versionRun the test suite and executable smoke test with:
./scripts/test.sh
./scripts/smoke-test.shThe SwiftPM executable product, source-built binary, and command are all named
cidrmerge.
With no file operands, cidrmerge reads line-oriented text from standard
input. For example:
printf '%s\n' \
192.0.2.0/25 \
192.0.2.128/25 \
2001:0DB8::1/64 \
| cidrmerge --statsStandard output contains IPv4 first and IPv6 second:
192.0.2.0...192.0.2.255
2001:db8::...2001:db8::ffff:ffff:ffff:ffff
Statistics are written to standard error, keeping standard output safe for a pipeline:
input: 3 entries (2 IPv4, 1 IPv6)
normalized: 1 entry
output: 2 ranges (1 IPv4, 1 IPv6)
reduction: 1 entry (33.3%)
- A bare IPv4 address means one
/32; a bare IPv6 address means one/128. - Slash-qualified input has network semantics. For example,
192.0.2.129/24canonicalizes to the coverage of192.0.2.0/24. - An inclusive range uses exactly
lower...upper, with address-only endpoints from the same family. Reversed bounds and CIDR-qualified endpoints are rejected. - Blank lines and
#comments are ignored. - Additional BGP or RPKI columns are rejected rather than silently discarded.
- Pass local files as operands. Use
-at most once to combine standard input with files. - HTTP and HTTPS operands are rejected. Download changing inputs separately
with
curl, CI tooling, or another acquisition step.
Every accepted value becomes an inclusive first-to-last address interval. Intervals are partitioned by family, numerically sorted, and coalesced. This retains exact address membership rather than input provenance: original prefix lengths and fragmentation cannot be reconstructed after union.
The default ranges representation emits the fewest disjoint closed intervals
needed to express the exact union. Request canonical CIDR output explicitly:
printf '%s\n' 192.168.2.2/31 192.168.2.4/30 \
| cidrmerge --representation cidr192.168.2.2/31
192.168.2.4/30
--representation ranges|cidr selects coverage representation. This is
orthogonal to serialization:
- Raw line-oriented output is the default;
--rawselects it explicitly. --jsonor-jemits structured JSON.--rawand--jsonare mutually exclusive.-o, --output <path>atomically replaces a file instead of writing stdout.--statsreports statistics for the selected representation on stderr.--versionprints the release version.-vis intentionally unassigned.
JSON records the selected representation and preserves separate, deterministic family arrays:
{
"ipv4" : [
"192.0.2.0/24"
],
"ipv6" : [
"2001:db8::/32"
],
"representation" : "cidr"
}Output is buffered until all input has parsed, merged, and rendered, so a failure in those stages emits no standard output. File destinations are atomically replaced where supported, and standard-output write failures are surfaced even though a stream write cannot be rolled back.
Add cidrmerge and swift-cidr to another Swift package:
dependencies: [
.package(
url: "https://github.com/RouteObjects/cidrmerge.git",
.upToNextMinor(from: "0.2.0")
),
.package(
url: "https://github.com/RouteObjects/swift-cidr.git",
.upToNextMinor(from: "0.5.0")
),
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "CIDRMergeCore", package: "cidrmerge"),
.product(name: "CIDR", package: "swift-cidr"),
]
),
]Applications use swift-cidr values directly, so declare its package and
CIDR product explicitly instead of relying on a transitive dependency.
swift-cidr owns IPAddressRange<Family>, AnyIPAddressRange, and
IPAddressCoverage<Family>. The family-bound coverage type normalizes any
sequence of intervals into an immutable exact union and provides logarithmic
containment lookup.
Inside a throwing context, handle textual configuration errors explicitly:
import CIDR
import CIDRMergeCore
enum PolicyInputError: Error {
case invalidValue(String)
}
func parseIPv4Network(_ text: String) throws -> IPv4Network {
guard let network = IPv4Network(text) else {
throw PolicyInputError.invalidValue(text)
}
return network
}
func parseIPv4Address(_ text: String) throws -> IPv4Address {
guard let address = IPv4Address(text) else {
throw PolicyInputError.invalidValue(text)
}
return address
}
func parseRange(_ text: String) throws -> AnyIPAddressRange {
guard let range = AnyIPAddressRange(text) else {
throw PolicyInputError.invalidValue(text)
}
return range
}
let prefixes = try ["192.0.2.0/25", "192.0.2.128/26"].map {
try parseIPv4Network($0)
}
let coverage = IPAddressCoverage(covering: prefixes)
coverage.ranges.map(\.description)
// ["192.0.2.0...192.0.2.191"]
coverage.summarizedNetworks().map(\.description)
// ["192.0.2.0/25", "192.0.2.128/26"]
coverage.contains(try parseIPv4Address("192.0.2.190")) // true
coverage.contains(try parseIPv4Address("192.0.2.192")) // falseIPAddressRange.summarizedNetworks() and
IPAddressCoverage.summarizedNetworks() delegate CIDR decomposition to
swift-cidr. They guarantee the same address membership, not the same prefix
lengths as the input. AnyIPAddressRange is the family-erased boundary for
parsing or mixed-family collections; keep algorithms family-bound when the
family is known statically.
CIDRMergeCoverage is cidrmerge's mixed-family facade. It normalizes each
family independently and always exposes IPv4 before IPv6:
let mixedRanges = try [
"2001:db8::...2001:db8::ffff",
"192.0.2.0...192.0.2.191",
].map { try parseRange($0) }
let mixed = CIDRMergeCoverage(ranges: mixedRanges)
mixed.ranges.map(\.description)
// ["192.0.2.0...192.0.2.191", "2001:db8::...2001:db8::ffff"]
mixed.summarizedNetworks().map(\.description)
// ["192.0.2.0/25", "192.0.2.128/26", "2001:db8::/112"]
mixed.contains(AnyIPAddress(try parseIPv4Address("192.0.2.190"))) // trueBeginning with the 0.2 line, CIDRMergeCore no longer provides the range and
coverage types that it declared in 0.1. Import CIDR and use the canonical
swift-cidr declarations directly. This deliberate pre-1.0 source break keeps
one owner for address, network, range, containment, normalization, and
summarization behavior.
cidrmerge operates on address coverage only. Range output is optimized for
containment indexes, not route advertisement. Before using BGP- or
RPKI-derived data, explicitly project it to prefixes. The output is not a route
advertisement or ROA and does not preserve ASN, AS path, community,
maxLength, TAL, source, or validation state.
The compiler is deliberately offline. Vendor feed acquisition, DNS, IRRd/RPKI
queries, admission-policy hot reload, and named vendor policy generation do not
belong in the 0.1 package boundary. Local-file and stdin parsing for a future
searchbot schema and admission-policy output remain 0.2 design work.
Run deterministic end-to-end corpora with:
CIDRMERGE_BENCHMARK_COUNT=10000 ./scripts/benchmark.sh siblings bgp-likeThe release acceptance run uses one million inputs per scenario and a 512 MiB peak-RSS ceiling. Benchmarks/README.md records timing, memory, output cardinality, compression, and the dated Google common-crawler example. That 2026-07-13 snapshot compiled 315 prefixes to 38 ranges or 65 CIDR prefixes without changing address coverage; live vendor data can change.
cidrmerge is part of the RouteObjects
Swift for Network Infrastructure toolkit:
- swift-cidr — Strongly typed IP address, network, and CIDR primitives for Swift.
- asroutes — SwiftNIO IRRd client and CLI for direct-ASN IPv4 and IPv6 origin-route lookups.
- cidrmerge — Deterministic exact coverage compilation into minimal address-range or CIDR representations.
- swift-cidr-admission — CIDR-based admission policies for Swift services.
- cidrwalk — Address-range and CIDR traversal and inspection utility.
cidrmerge is available under the Apache License 2.0. See LICENSE.
Resolved dependency and binary-runtime attribution information is recorded in
THIRD_PARTY_NOTICES.txt.