Skip to content

release: ipmininet v1.3.1 — revive upstream - #131

Open
iakov wants to merge 78 commits into
cnp3:masterfrom
iakov:upstream-v1.3.1
Open

iakov wants to merge 78 commits into
cnp3:masterfrom
iakov:upstream-v1.3.1

Conversation

@iakov

@iakov iakov commented Sep 10, 2026

Copy link
Copy Markdown

release: ipmininet v1.3.1 — revive upstream

A note of thanks

First and foremost, I want to extend my heartfelt thanks to the IPMininet
authors, maintainers, and community. This project has been a lifesaver — in
the classroom and in research alike — turning protocol theory into something
students could actually watch converge, break, and heal. It is my genuine
pleasure to give back everything we have built on top of it downstream, and an
even greater pleasure to think the maintainers might fold these improvements
into the project so the whole community can benefit. The work in this PR comes
from the downstream fork mimi-net/ipmininet
(my branch:
iakov/ipmininet@upstream-v1.3.1),
and it carries the effort of everyone who contributed there. On behalf of our
team, I offer it with deep gratitude — and real excitement for where IPMininet
goes next.

Summary

This PR brings cnp3/ipmininet from its 2023 state (last push 2023-01-18, last
release v1.1) up to the maintained downstream release v1.3.1. It is a large
but mostly mechanical delta — 78 commits, 156 files, +11,391/-6,295, much of
it tests and CI — contributed from the downstream fork
mimi-net/ipmininet. It returns that
work upstream, unblocks several open issues, and restores the repository to a
state that installs and passes CI on a modern machine. We are happy to split or
drop any part of this on maintainer request.

At a glance

Delta 78 commits, 156 files, +11,391 / -6,295
Version 1.2.x -> 1.3.1 (culminating tag v1.3.1)
Python >= 3.12 (pyproject; setup.py retired)
FRRouting 10.7.1, mgmtd-based per-node config (requires FRR >= 9)
ExaBGP 5.0.13, installed via pip (apt 4.x dropped)
Tests full suite 282 passed; branch-coverage gate 90, measured 92.29%
Duplicated code gate at 0 (non-example)
Docs Sphinx build with -W, CI job green

Breaking changes and migration (read first)

  • Python >= 3.12 (old 3.6-3.9 classifiers removed).
  • Build system: setup.py replaced by pyproject.toml + uv.lock
    (setuptools backend); install with pip install . or uv sync.
  • FRRouting 10.7.1 with mgmtd: generated configs target FRR >= 9, and the
    installer builds FRR 10.7.1 from source. Existing environments must be
    re-provisioned: sudo python -m ipmininet.install -af.
  • ExaBGP 5.0.13 is installed via pip; the old apt-based 4.x path is gone.
  • OpenR support removed (end-of-life daemon); pbrd/pathd remain
    unimplemented and are not added here.
  • Container image moved to Ubuntu 26.04 (PCRE1 built from source).
  • Rollback: a straight revert to cnp3/master. BGP set-action defaults are
    unchanged, so generated configs are byte-identical unless order is used.

What's in it

Switching / data plane

  • Hub switches fixed (brctl setageing 0); STP/RSTP support and priority;
    VLAN-filtering cleanup; link packet duplication.
  • Bringing an interface back up restores only the missing addresses (avoids
    address churn and zebra/ospfd races); faster, reliable IPv4-only startup.

DHCP / services

  • New dnsmasq DHCP daemon with per-interface selection, a DHCP-relay
    implementation, and mimidump capture tooling.

BGP / routing config

  • set_local_pref, set_med and set_community accept an explicit route-map
    order (parity with deny/permit/filter).
  • Fixed a latent AttributeError in the BGP config builder when two set
    actions on one peer used different match conditions; zebra
    PrefixListEntry("any") now sets .ge correctly.

Capture

  • wait_until_capturing() gains strict=True; header size is derived from
    magic bytes (pcap/pcapng), accepting slow-grow and bursty writers.

