The comparison harness: running it and adding a protocol¶
routing-compare (still also built as anthocnet-compare) is the ns-3
program behind every MANET-family number in this repository. It runs several routing protocols ("arms") on the same
seeds, with the same mobility, the same traffic and the same channel. It
reports per-seed rows that pair across arms, so a difference between two
protocols carries a confidence interval rather than an anecdote.
This page is for anyone who wants to use the harness: to rerun a published cell, to compare an existing protocol, or to add their own. The decisions behind it are in methodology.md and the metric definitions are in metrics.md.
← Benchmark index · Methodology · Metrics · v2.5.0 comparators
Status: protocol-agnostic since v2.6.0 (ADR-0027). The harness is its own ns-3 module,
routing-bench, which links only stock ns-3 and names no protocol. Every arm registers itself from its own module, so the harness builds and runs without AntHocNet.anthocnet-compareis the same program under its old name, kept at least through v3.x. The move reproduced every committed MANET-family cell byte for byte (control). Shipping it as its own package is planned for v2.7.0 (Packaging).
What it measures¶
One invocation runs every arm in --protocols on seeds
--firstRun … --firstRun+--runs−1. For each seed and arm it builds a fresh
ns-3 world: nodes, mobility, Wi-Fi channel and CBR or TCP flows. It installs
the arm, runs it and measures it with FlowMonitor and the harness's own trace
sinks. Arms never share a process state that could leak between them. Their
randomness is pinned through AssignStreams, so seed k gives every arm the
same node trajectories and flow schedule
(#352).
Built-in arms, as of v2.5.0:
| arm | what it is | module |
|---|---|---|
anthocnet |
this project's protocol | anthocnet |
aodv, olsr, dsdv |
stock ns-3 | routing-bench (stock arms) |
aomdv |
multipath AODV | aomdv (vendored) |
gpsr, gpcr, gpsr3d |
geographic routing and its VANET/FANET modes | gpsr (vendored) |
hwmp |
802.11s mesh path selection | routing-bench (stock ns-3 mesh) |
oracle |
shortest-path ground truth, an upper bound | oracle |
ara, termite, qrouting, beeadhoc |
ant-colony, stigmergic, learned and bee-inspired comparators (beeadhoc: v2.7.0, #589) |
adaptive |
routing-compare --PrintHelp lists the arms actually installed: the
--protocols help is built from the registry. With only routing-bench
installed, the harness has the four stock arms.
Scenario presets (--scenario=paper|fanet|vanet|disaster|…) set the field,
mobility and channel of each network family. Any flag overrides a preset.
Running it¶
With the modules installed into an ns-3 tree (make install-ns3
NS3DIR=/path/to/ns-3, or the release's anthocnet-ns3-modules-<ver>.tar.gz
unpacked into contrib/; routing-bench alone is enough for the stock arms):
cd /path/to/ns-3
./ns3 configure --enable-examples --enable-tests && ./ns3 build
./ns3 run "routing-compare --scenario=paper --runs=10 --time=900 \
--protocols=anthocnet,aodv,olsr --diag"
Useful flags:
--nNodes,--areaX/--areaY,--range;--mobility=rwp|ssrwp|gaussmarkov|manhattan|trace;--propagation=range|tworay|nakagami|urban;--flows,--cbrBps,--transport=udp|tcp,--flowsFile(explicit flows);--firstRunto split seeds across jobs;- any
--ns3::<TypeId>::<Attribute>=<value>to change one protocol's parameter.
./ns3 run "routing-compare --PrintHelp" lists everything.
The output is a summary table plus machine-readable marker lines:
##CONFIG##is the effective configuration.##RUN## <seed> <arm> pdr delay delay99 thrput nrl …is the per-seed row that pairing uses.##ROUTE##,##DROPID##,##COMMON##and##GOODPUT##cover route stability, the drop-cause identity, the identity-matched packet set and TCP goodput.# dropsis the drop-cause book, which must sum to about 100 %.
metrics.md defines every one of them. Treat the markers as the interface: the tools below read them, never the human table.
Reading the results¶
Never compare numbers by eye. The scripts do the arithmetic and the noise call:
| tool | does |
|---|---|
tools/bench/scenario_check.py preflight … |
checks a configuration before dispatch: partitioned field, saturated channel, single-hop degeneracy |
tools/bench/scenario_check.py results FILE |
plausibility checks on a result: PDR bounds, the drop book closing, delay99 ≥ mean |
tools/bench/bench_parse.py --ab A B --proto X |
paired per-seed deltas of one arm between two files, with t and bootstrap CIs and a noise verdict |
tools/bench/bench_parse.py --get FIELD --from run\|table\|csv\|paths --proto X - |
one field of one arm's result, read by name: a ##RUN## metric, a summary-table metric, a --csv column by its header name, or a # paths key |
tools/bench/comparator_view.py |
arm-versus-arm readings: anchors, and arms paired against committed control cells |
tools/bench/campaign.py plan/dispatch/collect |
a set of cells × arms run on the benchmark-cell workflow, throttled, then collected |
The CI gates in tools/checks/ read marker lines through the same reader,
with bench_parse.py --get, never by awk column position (#791). A marker
change the reader absorbs therefore cannot make a gate read the wrong number,
and tools/bench/test_bench_parse_query.py checks the reader against the old
awk expressions on every committed cell.
Adding a protocol¶
There are two ways in, depending on what you are bringing.
Your own ns-3 routing module¶
If your protocol already is an ns-3 Ipv4RoutingProtocol with an
Ipv4RoutingHelper, it needs no rewrite. Vendor it like
ns3/baselines/aomdv/ or ns3/baselines/gpsr/: its own directory, a
CMakeLists.txt built with build_lib, and a README carrying the fidelity
sheet. Then register it, as described below.
A per-hop, table-driven arm on the adaptive base¶
For a protocol written from a paper that keeps a per-neighbour table (ant
colony, stigmergic, Q-learning-style), ns3/baselines/adaptive/ gives you a
base class, ns3::adaptive::RoutingBase, that does all the ns-3 plumbing.
The arm writes only its policy. ARA, Termite and Q-routing are each one file
pair on top of it.
What the base does for you:
| plumbing | detail |
|---|---|
| control channel | UDP sockets per interface, unicast and broadcast, on the arm's port. The harness counts it as control traffic automatically |
| no-route hold | a packet without a next hop waits in a bounded queue (64 packets, 30 s) and is released when the arm learns a route, or dropped as a route drop |
| previous hop | PrevHopTag tells every hop which neighbour sent the packet, which ns-3's RouteInput does not |
| link failure | MAC retry-limit drops and unanswered ARP are turned into OnLinkFailure(neighbor), and the lost data packet is recovered and re-routed. Traces MacReinject and ArpReinject keep the drop book honest: an arm that re-routes is booked per packet, by terminal fate (metrics, #761) |
| randomness | one pinned random stream per node (AssignStreams) |
What the arm implements:
| hook | called when |
|---|---|
ControlPort() |
once: the arm's UDP port |
SelectNextHop(dst, src, prev, p) |
a data packet needs a next hop; return Ipv4Address() for "none" |
OnDataReceived(p, header, prev, backtracked) |
a data packet arrived from neighbour prev; learn from it, return false to drop it |
OnDataForwarded(p, header, next) |
optional: a data packet is being handed to next |
OnNoRouteInTransit(…) |
optional: no route at an intermediate node; return true to dispose of the packet yourself |
StartDiscovery(dst) |
data is held for dst and no route exists |
RecvControl(p, from, receiver) |
a control message arrived |
OnLinkFailure(neighbor) |
the link to neighbor is gone |
KnownNeighbors() |
the neighbours the failure resolver may match |
The base's services cover sending and flushing:
BroadcastControl,BroadcastControlJitteredandUnicastControlsend control messages.FlushQueue(dst)releases held packets once you have a route.DropQueued(dst)gives up on them.Rng()is the pinned random stream.
adaptive-tables.h holds reusable pheromone tables (geometric or exponential
decay, bounded with floor, ceiling and pruning) and the proportional draw.
ControlHeader is a 24-byte control header with type, hops, ttl, seq, origin,
target and a value.
The module links only stock ns-3 (core, network, internet, wifi). Start from
qrouting-routing-protocol.cc,
the smallest complete arm (about 270 lines), and install it with AdaptiveHelper("ns3::adaptive::<YourType>").
Registering an arm¶
An arm is a class derived from ns3::bench::Arm
(routing-bench-arm.h),
created fresh for every (seed, arm) run. Three hooks are required:
Name(), the--protocolstoken;InstallRouting(internet, run), which sets your routing helper on the stack;AssignStreams(run, stream), which pins your protocol's random streams and returns how many it took (#352).
The optional hooks cover everything else the harness used to special-case:
own devices (hwmp), post-stack wiring (gpsr), trace connections, refused
sends added back to the offered load (oracle, olsr), extra diagnostics,
self-checks and the replay. The stock arms in
stock-arms.cc
and the module arms are the worked examples; Q-routing's is a dozen lines
(adaptive-bench-arms.cc).
To join the harness, your module ships one file,
helper/<module>-bench-arms.cc, defining
ns3::bench::RegisterArms_<module>() (hyphens become underscores), which
calls RegisterArm("<name>", "<summary>", &MakeYourArm) for each arm. Compile
it into your module only when routing-bench is installed and enabled, the way
adaptive's CMakeLists
does. routing-compare finds the file at configure time, links your module and
calls the function at start-up. No harness source, check script or workflow
changes.
What still needs a line: your module in the --enable-modules lists
(tools/checks/check-allowlists.sh derives the module set from the tree and
fails on a list that is missing it).
The gates before a number counts¶
A protocol this project did not design must clear three gates before its numbers are published (AGENTS.md, Conventions). These keep comparisons honest: two vendored arms once compiled, passed CI and forwarded nothing (#425, #416).
- Fidelity sheet. Every parameter from the paper with its section, every
value the paper does not give marked as a repo choice, and every deviation
with its reason. The model is the
adaptiveREADME. - Delivery smoke (
tools/checks/check-arm-delivery.sh <ns3-dir>, on every PR; #439). Every arm inrouting-compare's--protocolshelp runs on a small static multi-hop field. It must deliver above a floor, use multi-hop paths, and close its drop book. - Anchor. The arm must reproduce its own paper's headline trend on a scenario matching the paper's. The rule is fixed in a pre-registration page before the run. A miss ships as a written infeasibility verdict, not as numbers.
The v2.5.0 comparators are the worked example: ARA passed; Termite and Q-routing missed and shipped as verdicts.
Packaging and what is planned¶
Today (v2.6.0):
- The release ships
anthocnet-ns3-modules-<ver>.tar.gz, six ns-3 contrib modules to unpack intocontrib/:routing-bench(the harness and the stock arms),anthocnet, and the comparison modulesaomdv,gpsr,oracleandadaptive. Keeprouting-benchand drop the rest to run the harness on the stock arms alone, or add your own arm module beside it. - The
adaptivemodule also works on its own in any ns-3 ≥ 3.36 tree. Copycontrib/adaptivealone and useAdaptiveHelperin your own program.
Planned (epic #764):
- v2.7.0: the harness and the
adaptivearms ship as their own release packages, the readers as a Python package with a versioned marker contract, and this guide becomes a bring-your-own-protocol tutorial with an arm template.
See the roadmap for the exit criteria.
Citing¶
Cite the harness and the comparators through the release DOI of the version
you used. The version DOIs are in
CONTRIBUTING.md;
v2.5.0 is 10.5281/zenodo.23748858. The concept DOI in CITATION.cff always
resolves to the latest release. Name the arms and the version: a number
measured at one version is not guaranteed at another
(provenance).