ADR-0028: core/ is dual-licensed MIT OR GPL-2.0-only¶
- Status: Accepted (maintainer decision 2026-10-29; decides #782). Implemented by the PR that closes #782 (checklist under Implementation); takes effect from the first release after v2.6.0. Amended the same day to extend the dual licence to the standalone tooling (below).
- Date: 2026-10-29
- Amends: the single repository licence (
LICENSE, GPL-2.0-only) for thecore/directory only. Every other directory is unchanged.
Context¶
Until now the whole repository has been GPL-2.0-only. That covers LICENSE,
CITATION.cff, the README badge, the contribution clause in CONTRIBUTING.md,
and the SPDX header on every source file, which docs/tools/check-headers.py
requires.
The question came up on 2026-10-29, when an external code index asked for the
ns-3 adaptive comparator module (#754) to be released under MIT. That request
was declined. The module includes ns-3 headers, builds only inside a GPL-2.0
ns-3 tree, and anything shipped from it is GPL anyway, so an MIT licence would
change nothing in practice.
That request exposed an asymmetry, though. core/ is different from everything
else in the repo:
- It includes no simulator header (golden rule 1, enforced by
check_invariants.sh). It depends only on its ports (IClock,IRng,INeighborProvider,ITimerScheduler, and the optionalILinkQuality/IKinematics, ADR-0024/0025). - It is the part with uses outside ns-3. These include another simulator (OMNeT++, #32), a real routing daemon, embedded firmware on a drone or vehicle, and the browser (ADR-0021). Some of those places cannot take GPL code: app stores that conflict with GPL, closed vendor SDKs, and permissively licensed simulators that will not mix licences.
- Under GPL-2.0-only, that reuse is closed off. Under MIT alone, the repository's history of copyleft would end for the one part that most needs it to stay open.
The adapters are in the opposite position. ns3/anthocnet/,
ns3/routing-bench/ and ns3/baselines/* are ns-3 modules; GPL-2.0 is the
licence of their host. aomdv and gpsr are third-party GPLv2 code (Copyright
IITP RAS) and cannot be relicensed by this project at all.
Why the repository was GPL in the first place. Through v1.9.0 the repo
carried a full NS-2 integration (ADR-0005): files patched into, and built
inside, the GPL NS-2 tree. Keeping the whole repository on NS-2's licence was
the simple and correct choice then. ADR-0002 split the algorithm into a
simulator-free core/, and v2.0.0 removed the NS-2 adapter (#307, ADR-0023).
Since then nothing in core/ comes from, or is built into, a simulator. The
licence was inherited from a constraint that no longer applies to this
directory.
Decision¶
core/ is licensed MIT OR GPL-2.0-only, recipient's choice. Every other
directory stays GPL-2.0-only.
- Scope. Every tracked file under
core/is covered, includingsrc/,include/,tests/(andtests/fuzz_corpus/),bench/and theCMakeLists.txtfiles. Every C++ file carriesSPDX-License-Identifier: MIT OR GPL-2.0-only. The build files and the binary fuzz corpus carry no header, as they never did;core/LICENSEcovers them. - Licence texts.
core/LICENSEholds the MIT text and says that the GPL-2.0 option is the rootLICENSE. The rootLICENSEstays the stock GPL text.LICENSING.mdat the root maps each directory to its licence.make install-ns3(and so the module tarball) copiescore/LICENSEnext to the core sources it lays intocontrib/anthocnet/core/andcontrib/oracle/core/. - One-way dependency, enforced. GPL code may depend on
core/, butcore/must never pull GPL-only code in. Golden rule 1 already makes this structural, sincecore/includes nothing from outsidecore/.docs/tools/check-headers.py(CI,lint.yml) makes it explicit. Everycore/C++ file must carry exactlyMIT OR GPL-2.0-only. Its angle includes must be C++ standard-library headers, and its quote includes must resolve to a file undercore/. So nons3/,web/or vendored-baseline header can get in, and neither can a third-party library. - Contributions. Contributions to
core/are accepted under both licences. Contributions elsewhere are accepted under GPL-2.0-only, as now.CONTRIBUTING.mdstates this per directory. - Combined artifacts. Combined artifacts are GPL-2.0, and that is intended:
- The ns-3 module tarball and install bundle ship
core/inside a GPL ns-3 module. - The learn site ships
core/linked with the GPLweb/adapter as WASM. - In both cases the shipped whole is distributed under GPL-2.0, while each
core/source file keeps its dual notice. Takingcore/out of the tree gives it back its MIT option. - No retroactive change. Every release up to and including v2.6.0 (and its Zenodo version DOI) remains GPL-2.0-only. The dual licence applies from the first release that carries this change.
Amendment (2026-10-29): the standalone tooling¶
tools/, ns3/tools/ and docs/tools/ are also MIT OR GPL-2.0-only.
The one exception is docs/tools/mkdocs_hooks.py, which stays GPL-2.0-only.
The C++ comparison harness, ns3/routing-bench/, stays GPL-2.0-only.
(Maintainer decision on #782, landed in the same PR as the core change.)
The tooling is in the same position as core/:
- It is standalone. These are 52 Python and shell programs that read logs,
CSVs and plans: benchmark parsing, the paired statistics and noise verdicts
(
tools/bench/stats_util.py,bench_parse.py), the campaign runner, the chart and summary scripts, the CI checks and the release scripts. None of them imports ns-3 or its bindings. Their imports are the standard library, each other, and permissive third-party packages (matplotlib, PyYAML). - It is the maintainer's own code. Every copyright line in those paths is the maintainer's.
- It has uses outside this repo. The noise verdicts and paired comparisons work for anyone benchmarking any protocol on any simulator.
Why the exceptions:
docs/tools/mkdocs_hooks.pyloadsweb/shell/inject.py(GPL-2.0-only) to render the site chrome. A dual-licensed file that loads GPL-only code has no usable MIT form, so the file stays GPL. It is listed inGPL_ONLYincheck-headers.py.ns3/routing-bench/is an ns-3 module. Its provenance is clean (anthocnet-compare.ccat v1.2.0 carries no third-party header and does not derive from ns-3'smanet-routing-compare.cc), so it could be relicensed. But it builds only inside a GPL ns-3 tree, so every artifact made from it is GPL-2.0 anyway. The same reasoning kept theadaptivemodule (#754) GPL-only.
What the amendment adds to Decision item 3 (the one-way rule): a
dual-licensed Python file that loads code by path (sys.path,
spec_from_file_location, import_module) must not name a GPL-only Python
root (web/shell, .claude/skills). The rule is narrow on purpose (#229).
It catches the way this repo actually loads code across directories, not every
possible indirection; review covers the rest. The data files in those roots
(campaign plans, anchors, fixtures, lsan.supp) carry no header, as before.
LICENSE-MIT at the root covers them, and LICENSING.md maps them.
Why dual, not MIT alone, and not "leave it"¶
- MIT is GPL-compatible, so an MIT option costs the ns-3 adapter nothing.
The adapter keeps linking
core/exactly as it does today, and the ns-3 App Store listing (#613) only needs a GPLv2-compatible licence. JOSS (#625) only needs an OSI licence, and both options are OSI-approved. - The
OR GPL-2.0-onlyhalf keeps the record intact. Every downstream user who receivedcore/under GPL-2.0 (all releases to date) keeps exactly those terms. GPL projects can takecore/with no licence analysis at all. - MIT alone was rejected. It is the same permissiveness with no continuity, and a single licence on a single directory with a different licence next to it reads as an accident. A disjunctive SPDX expression reads as a decision.
- Status quo was rejected because it leaves the reuse case above closed, which is the reason the core/adapter seam exists (ADR-0002, kept by ADR-0023).
Consequences¶
- Permanent. Code released under the MIT option can be used in proprietary products, and that grant cannot be withdrawn for the versions that carry it. Later versions could drop the MIT option, but the released ones keep it.
- Provenance is a gate, not a courtesy. Only the copyright holder can add
a licence. The maintainer confirmed on 2026-10-29 (#782) that every line
under
core/is their own, including the pre-core implementation thatcore/include/anthocnet/core/ports.h:31calls "the original code", and that nothing was carried in from NS-2 or any other third-party code. A spot check ofcore/at the v1.2.0 tag agrees. Every copyright line there is the maintainer's. The only third-party text is citations of the Ducatelle thesis and papers in comments (short quotations of the source the algorithm is built from, not code). Any future core file from someone else needs the same confirmation before it lands. - AI-assisted commits. Many
core/commits carry aCo-Authored-By: Claude …trailer. They do not block relicensing by the sole human author: the author can license whatever rights they hold. The copyrightability of AI-generated code is legally unsettled, so the grant is "whatever rights the author holds", which is the same position as the existing GPL grant. - A new external contributor to
core/must agree to both licences, which the per-directory clause inCONTRIBUTING.mdcovers. A contribution that cannot be dual-licensed belongs outsidecore/or is declined. - Tooling learns per-directory licences.
check-headers.pystill accepts any SPDX expression elsewhere. Under the dual roots (core/,tools/,ns3/tools/,docs/tools/, minusGPL_ONLY) it requires the exact expression from Decision item 1, and it now also covers.shfiles in the tooling roots.CITATION.cfflists both SPDX ids, because CFF takes a list, not an expression; the per-directory split is in a comment there and inLICENSING.md. The release bundle gate (tools/release/check-bundle.sh) checks thatLICENSE,LICENSE-MIT,LICENSING.mdandcore/LICENSEall ship. - The adapters are untouched. This ADR adds no licence option to the ns-3
modules under
ns3/(anthocnet,routing-bench,baselines/*),web/,docs/pages,results/,api/orpaper/.
Implementation¶
Tracked in #782; one PR:
- [x] Provenance confirmed by the maintainer (Consequences, above).
- [x] SPDX headers on all 62
core/C++ files set toMIT OR GPL-2.0-only. - [x]
core/LICENSEadded (MIT text plus a pointer to the rootLICENSEfor the GPL option);LICENSING.mddirectory map added;make install-ns3copiescore/LICENSEwith the core sources. - [x]
docs/tools/check-headers.pyrequires the dual expression undercore/and rejectscore/includes from outsidecore/, with self-test cases for each rule. - [x]
CITATION.cff, the README licence section and badge, and theCONTRIBUTING.mdcontribution clause updated. - [x]
tools/release/check-bundle.shasserts the licence texts are in the package;assemble-bundle.shshipsLICENSING.md. - [x] Amendment: 52 tooling scripts set to
MIT OR GPL-2.0-only(38 Python and shell headers rewritten, plus a header added to the 14tools/checks/*.shandns3/tools/run-comparison.shscripts that had none).LICENSE-MITadded.check-headers.pyextended: dual roots,.shcoverage, theGPL_ONLYexception and the Python load rule, each with self-test cases.LICENSING.md, README,CONTRIBUTING.md,CITATION.cff,mkdocs.ymland the bundle scripts updated.
References¶
- #782: the decision issue.
- ADR-0023: one core, one simulator adapter, with the seam kept.
- ADR-0021: the browser is an adapter.
ns3/baselines/aomdv/README.md,ns3/baselines/gpsr/README.md: third-party GPLv2 provenance.- SPDX licence expressions; GNU licence list, Expat (GPL-compatible).