Platforms / build

  • Ubuntu 26.04 container (PCRE1 from source); mininet dependency pinned to the
    maintained fork; SRv6/IPv6 connectivity waits; Python >= 3.12 / pyproject+uv.

Quality / CI

  • Parallel, re-architected test suite (rootless + bare-metal + container CI);
    branch-coverage gate 90 (measured 92.29%); zero duplicate-code gate.
  • ruff / ruff format / mdformat with pre-commit hooks; docs built under
    uv with -W; dependabot for Actions and Docker.

Open upstream issues addressed or clarified

Issue Status after this PR
#99 set_local_pref not flexible enough Fixed: explicit order + crash fix
#130 OSPF6D config-check error Resolved on FRR 10.7.1 (18.04 below floor)
#107 BGP filter pitfalls Superseded by the ordered route-map model
#121 OpenFlow switch IPOVSSwitch exists and is documented
#122 call_action/exit_policy Rendered correctly by the current template
#125 Hub not working Hub fix present (brctl setageing 0)
#128 hosts can't ping Convergence; poll for reachability
#129 modify FRR config/vtysh Per-node /var/run/frr + mgmtd push
#120 install on Ubuntu 22.04 Fixed by pinned mininet + PEP-517/668 handling
#104 HMAC TLV post_build() remains the intended hook
#28 / #116 / #127 OpenR Daemon removed; no longer applicable
#73 PBR / #108 PCEP Still open feature gaps (not in this PR)

We are not closing any of these from this side; happy to help triage once the
PR lands.

Validation

  • PR CI (v1.3.1, mimi-net): rootless, docs, test (bare metal) and
    container-test all green.
  • Post-merge master heavy suite: TOTAL 92.29%, 282 passed
    (run 34441918630); container-test 34441918620.
  • Local gates: ruff check/format, mdformat --check and
    scripts/check-duplicates.sh (0 blocks) clean.
  • Reproduce: uv sync --all-extras then pytest, or
    COVERAGE=1 scripts/run-tests-parallel.sh as root.

For review, the diff is easiest in this order: pyproject.toml/uv.lock +
.github/workflows/* (build/CI) -> ipmininet/router/config/* (daemons) ->
ipmininet/ipswitch.py, ipovs_switch.py, install/ (switch/DHCP) ->
ipmininet/tests/*. Most added lines are uv.lock and tests; the rest is
lint/format churn. Note: GitHub's compare view under-reports a few large files
(e.g. uv.lock shows as added with 0 changes); use
git diff --stat 72cefde..<head> for exact numbers.

Notes

Checklist

  • Tests pass (rootless + bare metal + container)
  • Docs build with -W
  • Lint/format and duplicate-code gates clean
  • Migration notes included (this description)
  • No fork-only files (agentic/, .tmp/, scratch) included
  • Maintainer approval / merge as merge commit (preserve history)

Thank you again for building and caring for IPMininet — we are grateful for the
chance to contribute, and excited for what comes next.

References

MinyazevR and others added 30 commits November 2, 2023 02:54
Mimidump added
Добавил RSTP для switch
Add stp and rstp priority
Изменил устаревший setup.py на pyproject.toml
Добавил выбор интерфейсов в Dnsmasq
Изменила источник mininet
* build: migrate to uv package manager

uv replaces pip/setuptools for dependency management and virtualenv
handling. .python-version pins the Python version and uv.lock is the
lockfile. The build backend stays setuptools; no other tooling changes
are introduced here.

* test: add rootless local test runner and pure unit tests

- scripts/run-tests-local.sh: run the suite as a non-root user; root-only
  tests are skipped via the existing require_root marker
- tests/test_pure.py: new rootless tests for _parse_addresses, is_subnet_of,
  is_container, get_set, ConfigDict and ip_statement
- test_misc: mark test_ip_address_format as requiring root (it calls
  'ip link set dev lo up')

* fix(install): make -a work with uv and inside containers

- install_mininet: add a minimal path used by `-a` that only builds and
  installs the mnexec binary; the full install.sh -a installs packages that
  no longer exist on Ubuntu 24.04 and the Python package is provided by uv.
  OVS now comes from the openvswitch-switch apt package.
- create /run/sshd for openssh-server
- skip the IPv6 grub configuration when /etc/default/grub is absent, as it
  is inside a container
- parse pip versions with packaging.version instead of pkg_resources

* fix(tests): fix pre-existing failures and skip daemon-dependent tests only when missing

- test_link: 2002::/16 (6to4) is now classified private by ipaddress in
  Python 3.12, so the global 2001::/16 sorts first
- test_exabgp: fix a wrong self-comparison (asn_received == asn_received)
- Replace the unconditional @pytest.mark.skip on the openr, exabgp and
  network_capture tests with conditional markers (require_openr,
  require_exabgp, require_mimidump) in tests/__init__.py. The tests now run
  wherever the daemon is available and skip with a reason otherwise; ExaBGP
  additionally requires the daemon to actually start (4.2.11 ships a broken
  vendored six).
- Document the skip policy in tests/__init__.py and point to it from
  docs/contribute.rst.

* ci: add containerized, rootless and heavy test workflows

Three workflows replace the single test entry point:

- test.yaml: bare-metal CI with a rootless job (runs the pure/rootless
  tests as the non-root runner user via the require_root marker) and a
  root job running the full suite after 'ipmininet.install -a'.
- container-test.yaml: builds the self-contained Containerfile (uv venv
  in /opt/venv + full install) and runs a test subset inside it.
- heavy-test.yaml: full suite on master and on demand.

The full-suite jobs collect every test file; daemon-dependent tests
skip themselves via the conditional markers in tests/__init__.py, so
their reasons show up in pytest -rs output instead of being dropped
with --ignore.

Lint and format checks are intentionally not included; they will be added
with the follow-up reformatting PR.

* chore: add dependabot configuration

Monthly grouped updates for uv (Python deps via uv.lock), GitHub Actions
and Docker (Containerfile) ecosystems.

* fix: remove -t / from named startup line for userns compatibility

The '-t /' chroot makes named's working directory '/' which a
user-namespace root cannot write, causing 'loading configuration:
permission denied' inside rootless podman containers. The chroot to
/ is a no-op in rootful environments; removing it fixes named in
podman while preserving rootful behavior.

* fix: run node daemons in the node working directory

The DNS tests (test_dns.py) were flaky in CI: 6 tests failed with
"connection refused" on port 53 because named exited at startup. Root
cause, reproduced locally in Docker and podman:

- Daemons are launched via node.popen() (mininet), which runs the command
  in the node network namespace but keeps the *pytest process* cwd. On CI
  that cwd is the runner checkout / mounted workspace, owned by a
  non-root user (runner / ubuntu).
- named is started with `-u root`, which makes BIND drop CAP_DAC_OVERRIDE.
  A uid-0 process without CAP_DAC_OVERRIDE can only write directories it
  owns or that are world-writable; it cannot write session.key /
  _default.tsigkeys into a 755 non-root-owned cwd, so named dies with
  "loading configuration: permission denied".
- This never reproduced locally under podman because rootless podman maps
  the container root onto the host user, making the workspace root-owned.
- The earlier `-t /` removal (commit 0fb8522) was correct: named chdirs to
  "/" after chroot, and a user-namespace root cannot write "/", so `-t /`
  breaks rootless podman. But in rootful CI the chroot made the effective
  working directory "/" (root-owned), which masked the bug above; without
  it the real failure surfaced.

Changes
-------
- ipmininet/router/__router.py: spawn node daemons with cwd=self.cwd (the
  node working directory, world-writable /tmp by default). named now has a
  writable cwd even after dropping CAP_DAC_OVERRIDE, in all environments.
- .github/workflows/container-test.yaml: add ipmininet/tests/test_dns.py to
  the containerized job so DNS tests are covered there too (~5.5 min
  added; the job stays far below the 60 min timeout).

A previous attempt installed apparmor-utils and changed the named comment,
based on a wrong AppArmor hypothesis (that a confined named was the cause).
It changed nothing in CI results and is reverted here; the daemon cwd was
the real issue. Noted so the decision can be re-validated later.

* fix(ci): create /run/sshd before running the container test suite

sshd -t (the SSHd daemon dry_run check) requires the privilege separation
directory /run/sshd. It is created by install -a at image build time but is
lost between the image build and the container start, so test_sshd failed
in the container with SystemExit 1. Create it in the test entrypoint;
bare-metal CI was unaffected because install -a runs on the live runner.
…t#12)

* fix: skip IPv6 DAD wait for IPv4-only nodes (use_v6=False)

* fix: start interface captures only once the interface is up
Poll for interface and topology convergence faster and probe hosts in
parallel; tune RIPng and STP timers in test topologies; and skip IPv6
address allocation where it buys nothing (GRE and OSPF parameter tests).
Make route assertions deterministic: assert_routing_table() waits for
prefixes to be present or absent within a bounded deadline and dumps the
actual table on failure. The link-restoration tests wait for each router
to relearn the far host subnet in both address families before probing
connectivity.
scripts/run-tests-parallel.sh distributes the suite across pytest-xdist
workers, each exec'd through scripts/py-unshare.sh into private
mount/net/pid namespaces. The CI test job uses the runner. Results are
identical to serial: 185 passed, 8 skipped.
iakov and others added 29 commits August 31, 2026 15:14
…ble past-header files

Address review feedback:

- The strict check no longer assumes a fixed 24-byte classic pcap header.
  _capture_header_size() reads the file's magic bytes and returns the
  correct header length for classic pcap (LE/BE) and pcapng, defaulting to
  classic pcap for unknown or unreadable files.
- Strict mode now accepts a file that is already stable past its header (a
  bursty writer whose whole exchange landed before the first poll), instead
  of only growing files, so the file-growth fallback is reliable for
  captures that cannot signal readiness. READY remains the primary signal.

Tests expanded to cover both magic byte formats, the bursty stable case and
an undersized/torn header.

Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
test_randomFailure[3] restores all three links at once; under load (and
with the coverage-instrumented full suite) OSPF can take longer than the
120s cap to repopulate r1/r2. The poll exits as soon as routes appear, so
the larger cap only costs time when convergence is genuinely slow.

Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
pytest-cov already merges the per-worker data files into .coverage at the
end of the suite, so 'coverage combine' finds nothing to merge and exits
non-zero. The gate step now tolerates that and runs under sudo because the
suite writes the report files as root.

Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
The fork CI heavy-test run measured 84% branch coverage of the core library
(3746 statements, omitting tests/examples/install); the gate now fails below
that level.

Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
pytest-cov enforces the [tool.coverage.report] fail_under on every --cov
run, so the rootless job (26% on the small rootless subset) failed the
gate meant for the full-suite master run. Disable it there with
--cov-fail-under=0; the strict gate stays enforced by heavy-test on master.

Signed-off-by: Iakov Kirilenko <jake.kirilenko@gmail.com>
…-net#32)

* fix(install): build PCRE1 from source on Ubuntu 26.04 and newer

Ubuntu 26.04 dropped the obsolete PCRE1 packages (libpcre3-dev). libyang
v1, which FRRouting 7.5 requires, is still built against PCRE1, so fall
back to building PCRE 8.45 from a SHA-256-pinned tarball when apt no
longer ships it. Defer TRY003 in install.py, matching the rest of the
codebase.

* build(container): base the test container on ubuntu:26.04

Drop libpcre3-dev from the apt install list (gone in 26.04; install.py
now provisions PCRE1 itself) and refresh the PEP 668 comments.

* fix(install): configure libyang on CMake 4 via policy-version minimum

Ubuntu 26.04 ships CMake 4.x, which dropped compatibility with projects
declaring cmake_minimum_required < 3.5 (libyang v1.0.215 still declares
2.8.12). Pass -DCMAKE_POLICY_VERSION_MINIMUM=3.5, the documented escape
hatch; older CMake ignores the variable.

Validated locally on ubuntu:26.04: PCRE 8.45 source build + libyang
v1.0.215 configure and full build.

* fix(install): make FRR clippy find the uv-managed libpython

FRR's build-time clippy tool embeds the Python that configured it. In the
container that is a uv-managed CPython whose libpython3.12.so.1.0 sits
outside the loader's default search path (on 24.04 the system Python 3.12
masked this; on 26.04 the system Python is 3.13). Expose the running
interpreter's LIBDIR via LD_LIBRARY_PATH for the FRR configure/make/install.

Validated on ubuntu:26.04: a Python-embedding program reproduces the
clippy 'cannot open shared object file' error without the path and runs
with it.

* fix(link): disable iproute2 color so parsers see plain output

mininet runs commands on a pty, and iproute2 >= 6.19 (Ubuntu 26.04)
colorizes `ip address show` and `ip rule list` there, corrupting the
parsed addresses (ipaddress.AddressValueError on the ANSI escapes). Pass
ip -color=never in the two places whose output is parsed; a no-op on
older iproute2.

* fix(install): build PCRE1 with Unicode property support

FRR 7.5's bundled YANG (ietf-inet-types) uses \p{...} regexes, which PCRE
only compiles in with --enable-unicode-properties (Debian's libpcre3-dev
enables it by default, so the system package worked on 24.04). Without it,
libyang fails to parse the module and every FRR daemon aborts its config
check. Validated on ubuntu:26.04: \p{L} compiles and matches.

* fix(host,cli): named zone transfers on BIND 9.20 and iproute2 colors

BIND >= 9.20 denies zone transfers unless allow-transfer is explicit, so
slaves SERVFAIL their zones (24.04's 9.18 allowed transfers by default).
Add allow-transfer to master zones and a directory clause (9.20 requires a
writable working directory to load zones). Also pass ip -color=never for
the CLI's `ip route get` output, which iproute2 >= 6.19 colorizes on a
pty and would otherwise corrupt the asserted output.

* fix(tests): parse multi-address ipv6.dst and resolve hostnames via system DNS

- sr_path: tshark 4.x reports every IPv6 header address for SRv6
  encapsulated packets (SID plus inner destination) as a single comma
  separated field; switching to a tab separator and keeping the
  outermost address restores the through-segment matching that routes
  4-6 rely on.
- host_connected: nmap 7.98's internal resolver ignores /etc/hosts;
  --system-dns routes hostname targets through the system resolver so
  the per-node hosts files are honoured again.
The uploader resolved the repository to the fork (iakov/ipmininet) even on
runs against mimi-net, so the README badge (mimi-net/ipmininet) stayed empty.
Force the slug to github.repository so the report lands on the right repo.
Set PIP_BREAK_SYSTEM_PACKAGES explicitly in pip_install instead of relying
on the environment in CI scripts and the container, and key the distro
package selections off a package_family helper (apt/rpm) to drop the
repeated NAME comparisons. Also sync uv.lock with the 1.2.7 version bump.
OpenR has been unmaintained upstream since 2019 and its test was always
skipped in CI (require_openr). Drop the installer, config generators,
example, test, and docs.
Migrates the routing stack to FRRouting 10.7.1 and libyang v3, adopting the FRR 9+ mgmtd configuration architecture and slimming the test container via a two-stage build.

- install.py: FRR 10.7.1 (tag archive + bootstrap.sh), libyang v3.13.6 (drops the PCRE1 source build and CMake policy hack), new build deps, standard system paths (--sysconfdir=/etc --localstatedir=/var --runstatedir=/var/run), --enable-protobuf=no.
- New Mgmtd daemon and MgmtdBackendDaemon base: staticd, ripd and ripngd are mgmtd backends in FRR 9+ and reject -f/-C; their rendered config is pushed via vtysh once each daemon is up.
- bgpd: skip the -C dry-run (FRR 10.7.1 crashes on 'neighbor activate' in the config-check path; validated at real startup instead).
- ospf6d: attach interfaces to their area with 'ipv6 ospf6 area' inside the interface block ('interface X area Y' was removed in FRR 10.x).
- Containerfile: two-stage builder/runtime build. The builder runs install -a and builds the venv + mimidump; the runtime stage ships only test-facing apt packages plus the compiled artifacts (FRR, libyang, mnexec, exabgp, mimidump, venv + uv CPython toolchain). Final image drops from 1.74 GB to 0.98 GB (613 MB to 277 MB compressed download, -55%).

Full container test suite green (container-test, test, rootless, docs).
* feat(install): migrate ExaBGP to 5.0.13 and install it via pip

ExaBGP 5.x moved to a src/ layout and a console script (entry point
exabgp.application.main:main); the 4.x zipapp build no longer applies.
Install with pip and symlink the entry point to /usr/sbin/exabgp.

* fix(exabgp): adapt to the 5.x subcommand CLI

ExaBGP 5.0.13 moved to a subcommand interface: the config is validated
with `exabgp validate <file>`, the server starts with `exabgp server
<file>` and the env file is passed via --env-file. Update the daemon's
startup/dry-run lines and the availability probe, which used the 4.x
`--validate`/`--env`/global flags.

* refactor(install): install ExaBGP into the uv virtualenv

ExaBGP 5.0.13 now rides in the uv-managed venv (/opt/venv) that the
container image copies wholesale, so the image no longer needs the
/root/exabgp zipapp or a /usr/sbin/exabgp symlink. install_exabgp()
prefers 'uv pip install' inside a virtualenv and falls back to the
system pip. CI dep caching no longer snapshots exabgp: it is a pure
pip install, cheap enough to redo every run.

Also fix _needs_rebuild() accepting None paths so the exabgp guard does
not raise a TypeError on systems where the entry point is absent.

* fix(exabgp): use the CRITICAL log level accepted by ExaBGP 5.x

ExaBGP 4.x read syslog priority names for exabgp.log.level (e.g. CRIT),
but 5.x validates the value against its Python logging levels and aborts
at startup with 'invalid value for log.level : CRIT' when the env file
contains one. Since the daemon died before its BGP session with FRRouting
was ever set up, the injected routes never reached the RIB and the exabgp
tests timed out.

Use CRITICAL, and pass the env file to 'exabgp validate' (as the 4.x
dry-run did via --validate --env) so a bad env file fails the router
config check instead of silently killing the daemon at launch.
…ith 2 updates (mimi-net#34)

Bumps the actions-dependencies group with 2 updates in the / directory: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/upload-artifact` from 4 to 7
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

Updates `codecov/codecov-action` from 4 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](codecov/codecov-action@v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-dependencies
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
test.yaml, heavy-test.yaml and container-test.yaml all ran the same full
test suite on every master push, and master only changes through merged
PRs whose checks already ran all three. Restrict test.yaml to pull
requests: master pushes now run the coverage-gated heavy-test job plus
container-test, dropping the duplicated bare-metal run (~23 min/push).

Also refresh the stale heavy-test header, which still described ExaBGP
as a known-broken daemon skipped in its own test module.
…et#38)

test(srv6): wait for the measured IPv6 paths to converge

The path assertions measure IPv6 routes, but the test only waited for
IPv4 connectivity first. Under parallel CI load OSPFv6 could still be
settling when sr_path started probing, so it returned an empty path and
the test flaked on the routesN parametrizations.

Only the endpoints exercised by each assertion are guaranteed to become
reachable over IPv6 in this topology, so wait for the exact src/dst pair
each path measures instead of asserting full IPv6 host connectivity.
…gnores (mimi-net#41)

ci(coverage): raise gate to 85 and clear product PLR2004 ignores

Raise the full-suite coverage floor from 84 to 85%. To make room for
removing PLR2004 per-file ignores, introduce IP_V4/IP_V6 constants in
ipmininet/utils.py and use them for the product IP-version comparisons
in named, ipnet, link, router/config/utils, and zebra. Give the legacy
LinkDescription integer indexes (0/1/3) named constants as well. Delete
now-dead ignore entries (install.py E501+TRY003, test_gre/test_sshd
PLR2004) that had zero live violations.
…et#42)

Replace every remaining PLR2004 (magic-value-comparison) violation in
product code, tests and examples with named constants, and drop the
PLR2004 per-file ignores so the rule now covers the whole tree:

- srv6.py: lowest reserved Linux table number (MIN_RESERVED_TABLE)
- examples/exabgp_prefix_injector.py: max prefixes per AFI
- tests/test_link.py: default/updated IPv4 and IPv6 prefix lengths
- tests/test_misc.py: MAC length plus utils.IP_V4/IP_V6 reuse
- tests/test_srv6.py: probe-grouping window and tshark field count
- tests/test_tc.py: ping count and required matching samples
- tests/utils.py: identical traceroutes before declaring convergence

No per-file-ignores for PLR2004 remain, so any new magic constant in a
comparison now fails ruff check in CI and pre-commit.
…imi-net#43)

* test(coverage): cover the dnsmasq, DHCPRelay and IPOVSSwitch modules

* test(coverage): fix rstp substring assert and xdist basetemp race
* refactor(lint): drop circular-import workarounds and split router configs

* style(lint): replace file-wide PLR/PLC/TRY003 ignores with targeted noqa
…o tests (mimi-net#47)

* test(coverage): expand scenario coverage toward the 90 gate

Add rootless scenario tests exercising the zebra routing-policy DSL and
the IPTopo/Subnet/NetworkCapture APIs, extend the rootful topology DB and
link-failure suites with error-path and API corner-case scenarios, and
enable workflow_dispatch on the pull-request test workflows so the full
suite can be measured on this branch. Make the heavy-test coverage gate
enforcement fail the job again by running 'coverage report -m' last.

* test(topologydb): resolve the h1-r1 interface by link lookup

mininet indexes interfaces by their own name, not by the peer node name,
so looking up net['h1'].intf('r1') raises KeyError. Resolve the interface
through connectionsTo() instead.

* test(topologydb): make the DB lookup scenarios robust to IP layout

Derive interface lookups from the recorded interface names and the DB
records themselves instead of assuming naming or address-format details
of the underlying interfaces, which differ before the network is started.

* ci(coverage): raise the full-suite gate to 90

The scenario tests push the blended total (statements + branch arcs) to
90.9%, so the gated master heavy-test can now enforce fail_under = 90.
…imi-net#48)

* fix(zebra): always set PrefixListEntry.ge, including for 'any'

The 'any' prefix-list entry returned early from __init__ before assigning
.ge, leaving it undefined. Template code that renders the prefix-list then
raises AttributeError when it checks 'if e.ge'. Initialize .ge to None so
'all' entries expose a consistent attribute surface.

* test(coverage): cover ExaBGP attribute model, link descriptions and capture headers

Adds rootless scenario tests that exercise code previously only reachable
inside rootful or hardware flows:

- test_exabgp_model_scenarios builds known and hex-encoded attributes,
  flag bit layouts and route lookups, closing gaps in the ExaBGP value model.
- test_topo_scenarios now drives LinkDescription indexing/ordering/errors and
  the pcap/pcapng magic-byte sniffing, covering the description and capture
  header helpers rootlessly.
- Registers the new module in the heavy suite file list.

* test(topo_scenarios): drop unused os import in capture header test

* test(topo_scenarios): write capture headers outside the pytest basetemp

The xdist workers run in a private mount namespace with a fresh /tmp
(scripts/py-unshare.sh), so pytest's basetemp directory does not survive
into the worker and the tmp_path fixture flaked at session wrap-up on the
heavy-test run. Follow the TemporaryDirectory convention already used by
test_network_capture.py for these rootless capture-header tests.
…mi-net#49)

* test: reuse net teardown and parametrize rootless scenario tables

- add run_ipnet() context manager to tests/utils.py (net.start with a
  guaranteed net.stop()+cleanup()) and use it in test_misc, test_network_capture
  and test_topologydb instead of hand-rolled try/finally scaffolding
- parametrize test_dhcp dnsmasq pid lookups and the test_topo_scenarios subnet
  overlay cases into data tables
- drop test_pure duplicates (ConfigDict, ip_statement) covered by richer sibling
  tests; import the pcap magic constants instead of redefining them

* test: collapse IGP/BGP network tests onto shared scenario helpers

Add assert_config_file, assert_all_paths, run_topology_scenario and the shared
IGP path tables to tests/utils.py, then rewrite the rootful daemon tests
(ospf/ospf6/ripng/radv/static/bgp/switch/linkfailure) to use run_ipnet and the
scenario runner instead of hand-rolled start/assert/stop/cleanup scaffolding.
test_bgp local_pref/med/rr/full become one parametrized test.

* refactor: de-duplicate switch capture and IGP config scaffolding

- factor the per-node/per-interface capture start/stop loops of IPSwitch and
  IPOVSSwitch into start_captures/stop_captures helpers
- share is_active_interface and the per-interface ConfigDict builder between
  the OSPF and RIPng configs, and the interface/network population step on the
  common QuaggaDaemon base
- replace the duplicated visited-set BFS in base.py router-id scan and
  utils.find_node by a shared walk_unvisited() generator

* lint: enforce a zero-tolerance duplicate-code gate

The product code and the test suite no longer contain duplicated blocks, so
drop the duplication_max ceiling to 0, scope scripts/check-duplicates.sh to
exclude the standalone examples/install demos, and add a local pre-commit hook
that runs it before every push.

* fix(ospf): keep OSPF.is_active_interface for the ospf6 subclass

OSPF6 subclasses OSPF and calls is_active_interface from its own
_build_interfaces; delegate to the shared is_l3router_interface helper instead
of dropping the method.

* fix(utils): expand whole router interface lists in find_node

walk_unvisited must enqueue realIntfList(n.node) for every L3 neighbour like
the original code did, not just the single neighbour interface, otherwise the
BFS never leaves the first broadcast domain (breaks find_node and the DNS zone
server resolution that relies on it).

* style(utils): ruff format find_node
build: prepare v1.3.0 (version bump, metadata and install docs refresh)

- bump version to 1.3.0 and refresh the Python classifiers (>=3.12 only)
- regenerate uv.lock for the new version
- document that install.py provisions FRRouting 10.7.1 (mgmtd; FRR >= 9
  required, so existing environments must be re-provisioned) and ExaBGP
  5.0.13 via pip; bump the pinned install tag in docs
- drop the dead upstream Python 3.7 Jenkins badges from the README
set_local_pref/set_med/set_community now accept an optional order and place
their route-map entry at that order, mirroring deny/permit/filter. Also fix a
latent crash when two set actions on the same peer used different match
conditions: the route-map lookup miss dereferenced None (AttributeError in
add_set_action) instead of appending a new auto-ordered entry. Add a rootless
model test exercising the merge, distinct-entry and explicit-order paths.
Relates to cnp3#99.
build: prepare v1.3.1

Version bump 1.3.0 -> 1.3.1 (patch: bugfix + additive route-map order
parameter, no API or environment change) and bump the pinned install tag
in the docs.
iakov added a commit to iakov/ipmininet that referenced this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants